import { useCallback, useMemo } from "react"; import { clientApi } from "@homarr/api/client"; import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context"; import { useConfirmModal } from "@homarr/modals"; import { useSettings } from "@homarr/settings"; import { useI18n } from "@homarr/translation/client"; import type { Item } from "~/app/[locale]/boards/_types"; import { filterByItemKind } from "./category/filter"; interface SectionItemLayout { layoutId: string; sectionId: string; } interface SectionLayout { layoutId: string; parentSectionId: string; } interface SectionTreeItem { layouts: readonly SectionItemLayout[]; } interface SectionTreeSection { id: string; kind: string; layouts?: readonly SectionLayout[]; } interface SectionTree { items: readonly TItem[]; sections: readonly SectionTreeSection[]; } /** * Finds every item contained by a section in one responsive layout. Dynamic * sections can be nested, so their descendants are traversed recursively. */ export const getSectionItemsForLayout = ( board: SectionTree, rootSectionId: string, layoutId: string, ) => { const childrenByParent = new Map(); for (const section of board.sections) { if (section.kind !== "dynamic") continue; const layout = section.layouts?.find((candidate) => candidate.layoutId === layoutId); if (!layout) continue; const children = childrenByParent.get(layout.parentSectionId) ?? []; children.push(section.id); childrenByParent.set(layout.parentSectionId, children); } const includedSectionIds = new Set([rootSectionId]); const queue = [rootSectionId]; for (const sectionId of queue) { for (const childSectionId of childrenByParent.get(sectionId) ?? []) { if (includedSectionIds.has(childSectionId)) continue; includedSectionIds.add(childSectionId); queue.push(childSectionId); } } return board.items.filter((item) => item.layouts.some((layout) => layout.layoutId === layoutId && includedSectionIds.has(layout.sectionId)), ); }; export const useOpenSectionApps = (sectionId: string, enabled: boolean) => { const board = useRequiredBoard(); const currentLayoutId = useCurrentLayout(); const settings = useSettings(); const { openConfirmModal } = useConfirmModal(); const t = useI18n(); const appIds = useMemo(() => { const items: Item[] = getSectionItemsForLayout(board, sectionId, currentLayoutId); return Array.from(new Set(filterByItemKind(items, settings, "app").map((item) => item.options.appId))); }, [board, currentLayoutId, sectionId, settings]); const { data: apps = [], isLoading } = clientApi.app.byIds.useQuery(appIds, { enabled: enabled && appIds.length > 0, staleTime: 30_000, }); const open = useCallback(() => { const appsWithUrls = apps.filter((app) => app.href && app.href.length > 0); for (const app of appsWithUrls) { const openedWindow = window.open(app.href ?? undefined); if (openedWindow) continue; openConfirmModal({ title: t("section.openAllInNewTabs.title"), children: t("section.openAllInNewTabs.text"), }); break; } }, [apps, openConfirmModal, t]); return { open, isLoading: enabled && appIds.length > 0 && isLoading, }; };