import { useEffect, useRef, useState } from "react"; import { ActionIcon, Menu } from "@mantine/core"; import { IconCopy, IconDotsVertical, IconLayoutKanban, IconPencil } from "@tabler/icons-react"; import { useSession } from "@homarr/auth/client"; import { useEditMode } from "@homarr/boards/edit-mode"; import { 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 { BoardRemoveConfirmationMenuItem } from "../remove-confirmation-menu-item"; import { useSectionContext } from "../sections/section-context"; import { useItemActions } from "./item-actions"; import { useOpenItemMoveModal } from "./item-move-modal"; import itemContentClasses from "./item-content.module.css"; import { LazyWidgetEditModal, preloadWidgetEditModal } from "./lazy-widget-edit-modal"; interface BoardItemMenuProps { 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 = ({ item, definition, resetErrorBoundary }: BoardItemMenuProps) => { const { data: session } = useSession(); const canDuplicate = item.kind !== "customApi" || (session?.user.permissions.includes("admin") ?? false); const refResetErrorBoundaryOnNextRender = useRef(false); const tItem = useScopedI18n("item"); const t = useI18n(); const { openModal } = useModalAction(LazyWidgetEditModal); const openMoveModal = useOpenItemMoveModal(); const [isMenuOpen, setIsMenuOpen] = useState(false); const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, duplicateItem, removeItem } = useItemActions(); const { integrations: integrationData, section } = useSectionContext(); const settings = useSettings(); const label = item.advancedOptions.title?.trim() || t(`widget.${item.kind}.name`); // 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; }, onIntegrationSaved: resetErrorBoundary, integrationData: (integrationData ?? []).filter( (integration) => integration.permissions.hasUseAccess && "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(translate) { return `${translate("item.edit.title")} - ${translate(`widget.${item.kind}.name`)}`; }, }, ); }; return ( } onClick={openEditModal} onFocus={preloadWidgetEditModal} onPointerEnter={preloadWidgetEditModal} > {tItem("action.edit")} } onClick={() => { openMoveModal({ entry: item, sourceSectionId: section.id, }); }} > {tItem("action.moveResize")} {canDuplicate && ( } onClick={() => duplicateItem({ itemId: item.id })}> {tItem("action.duplicate")} )} removeItem({ itemId: item.id })} /> ); };