"use client"; import { Anchor, Box, Card, Divider, Flex, Group, Stack, Text, Title, UnstyledButton } from "@mantine/core"; import combineClasses from "clsx"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; import { useRequiredBoard } from "@homarr/boards/context"; import { useRegisterSpotlightContextResults } from "@homarr/spotlight"; import { MaskedOrNormalImage } from "@homarr/ui"; import type { WidgetComponentProps } from "../definition"; import classes from "./bookmark.module.css"; export default function BookmarksWidget({ options, width, height, itemId }: WidgetComponentProps<"bookmarks">) { const board = useRequiredBoard(); const [data] = clientApi.app.byIds.useSuspenseQuery(options.items, { select(data) { return data.sort((appA, appB) => options.items.indexOf(appA.id) - options.items.indexOf(appB.id)); }, }); useRegisterSpotlightContextResults( `bookmark-${itemId}`, data .filter((app) => app.href !== null) .map((app) => ({ id: app.id, name: app.name, icon: app.iconUrl, interaction() { return { type: "link", // We checked above that app.href is defined // eslint-disable-next-line @typescript-eslint/no-non-null-assertion href: app.href!, newTab: false, }; }, })), [data], ); return ( {options.title.length > 0 && ( {options.title} )} {options.layout === "grid" && ( )} {options.layout !== "grid" && ( )} ); } interface FlexLayoutProps { data: RouterOutputs["app"]["byIds"]; direction: "row" | "column"; hideIcon: boolean; hideHostname: boolean; openNewTab: boolean; hasIconColor: boolean; } const FlexLayout = ({ data, direction, hideIcon, hideHostname, openNewTab, hasIconColor }: FlexLayoutProps) => { const board = useRequiredBoard(); return ( {data.map((app, index) => (
{direction === "row" ? ( ) : ( )}
))}
); }; interface GridLayoutProps { data: RouterOutputs["app"]["byIds"]; width: number; height: number; hideIcon: boolean; hideHostname: boolean; openNewTab: boolean; hasIconColor: boolean; } const GridLayout = ({ data, width, height, hideIcon, hideHostname, openNewTab, hasIconColor }: GridLayoutProps) => { // Calculates the perfect number of columns for the grid layout based on the width and height in pixels and the number of items const columns = Math.ceil(Math.sqrt(data.length * (width / height))); const board = useRequiredBoard(); return ( {data.map((app) => ( ))} ); }; const VerticalItem = ({ app, hideIcon, hideHostname, hasIconColor, size = 30, }: { app: RouterOutputs["app"]["byIds"][number]; hideIcon: boolean; hideHostname: boolean; hasIconColor: boolean; size?: number; }) => { return ( {app.name} {!hideIcon && ( )} {!hideHostname && ( {app.href ? new URL(app.href).hostname : undefined} )} ); }; const HorizontalItem = ({ app, hideIcon, hideHostname, hasIconColor, }: { app: RouterOutputs["app"]["byIds"][number]; hideIcon: boolean; hideHostname: boolean; hasIconColor: boolean; }) => { return ( {!hideIcon && ( )} {app.name} {!hideHostname && ( {app.href ? new URL(app.href).hostname : undefined} )} ); };