"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import { Accordion, Alert, ColorInput, MultiSelect, NumberInput, Select, Slider, Stack, Switch, Text, Textarea, TextInput, } from "@mantine/core"; import { DateInput, TimeInput } from "@mantine/dates"; import { IconAlertTriangle } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useOptionalBoard } from "@homarr/boards/context"; import type { CustomWidgetOption } from "@homarr/custom-widgets/core"; import { normalizeCustomWidgetOptions, validateCustomWidgetOptions } from "@homarr/custom-widgets/core"; import { CustomWidgetCodeEditor } from "@homarr/custom-widgets/workbench"; import type { CustomWidgetEditorMessages } from "@homarr/custom-widgets/workbench"; import { IconPicker } from "@homarr/forms-collection"; import { useScopedI18n } from "@homarr/translation/client"; import type { CommonWidgetInputProps } from "./common"; import { useFormContext } from "./form"; export const WidgetCustomWidgetConfigurationInput = ({ property, }: CommonWidgetInputProps<"customWidgetConfiguration">) => { const form = useFormContext(); const labels = useScopedI18n("widget.customApi.configuration"); const board = useOptionalBoard(); const definitionId = typeof form.values.options.definitionId === "string" ? form.values.options.definitionId : ""; const configurationValue = form.values.options[property]; const configuration = useMemo(() => (isRecord(configurationValue) ? configurationValue : {}), [configurationValue]); const available = clientApi.customWidget.available.useQuery( { boardId: board?.id ?? "", currentId: definitionId || undefined }, { enabled: board !== null && Boolean(definitionId) }, ); const definition = available.data?.find((candidate) => candidate.id === definitionId); const options = isRecord(definition?.options) ? (definition.options as Record) : null; const definitionVersion = definition?.updatedAt instanceof Date ? definition.updatedAt.getTime() : undefined; const configurationVersion = form.values.options.configurationVersion; const needsDefinitionRepair = definitionVersion !== undefined && configurationVersion !== definitionVersion; const effectiveConfiguration = useMemo( () => (options && needsDefinitionRepair ? normalizeCustomWidgetOptions(options, configuration) : configuration), [configuration, needsDefinitionRepair, options], ); const issues = useMemo( () => (options ? validateCustomWidgetOptions(options, effectiveConfiguration) : []), [effectiveConfiguration, options], ); useEffect(() => { if (!needsDefinitionRepair || definitionVersion === undefined) return; form.setFieldValue(`options.${property}`, effectiveConfiguration); form.setFieldValue("options.configurationVersion", definitionVersion); }, [definitionVersion, effectiveConfiguration, form, needsDefinitionRepair, property]); useEffect(() => { const path = `options.${property}`; if (issues[0]) form.setFieldError(path, `${issues[0].path}: ${issues[0].message}`); else form.clearFieldError(path); }, [form, issues, property]); if (!definitionId || !options) return null; const entries = Object.entries(options); const regular = entries.filter(([, option]) => !option.advanced); const advanced = entries.filter(([, option]) => option.advanced); return ( {issues[0] && ( }> {labels("needsAttention", { message: issues[0].message })} )} {regular.map(([name, option]) => ( ))} {advanced.length > 0 && ( {labels("advancedOptions")} {advanced.map(([name, option]) => ( ))} )} ); }; function OptionField({ option, path, configuration, definitionId, }: { option: CustomWidgetOption; path: string; configuration: Record; definitionId: string; }) { const form = useFormContext(); const input = form.getInputProps(path, { type: option.control === "switch" ? "checkbox" : "input" }); const common = { label: option.label, description: option.description }; if (option.choicesFrom) return ; if (option.control === "switch") return ; if (["number", "duration"].includes(option.control)) return ; if (option.control === "slider") return ( {option.label} {option.description && ( {option.description} )} ); const choices = option.choices?.map((choice) => ({ label: choice.label, value: String(choice.value) })) ?? []; if (option.control === "select") return (