import type { ComponentType, MutableRefObject } from "react"; import { Suspense, use, useRef } from "react"; import type { CardProps } from "@mantine/core"; import { Badge, Card, Center, Loader } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; import { QueryErrorResetBoundary, useQueryClient } from "@tanstack/react-query"; import combineClasses from "clsx"; import { NoIntegrationSelectedError } from "@homarr/widgets/errors/classes"; import type { FallbackProps } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary"; import { useRequiredBoard } from "@homarr/boards/context"; import { useEditMode } from "@homarr/boards/edit-mode"; import { useSettings } from "@homarr/settings"; import { WidgetError } from "@homarr/widgets/errors"; import { getWidgetQueryKeys } from "@homarr/widgets/definition"; import type { WidgetComponentProps, WidgetDefinition } from "@homarr/widgets/definition"; import { loadWidgetResources, reduceWidgetOptionsWithDefinition } from "@homarr/widgets/manifest"; import type { SectionItem } from "~/app/[locale]/boards/_types"; import classes from "../sections/item.module.css"; import { useItemActions } from "./item-actions"; import itemContentClasses from "./item-content.module.css"; import { BoardItemMenu } from "./item-menu"; import { WidgetContextMenu } from "./widget-context-menu"; import { removeWidgetDataQueries } from "./widget-query-recovery"; interface BoardItemContentProps { item: SectionItem; } const getOverflowFromKind = (kind: SectionItem["kind"]) => { if (kind === "iframe") return "hidden"; if (kind === "systemResources") return "visible"; return undefined; }; type BoardItemCardProps = CardProps & { item: SectionItem; innerRef: (element: HTMLDivElement | null) => void; }; const BoardItemCard = ({ item, innerRef, children, ...cardProps }: BoardItemCardProps) => { const board = useRequiredBoard(); return ( {children} ); }; const WidgetDefinitionLoadError = ({ error, resetErrorBoundary, item, innerRef, }: FallbackProps & Pick) => ( ); export const BoardItemContent = ({ item }: BoardItemContentProps) => { const { ref, width, height } = useElementSize(); const board = useRequiredBoard(); const widgetStateRef = useRef | null>(null); return ( <> } > } > {item.advancedOptions.title?.trim() && ( {item.advancedOptions.title} )} > ); }; interface LoadedBoardItemContentProps extends Omit { innerRef: (element: HTMLDivElement | null) => void; } const LoadedBoardItemContent = ({ item, innerRef, ...innerContentProps }: LoadedBoardItemContentProps) => { const { definition, Component } = use(loadWidgetResources(item.kind)); return ( ( <> > )} > ); }; interface InnerContentProps { item: SectionItem; width: number; height: number; widgetStateRef: MutableRefObject | null>; definition: WidgetDefinition; Component: ComponentType>; } const InnerContent = ({ item, definition, Component, ...dimensions }: InnerContentProps) => { const settings = useSettings(); const board = useRequiredBoard(); const [isEditMode] = useEditMode(); const options = reduceWidgetOptionsWithDefinition(definition, settings, item.options); const newItem = { ...item, options }; const { updateItemOptions } = useItemActions(); const updateOptions = ({ newOptions }: { newOptions: Record }) => updateItemOptions({ itemId: item.id, newOptions }); const widgetSupportsIntegrations = "supportedIntegrations" in definition && (definition.supportedIntegrations?.length ?? 0) >= 1; const queryClient = useQueryClient(); const widgetQueryKeys = getWidgetQueryKeys(definition, item.kind); return ( {({ reset }) => ( { removeWidgetDataQueries(queryClient, widgetQueryKeys); reset(); }} fallbackRender={({ resetErrorBoundary, error }) => ( <> > )} > updateOptions({ newOptions: { ...options, ...partialNewOptions.newOptions, }, }) } {...dimensions} /> )} ); }; const Throw = ({ when, error }: { when: boolean; error: Error }) => { if (when) throw error; return null; };