From e14da6f747c507ce797da1f28c95f0d7ce4cc7e4 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Fri, 22 May 2026 09:58:29 +0200 Subject: [PATCH] feat: auto-open widget settings on add with all integrations pre-selected When adding a widget from the "Choose item to add" modal, the widget edit modal now opens immediately after placement, allowing the user to configure options and integrations before the widget renders. All available integrations for the widget are selected by default. --- .../board/items/actions/create-item.ts | 8 +-- .../board/items/item-select-modal.tsx | 51 +++++++++++++++++-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/apps/nextjs/src/components/board/items/actions/create-item.ts b/apps/nextjs/src/components/board/items/actions/create-item.ts index f71e5aed0..42b619364 100644 --- a/apps/nextjs/src/components/board/items/actions/create-item.ts +++ b/apps/nextjs/src/components/board/items/actions/create-item.ts @@ -8,12 +8,14 @@ import { getFirstEmptyPosition } from "./empty-position"; import { getSectionElements } from "./section-elements"; export interface CreateItemInput { + id?: string; kind: WidgetKind; options?: Record; + integrationIds?: string[]; } export const createItemCallback = - ({ kind, options = {} }: CreateItemInput) => + ({ id, kind, options = {}, integrationIds = [] }: CreateItemInput) => (previous: Board): Board => { const firstSection = previous.sections .filter((section): section is EmptySection => section.kind === "empty") @@ -23,11 +25,11 @@ export const createItemCallback = if (!firstSection) return previous; const widget = { - id: createId(), + id: id ?? createId(), kind, options, layouts: createItemLayouts(previous, firstSection), - integrationIds: [], + integrationIds, advancedOptions: { title: null, customCssClasses: [], diff --git a/apps/nextjs/src/components/board/items/item-select-modal.tsx b/apps/nextjs/src/components/board/items/item-select-modal.tsx index 5066f5539..fa154116b 100644 --- a/apps/nextjs/src/components/board/items/item-select-modal.tsx +++ b/apps/nextjs/src/components/board/items/item-select-modal.tsx @@ -2,20 +2,26 @@ import { useMemo, useState } from "react"; import { Avatar, Button, Card, Center, Grid, Group, Input, Stack, Text, Tooltip } from "@mantine/core"; import { IconSearch } from "@tabler/icons-react"; -import { objectEntries } from "@homarr/common"; +import { clientApi } from "@homarr/api/client"; +import { createId, objectEntries } from "@homarr/common"; import { getIconUrl, getIntegrationName } from "@homarr/definitions"; import type { IntegrationKind, WidgetKind } from "@homarr/definitions"; -import { createModal } from "@homarr/modals"; +import { createModal, useModalAction } from "@homarr/modals"; +import { useSettings } from "@homarr/settings"; import { useI18n } from "@homarr/translation/client"; import type { TablerIcon } from "@homarr/ui"; -import { widgetImports } from "@homarr/widgets"; +import { reduceWidgetOptionsWithDefaultValues, widgetImports } from "@homarr/widgets"; +import { WidgetEditModal } from "@homarr/widgets/modals"; import { useItemActions } from "./item-actions"; export const ItemSelectModal = createModal(({ actions }) => { const [search, setSearch] = useState(""); const t = useI18n(); - const { createItem } = useItemActions(); + const { createItem, updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations } = useItemActions(); + const { openModal: openEditModal } = useModalAction(WidgetEditModal); + const { data: integrationData } = clientApi.integration.all.useQuery(); + const settings = useSettings(); const items = useMemo( () => @@ -40,8 +46,43 @@ export const ItemSelectModal = createModal(({ actions }) => { ); const handleAdd = (kind: WidgetKind) => { - createItem({ kind }); + const definition = widgetImports[kind].definition; + const hasIntegrationSupport = "supportedIntegrations" in definition; + + const matchingIntegrations = hasIntegrationSupport + ? (integrationData ?? []).filter((integration) => + (definition.supportedIntegrations as string[]).includes(integration.kind), + ) + : []; + + const integrationIds = matchingIntegrations.map((i) => i.id); + const itemId = createId(); + const defaultOptions = reduceWidgetOptionsWithDefaultValues(kind, settings); + + createItem({ id: itemId, kind, integrationIds }); actions.closeModal(); + + openEditModal( + { + kind, + value: { + advancedOptions: { title: null, customCssClasses: [], borderColor: "" }, + options: defaultOptions, + integrationIds, + }, + onSuccessfulEdit: ({ options, integrationIds: newIntegrationIds, advancedOptions }) => { + updateItemOptions({ itemId, newOptions: options }); + updateItemAdvancedOptions({ itemId, newAdvancedOptions: advancedOptions }); + updateItemIntegrations({ itemId, newIntegrations: newIntegrationIds }); + }, + integrationData: matchingIntegrations, + integrationSupport: hasIntegrationSupport, + settings, + }, + { + title: (titleT) => `${titleT("item.edit.title")} - ${titleT(`widget.${kind}.name`)}`, + }, + ); }; return (