feat: add bookmark widget (#964)

* feat: add bookmark widget

* fix: item component type issue, widget-ordered-object-list-input item component issue

* feat: add button in items list

* wip

* wip: bookmark options dnd

* wip: improve widget sortable item list

* feat: add sortable item list input to widget edit modal

* feat: implement bookmark widget

* chore: address pull request feedback

* fix: format issues

* fix: lockfile not up to date

* fix: import configuration missing and apps not imported

* fix: bookmark items not sorted

* feat: add flex layouts to bookmark widget

* fix: deepsource issue

* fix: add missing layout bookmarks old-import options mapping

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
Manuel
2024-11-02 18:44:36 +01:00
committed by GitHub
parent f8bdd9c5c7
commit 49c0ebea6d
21 changed files with 889 additions and 32 deletions

View File

@@ -16,3 +16,5 @@ export type OldmarrBookmarkDefinition = CommonOldmarrWidgetDefinition<
layout: "autoGrid" | "horizontal" | "vertical";
}
>;
export type BookmarkApp = OldmarrBookmarkDefinition["options"]["items"][number];

View File

@@ -66,6 +66,7 @@ export const widgetKindMapping = {
"mediaRequests-requestList": "media-requests-list",
"mediaRequests-requestStats": "media-requests-stats",
indexerManager: "indexer-manager",
bookmarks: "bookmark",
healthMonitoring: "health-monitoring",
} satisfies Record<WidgetKind, OldmarrWidgetDefinitions["id"] | null>;
// Use null for widgets that did not exist in oldmarr

View File

@@ -13,6 +13,7 @@ type OptionMapping = {
: {
[OptionsKey in keyof WidgetComponentProps<WidgetKey>["options"]]: (
oldOptions: Extract<OldmarrWidgetDefinitions, { id: WidgetMapping[WidgetKey] }>["options"],
appsMap: Map<string, string>,
) => WidgetComponentProps<WidgetKey>["options"][OptionsKey] | undefined;
};
};
@@ -22,6 +23,22 @@ const optionMapping: OptionMapping = {
linksTargetNewTab: (oldOptions) => oldOptions.openInNewTab,
},
"mediaRequests-requestStats": {},
bookmarks: {
title: (oldOptions) => oldOptions.name,
// It's safe to assume that the app exists, because the app is always created before the widget
// And the mapping is created in insertAppsAsync
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
items: (oldOptions, appsMap) => oldOptions.items.map((item) => appsMap.get(item.id)!),
layout: (oldOptions) => {
const mappedLayouts: Record<typeof oldOptions.layout, WidgetComponentProps<"bookmarks">["options"]["layout"]> = {
autoGrid: "grid",
horizontal: "row",
vertical: "column",
};
return mappedLayouts[oldOptions.layout];
},
},
calendar: {
releaseType: (oldOptions) => [oldOptions.radarrReleaseType],
filterFutureMonths: () => undefined,
@@ -118,11 +135,13 @@ const optionMapping: OptionMapping = {
* Maps the oldmarr options to the newmarr options
* @param kind item kind to map
* @param oldOptions oldmarr options for this item
* @param appsMap map of old app ids to new app ids
* @returns newmarr options for this item or null if the item did not exist in oldmarr
*/
export const mapOptions = <K extends WidgetKind>(
kind: K,
oldOptions: Extract<OldmarrWidgetDefinitions, { id: WidgetMapping[K] }>["options"],
appsMap: Map<string, string>,
) => {
logger.debug(`Mapping old homarr options for widget kind=${kind} options=${JSON.stringify(oldOptions)}`);
if (optionMapping[kind] === null) {
@@ -132,7 +151,7 @@ export const mapOptions = <K extends WidgetKind>(
const mapping = optionMapping[kind];
return objectEntries(mapping).reduce(
(acc, [key, value]) => {
const newValue = value(oldOptions as never);
const newValue = value(oldOptions as never, appsMap);
logger.debug(`Mapping old homarr option kind=${kind} key=${key as string} newValue=${newValue as string}`);
if (newValue !== undefined) {
acc[key as string] = newValue;