diff --git a/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx b/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx index 049067f69..8f76fda4f 100644 --- a/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx +++ b/apps/nextjs/src/app/[locale]/widgets/[kind]/_content.tsx @@ -13,6 +13,7 @@ import type { IntegrationKind, WidgetKind } from "@homarr/definitions"; import { useModalAction } from "@homarr/modals"; import { showSuccessNotification } from "@homarr/notifications"; import { useScopedI18n } from "@homarr/translation/client"; +import type { BoardItemIntegration } from "@homarr/validation"; import { loadWidgetDynamic, reduceWidgetOptionsWithDefaultValues, @@ -53,7 +54,7 @@ export const WidgetPreviewPageContent = ({ }); const [state, setState] = useState<{ options: Record; - integrations: string[]; + integrations: BoardItemIntegration[]; }>({ options: reduceWidgetOptionsWithDefaultValues(kind, {}), integrations: [], @@ -104,8 +105,10 @@ export const WidgetPreviewPageContent = ({ - integrationData.find((integration) => integration.id === id)!, + (stateIntegration) => + integrationData.find( + (integration) => integration.id === stateIntegration.id, + )!, )} width={dimensions.width} height={dimensions.height} diff --git a/apps/nextjs/src/components/board/items/item-actions.tsx b/apps/nextjs/src/components/board/items/item-actions.tsx index badc39109..83df3aaa8 100644 --- a/apps/nextjs/src/components/board/items/item-actions.tsx +++ b/apps/nextjs/src/components/board/items/item-actions.tsx @@ -2,6 +2,7 @@ import { useCallback } from "react"; import { createId } from "@homarr/db/client"; import type { WidgetKind } from "@homarr/definitions"; +import type { BoardItemIntegration } from "@homarr/validation"; import type { EmptySection, Item } from "~/app/[locale]/boards/_types"; import { useUpdateBoard } from "~/app/[locale]/boards/(content)/_client"; @@ -30,6 +31,11 @@ interface UpdateItemOptions { newOptions: Record; } +interface UpdateItemIntegrations { + itemId: string; + newIntegrations: BoardItemIntegration[]; +} + interface CreateItem { kind: WidgetKind; } @@ -105,6 +111,36 @@ export const useItemActions = () => { [updateBoard], ); + const updateItemIntegrations = useCallback( + ({ itemId, newIntegrations }: UpdateItemIntegrations) => { + updateBoard((previous) => { + if (!previous) return previous; + return { + ...previous, + sections: previous.sections.map((section) => { + // Return same section if item is not in it + if (!section.items.some((item) => item.id === itemId)) + return section; + return { + ...section, + items: section.items.map((item) => { + // Return same item if item is not the one we're moving + if (item.id !== itemId) return item; + return { + ...item, + ...("integrations" in item + ? { integrations: newIntegrations } + : {}), + }; + }), + }; + }), + }; + }); + }, + [updateBoard], + ); + const moveAndResizeItem = useCallback( ({ itemId, ...positionProps }: MoveAndResizeItem) => { updateBoard((previous) => ({ @@ -200,6 +236,7 @@ export const useItemActions = () => { moveItemToSection, removeItem, updateItemOptions, + updateItemIntegrations, createItem, }; }; diff --git a/apps/nextjs/src/components/board/sections/content.tsx b/apps/nextjs/src/components/board/sections/content.tsx index afa3de1db..d034e109c 100644 --- a/apps/nextjs/src/components/board/sections/content.tsx +++ b/apps/nextjs/src/components/board/sections/content.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-unknown-property */ // Ignored because of gridstack attributes +import { useMemo } from "react"; import type { RefObject } from "react"; import { ActionIcon, Card, Menu } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; @@ -13,6 +14,7 @@ import { import combineClasses from "clsx"; import { useAtomValue } from "jotai"; +import { clientApi } from "@homarr/api/client"; import { useConfirmModal, useModalAction } from "@homarr/modals"; import { useScopedI18n } from "@homarr/translation/client"; import { @@ -20,6 +22,7 @@ import { reduceWidgetOptionsWithDefaultValues, useServerDataFor, WidgetEditModal, + widgetImports, } from "@homarr/widgets"; import type { Item } from "~/app/[locale]/boards/_types"; @@ -116,25 +119,42 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => { const { openModal } = useModalAction(WidgetEditModal); const { openConfirmModal } = useConfirmModal(); const isEditMode = useAtomValue(editModeAtom); - const { updateItemOptions, removeItem } = useItemActions(); + const { updateItemOptions, updateItemIntegrations, removeItem } = + useItemActions(); + const { data: integrationData, isPending } = + clientApi.integration.all.useQuery(); + const currentDefinition = useMemo( + () => widgetImports[item.kind].definition, + [item.kind], + ); - if (!isEditMode) return null; + if (!isEditMode || isPending) return null; const openEditModal = () => { openModal({ kind: item.kind, value: { options: item.options, - integrations: item.integrations.map(({ id }) => id), + integrations: item.integrations, }, - onSuccessfulEdit: ({ options, integrations: _ }) => { + onSuccessfulEdit: ({ options, integrations }) => { updateItemOptions({ itemId: item.id, newOptions: options, }); + updateItemIntegrations({ + itemId: item.id, + newIntegrations: integrations, + }); }, - integrationData: [], - integrationSupport: false, + integrationData: (integrationData ?? []).filter( + (integration) => + "supportedIntegrations" in currentDefinition && + (currentDefinition.supportedIntegrations as string[]).some( + (kind) => kind === integration.kind, + ), + ), + integrationSupport: "supportedIntegrations" in currentDefinition, }); }; diff --git a/packages/validation/src/index.ts b/packages/validation/src/index.ts index 6d873cfb4..9d3c503be 100644 --- a/packages/validation/src/index.ts +++ b/packages/validation/src/index.ts @@ -18,4 +18,8 @@ export const validation = { icons: iconsSchemas, }; -export { createSectionSchema, sharedItemSchema } from "./shared"; +export { + createSectionSchema, + sharedItemSchema, + type BoardItemIntegration, +} from "./shared"; diff --git a/packages/validation/src/shared.ts b/packages/validation/src/shared.ts index 7bae24c45..c5aa875a2 100644 --- a/packages/validation/src/shared.ts +++ b/packages/validation/src/shared.ts @@ -11,6 +11,8 @@ export const integrationSchema = z.object({ url: z.string(), }); +export type BoardItemIntegration = z.infer; + export const sharedItemSchema = z.object({ id: z.string(), xOffset: z.number(), diff --git a/packages/widgets/src/modals/widget-edit-modal.tsx b/packages/widgets/src/modals/widget-edit-modal.tsx index ec8abc118..06b72e20a 100644 --- a/packages/widgets/src/modals/widget-edit-modal.tsx +++ b/packages/widgets/src/modals/widget-edit-modal.tsx @@ -5,6 +5,7 @@ import { Button, Group, Stack } from "@mantine/core"; import type { WidgetKind } from "@homarr/definitions"; import { createModal } from "@homarr/modals"; import { useI18n } from "@homarr/translation/client"; +import type { BoardItemIntegration } from "@homarr/validation"; import { widgetImports } from ".."; import { getInputForType } from "../_inputs"; @@ -15,7 +16,7 @@ import { WidgetIntegrationSelect } from "../widget-integration-select"; export interface WidgetEditModalState { options: Record; - integrations: string[]; + integrations: BoardItemIntegration[]; } interface ModalProps {