77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
import { ActionIcon, Avatar, Group, Stack, Text } from "@mantine/core";
|
|
import { IconBookmark, IconX } from "@tabler/icons-react";
|
|
import { z } from "zod/v4";
|
|
|
|
import type { RouterOutputs } from "@homarr/api";
|
|
import { clientApi } from "@homarr/api/client";
|
|
|
|
import { getSafeAppHref } from "../common/application-url";
|
|
import { createWidgetDefinition } from "../definition";
|
|
import { optionsBuilder } from "../options";
|
|
import { BookmarkAddButton } from "./add-button";
|
|
|
|
export const { definition, componentLoader } = createWidgetDefinition("bookmarks", {
|
|
icon: IconBookmark,
|
|
supportsAdvancedFocus: true,
|
|
queryKey: [["app", "byIds"]],
|
|
createOptions() {
|
|
return optionsBuilder.from((factory) => ({
|
|
title: factory.text(),
|
|
layout: factory.select({
|
|
options: (["grid", "gridHorizontal", "row", "column"] as const).map((value) => ({
|
|
value,
|
|
label: (t) => {
|
|
if (value === "grid") return t("widget.common.layout.option.grid");
|
|
if (value === "row") return t("widget.common.layout.option.horizontal");
|
|
if (value === "column") return t("widget.common.layout.option.vertical");
|
|
return t("widget.bookmarks.option.layout.option.gridHorizontal.label");
|
|
},
|
|
})),
|
|
defaultValue: "column",
|
|
}),
|
|
hideTitle: factory.switch({ defaultValue: false }),
|
|
hideIcon: factory.switch({ defaultValue: false }),
|
|
hideHostname: factory.switch({ defaultValue: false }),
|
|
openNewTab: factory.switch({ defaultValue: true }),
|
|
withBorder: factory.switch({ defaultValue: false }),
|
|
customUrls: factory.multiText({
|
|
validate: z.string().refine((value) => getSafeAppHref(value) !== undefined),
|
|
withDescription: true,
|
|
}),
|
|
items: factory.sortableItemList<RouterOutputs["app"]["selectable"][number], string>({
|
|
ItemComponent: ({ item, 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="var(--mantine-font-size-xl)" />
|
|
</ActionIcon>
|
|
</Group>
|
|
);
|
|
},
|
|
AddButton: BookmarkAddButton,
|
|
uniqueIdentifier: (item) => item.id,
|
|
useData: (initialIds) => {
|
|
const { data, error, isLoading } = clientApi.app.byIds.useQuery(initialIds);
|
|
|
|
return {
|
|
data,
|
|
error,
|
|
isLoading,
|
|
};
|
|
},
|
|
}),
|
|
}));
|
|
},
|
|
}).withDynamicImport(() => import("./component"));
|