import { useEffect, useRef, useState } from "react"; import { ActionIcon, Menu } from "@mantine/core"; import { IconCopy, IconDotsVertical, IconLayoutKanban, IconPencil, IconTrash } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; import { useEditMode } from "@homarr/boards/edit-mode"; import { useConfirmModal, useModalAction } from "@homarr/modals"; import { useSettings } from "@homarr/settings"; import { useI18n, useScopedI18n } from "@homarr/translation/client"; import type { WidgetDefinition } from "@homarr/widgets/definition"; import type { SectionItem } from "~/app/[locale]/boards/_types"; import { useSectionContext } from "../sections/section-context"; import { useItemActions } from "./item-actions"; import { LazyItemMoveModal, preloadItemMoveModal } from "./lazy-item-move-modal"; import { LazyWidgetEditModal, preloadWidgetEditModal } from "./lazy-widget-edit-modal"; interface BoardItemMenuProps { offset: number; item: SectionItem; definition: WidgetDefinition; resetErrorBoundary?: () => void; } export const BoardItemMenu = (props: BoardItemMenuProps) => { const { data: session } = useSession(); const [isEditMode] = useEditMode(); if (!session || !isEditMode) return null; return ; }; const BoardItemMenuInner = ({ offset, item, definition, resetErrorBoundary }: BoardItemMenuProps) => { const refResetErrorBoundaryOnNextRender = useRef(false); const tItem = useScopedI18n("item"); const t = useI18n(); const { openModal } = useModalAction(LazyWidgetEditModal); const { openModal: openMoveModal } = useModalAction(LazyItemMoveModal); const { openConfirmModal } = useConfirmModal(); const [isMenuOpen, setIsMenuOpen] = useState(false); const hasSupportedIntegrations = "supportedIntegrations" in definition && (definition.supportedIntegrations?.length ?? 0) > 0; const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, duplicateItem, removeItem } = useItemActions(); const { data: integrationData, isPending } = clientApi.integration.all.useQuery(undefined, { enabled: isMenuOpen && hasSupportedIntegrations, }); const { gridstack } = useSectionContext().refs; const settings = useSettings(); // Reset error boundary on next render if item has been edited useEffect(() => { if (refResetErrorBoundaryOnNextRender.current) { resetErrorBoundary?.(); refResetErrorBoundaryOnNextRender.current = false; } }, [item, resetErrorBoundary]); const openEditModal = () => { openModal( { kind: item.kind, definition, value: { advancedOptions: item.advancedOptions, options: item.options, integrationIds: item.integrationIds, }, onSuccessfulEdit: ({ options, integrationIds, advancedOptions }) => { updateItemOptions({ itemId: item.id, newOptions: options, }); updateItemAdvancedOptions({ itemId: item.id, newAdvancedOptions: advancedOptions, }); updateItemIntegrations({ itemId: item.id, newIntegrations: integrationIds, }); refResetErrorBoundaryOnNextRender.current = true; }, integrationData: (integrationData ?? []).filter( (integration) => "supportedIntegrations" in definition && (definition.supportedIntegrations as string[]).some((kind) => kind === integration.kind), ), integrationSupport: "supportedIntegrations" in definition, settings, itemId: item.id, appId: item.kind === "app" ? (item.options.appId as string | undefined) : undefined, }, { title(t) { return `${t("item.edit.title")} - ${t(`widget.${item.kind}.name`)}`; }, }, ); }; const openRemoveModal = () => { openConfirmModal({ title: tItem("remove.title"), children: tItem("remove.message"), onConfirm: () => { removeItem({ itemId: item.id }); }, }); }; return ( {tItem("menu.label.settings")} } onClick={openEditModal} onFocus={preloadWidgetEditModal} onPointerEnter={preloadWidgetEditModal} disabled={hasSupportedIntegrations && isPending} > {tItem("action.edit")} } onFocus={preloadItemMoveModal} onPointerEnter={preloadItemMoveModal} onClick={() => { if (!gridstack.current) return; openMoveModal({ item, columnCount: gridstack.current.getColumn(), gridStack: gridstack.current }); }} > {tItem("action.moveResize")} } onClick={() => duplicateItem({ itemId: item.id })}> {tItem("action.duplicate")} {t("common.dangerZone")} } onClick={openRemoveModal}> {tItem("action.remove")} ); };