Files
homarr/packages/widgets/src/definition.ts
Meier Lukas 975f9123dd feat: add widget server loader (#16)
* wip: Add gridstack board

* wip: Centralize board pages, Add board settings page

* fix: remove cyclic dependency and rename widget-sort to kind

* improve: Add header actions as parallel route

* feat: add item select modal, add category edit modal,

* feat: add edit item modal

* feat: add remove item modal

* wip: add category actions

* feat: add saving of board, wip: add app widget

* Merge branch 'main' into add-board

* chore: update turbo dependencies

* chore: update mantine dependencies

* chore: fix typescript errors, lint and format

* feat: add confirm modal to category removal, move items of removed category to above wrapper

* feat: remove app widget to continue in another branch

* feat: add loading spinner until board is initialized

* fix: issue with cellheight of gridstack items

* feat: add translations for board

* fix: issue with translation for settings page

* feat: add widget server loader

* fix: typing issue

* chore: address pull request feedback

* fix: formatting
2024-02-08 07:00:00 +01:00

127 lines
3.6 KiB
TypeScript

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<TKind extends WidgetKind> = () => Promise<{
default: (props: WidgetProps<TKind>) => Promise<Record<string, unknown>>;
}>;
const createWithDynamicImport =
<
TKind extends WidgetKind,
TDefinition extends WidgetDefinition,
TServerDataLoader extends ServerDataLoader<TKind> | undefined,
>(
kind: TKind,
definition: TDefinition,
serverDataLoader: TServerDataLoader,
) =>
(
componentLoader: () => LoaderComponent<
WidgetComponentProps<TKind> &
(TServerDataLoader extends ServerDataLoader<TKind>
? {
serverData: Awaited<
ReturnType<Awaited<ReturnType<TServerDataLoader>>["default"]>
>;
}
: never)
>,
) => ({
definition: {
...definition,
kind,
},
kind,
serverDataLoader,
componentLoader,
});
const createWithServerData =
<TKind extends WidgetKind, TDefinition extends WidgetDefinition>(
kind: TKind,
definition: TDefinition,
) =>
<TServerDataLoader extends ServerDataLoader<TKind>>(
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<TKind extends WidgetKind> {
options: inferOptionsFromDefinition<WidgetOptionsRecordOf<TKind>>;
integrations: inferIntegrationsFromDefinition<
WidgetImports[TKind]["definition"]
>;
}
type inferServerDataForKind<TKind extends WidgetKind> =
WidgetImports[TKind] extends { serverDataLoader: ServerDataLoader<TKind> }
? Awaited<
ReturnType<
Awaited<
ReturnType<WidgetImports[TKind]["serverDataLoader"]>
>["default"]
>
>
: undefined;
export type WidgetComponentProps<TKind extends WidgetKind> =
WidgetProps<TKind> & {
serverData?: inferServerDataForKind<TKind>;
};
type inferIntegrationsFromDefinition<TDefinition extends WidgetDefinition> =
TDefinition extends {
supportedIntegrations: infer TSupportedIntegrations;
} // check if definition has supportedIntegrations
? TSupportedIntegrations extends IntegrationKind[] // check if supportedIntegrations is an array of IntegrationKind
? IntegrationSelectOptionFor<TSupportedIntegrations[number]>[] // if so, return an array of IntegrationSelectOptionFor
: IntegrationSelectOption[] // otherwise, return an array of IntegrationSelectOption without specifying the kind
: IntegrationSelectOption[];
interface IntegrationSelectOptionFor<TIntegration extends IntegrationKind> {
id: string;
name: string;
url: string;
kind: TIntegration[number];
}
export type WidgetOptionsRecordOf<TKind extends WidgetKind> =
WidgetImports[TKind]["definition"]["options"];