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

@@ -0,0 +1,67 @@
import { ActionIcon, Avatar, Button, Group, Stack, Text } from "@mantine/core";
import { IconClock, IconX } from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useModalAction } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { createWidgetDefinition } from "../definition";
import { optionsBuilder } from "../options";
import { AppSelectModal } from "./app-select-modal";
export const { definition, componentLoader } = createWidgetDefinition("bookmarks", {
icon: IconClock,
options: optionsBuilder.from((factory) => ({
title: factory.text(),
layout: factory.select({
options: (["grid", "row", "column"] as const).map((value) => ({
value,
label: (t) => t(`widget.bookmarks.option.layout.option.${value}.label`),
})),
defaultValue: "column",
}),
items: factory.sortableItemList<RouterOutputs["app"]["all"][number], string>({
ItemComponent: ({ item, handle: Handle, removeItem, rootAttributes }) => {
return (
<Group {...rootAttributes} tabIndex={0} justify="space-between" wrap="nowrap">
<Group wrap="nowrap">
<Handle />
<Group>
<Avatar src={item.iconUrl} alt={item.name} />
<Stack gap={0}>
<Text>{item.name}</Text>
</Stack>
</Group>
</Group>
<ActionIcon variant="transparent" color="red" onClick={removeItem}>
<IconX size={20} />
</ActionIcon>
</Group>
);
},
AddButton({ addItem, values }) {
const { openModal } = useModalAction(AppSelectModal);
const t = useI18n();
return (
<Button onClick={() => openModal({ onSelect: addItem, presentAppIds: values })}>
{t("widget.bookmarks.option.items.add")}
</Button>
);
},
uniqueIdentifier: (item) => item.id,
useData: (initialIds) => {
const { data, error, isLoading } = clientApi.app.byIds.useQuery(initialIds);
return {
data,
error,
isLoading,
};
},
}),
})),
}).withDynamicImport(() => import("./component"));