import type React from "react"; import type { DraggableAttributes, UniqueIdentifier } from "@dnd-kit/core"; import type { ActionIconProps } from "@mantine/core"; import type { IntegrationKind } from "@homarr/definitions"; import type { ZodType } from "@homarr/validation"; import { z } from "@homarr/validation"; import type { inferSelectOptionValue, SelectOption } from "./_inputs/widget-select-input"; interface CommonInput { defaultValue?: TType; withDescription?: boolean; } interface TextInput extends CommonInput { validate?: z.ZodType; } interface MultiSelectInput extends CommonInput[]> { options: TOptions; searchable?: boolean; } export interface SortableItemListInput extends Omit, "withDescription"> { AddButton: (props: { addItem: (item: TItem) => void; values: TOptionValue[] }) => React.ReactNode; ItemComponent: (props: { item: TItem; removeItem: () => void; rootAttributes: DraggableAttributes; handle: (props: Partial>) => React.ReactNode; }) => React.ReactNode; uniqueIdentifier: (item: TItem) => TOptionValue; useData: (values: TOptionValue[]) => { data: TItem[] | undefined; isLoading: boolean; error: unknown }; } interface SelectInput extends CommonInput> { options: TOptions; searchable?: boolean; } interface NumberInput extends CommonInput { validate: z.ZodNumber; step?: number; } interface SliderInput extends CommonInput { validate: z.ZodNumber; step?: number; } export interface OptionLocation { name: string; latitude: number; longitude: number; } const optionsFactory = { switch: (input?: CommonInput) => ({ type: "switch" as const, defaultValue: input?.defaultValue ?? false, withDescription: input?.withDescription ?? false, }), text: (input?: TextInput) => ({ type: "text" as const, defaultValue: input?.defaultValue ?? "", withDescription: input?.withDescription ?? false, validate: input?.validate, }), multiSelect: (input: MultiSelectInput) => ({ type: "multiSelect" as const, defaultValue: input.defaultValue ?? [], options: input.options, searchable: input.searchable ?? false, withDescription: input.withDescription ?? false, }), select: (input: SelectInput) => ({ type: "select" as const, defaultValue: (input.defaultValue ?? input.options[0]) as inferSelectOptionValue, options: input.options, searchable: input.searchable ?? false, withDescription: input.withDescription ?? false, }), number: (input: NumberInput) => ({ type: "number" as const, defaultValue: input.defaultValue ?? ("" as const), step: input.step, withDescription: input.withDescription ?? false, validate: input.validate, }), slider: (input: SliderInput) => ({ type: "slider" as const, defaultValue: input.defaultValue ?? input.validate.minValue ?? 0, step: input.step, withDescription: input.withDescription ?? false, validate: input.validate, }), location: (input?: CommonInput) => ({ type: "location" as const, defaultValue: input?.defaultValue ?? { name: "", latitude: 0, longitude: 0, }, withDescription: input?.withDescription ?? false, validate: z.object({ name: z.string().min(1), latitude: z.number(), longitude: z.number(), }), }), multiText: (input?: CommonInput & { validate?: ZodType }) => ({ type: "multiText" as const, defaultValue: input?.defaultValue ?? [], withDescription: input?.withDescription ?? false, values: [] as string[], validate: input?.validate, }), app: () => ({ type: "app" as const, defaultValue: "", withDescription: false, }), sortableItemList: ( input: SortableItemListInput, ) => ({ type: "sortableItemList" as const, defaultValue: [] as TOptionValue[], itemComponent: input.ItemComponent, addButton: input.AddButton, uniqueIdentifier: input.uniqueIdentifier, useData: input.useData, withDescription: false, }), }; type WidgetOptionFactory = typeof optionsFactory; export type WidgetOptionDefinition = | ReturnType]> // We allow any here as it's already type guarded with Record and it still infers the correct type // eslint-disable-next-line @typescript-eslint/no-explicit-any | ReturnType>; export type WidgetOptionsRecord = Record; export type WidgetOptionType = WidgetOptionDefinition["type"]; export type WidgetOptionOfType = Extract; type inferOptionFromDefinition = TDefinition["defaultValue"]; export type inferOptionsFromDefinition = { [key in keyof TOptions]: inferOptionFromDefinition; }; interface FieldConfiguration { shouldHide: (options: inferOptionsFromDefinition, integrationKinds: IntegrationKind[]) => boolean; } type ConfigurationInput = Partial< Record> >; const createOptions = ( optionsCallback: (factory: WidgetOptionFactory) => TOptions, configuration?: ConfigurationInput, ) => { const obj = {} as Record; const options = optionsCallback(optionsFactory); for (const key in options) { obj[key] = { ...configuration?.[key], ...options[key], }; } return obj as { [key in keyof TOptions]: TOptions[key] & FieldConfiguration; }; }; type OptionsBuilder = typeof createOptions; export type OptionsBuilderResult = ReturnType; export const optionsBuilder = { from: createOptions, };