import type { LoaderComponent } from "next/dynamic"; import type { IntegrationKind, WidgetKind } from "@homarr/definitions"; import type { TablerIconsProps } from "@homarr/ui"; import type { WidgetImports } from "."; import type { inferOptionsFromDefinition, WidgetOptionsRecord, } from "./options"; import type { IntegrationSelectOption } from "./widget-integration-select"; type ServerDataLoader = () => Promise<{ default: (props: WidgetProps) => Promise>; }>; const createWithDynamicImport = < TKind extends WidgetKind, TDefinition extends WidgetDefinition, TServerDataLoader extends ServerDataLoader | undefined, >( kind: TKind, definition: TDefinition, serverDataLoader: TServerDataLoader, ) => ( componentLoader: () => LoaderComponent< WidgetComponentProps & (TServerDataLoader extends ServerDataLoader ? { serverData: Awaited< ReturnType>["default"]> >; } : never) >, ) => ({ definition: { ...definition, kind, }, kind, serverDataLoader, componentLoader, }); const createWithServerData = ( kind: TKind, definition: TDefinition, ) => >( serverDataLoader: TServerDataLoader, ) => ({ definition: { ...definition, kind, }, kind, serverDataLoader, withDynamicImport: createWithDynamicImport( kind, definition, serverDataLoader, ), }); export const createWidgetDefinition = < TKind extends WidgetKind, TDefinition extends WidgetDefinition, >( kind: TKind, definition: TDefinition, ) => ({ withServerData: createWithServerData(kind, definition), withDynamicImport: createWithDynamicImport(kind, definition, undefined), }); export interface WidgetDefinition { icon: (props: TablerIconsProps) => JSX.Element; supportedIntegrations?: IntegrationKind[]; options: WidgetOptionsRecord; } export interface WidgetProps { options: inferOptionsFromDefinition>; integrations: inferIntegrationsFromDefinition< WidgetImports[TKind]["definition"] >; } type inferServerDataForKind = WidgetImports[TKind] extends { serverDataLoader: ServerDataLoader } ? Awaited< ReturnType< Awaited< ReturnType >["default"] > > : undefined; export type WidgetComponentProps = WidgetProps & { serverData?: inferServerDataForKind; }; type inferIntegrationsFromDefinition = TDefinition extends { supportedIntegrations: infer TSupportedIntegrations; } // check if definition has supportedIntegrations ? TSupportedIntegrations extends IntegrationKind[] // check if supportedIntegrations is an array of IntegrationKind ? IntegrationSelectOptionFor[] // if so, return an array of IntegrationSelectOptionFor : IntegrationSelectOption[] // otherwise, return an array of IntegrationSelectOption without specifying the kind : IntegrationSelectOption[]; interface IntegrationSelectOptionFor { id: string; name: string; url: string; kind: TIntegration[number]; } export type WidgetOptionsRecordOf = WidgetImports[TKind]["definition"]["options"];