From f10bb3ae23fefc9779332733706aa567cc40ba48 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 23 May 2026 16:19:36 +0200 Subject: [PATCH] feat: auto-open widget settings on add with all integrations pre-selected (#5767) 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 (