diff --git a/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx b/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx index 5ac4c9e09..15de9c00c 100644 --- a/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx +++ b/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx @@ -1,5 +1,6 @@ import { useCallback } from "react"; +import { fetchApi } from "@homarr/api/client"; import { createId } from "@homarr/db/client"; import { useConfirmModal, useModalAction } from "@homarr/modals"; import { useI18n } from "@homarr/translation/client"; @@ -7,6 +8,7 @@ import { useI18n } from "@homarr/translation/client"; import type { CategorySection } from "~/app/[locale]/boards/_types"; import { useCategoryActions } from "./category-actions"; import { CategoryEditModal } from "./category-edit-modal"; +import { filterByItemKind } from "./filter"; export const useCategoryMenuActions = (category: CategorySection) => { const { openModal } = useModalAction(CategoryEditModal); @@ -97,6 +99,28 @@ export const useCategoryMenuActions = (category: CategorySection) => { ); }, [category, openModal, renameCategory, t]); + const openAllInNewTabs = useCallback(async () => { + const appIds = filterByItemKind(category.items, "app").map((item) => { + return item.options.appId; + }); + + const apps = await fetchApi.app.byIds.query(appIds); + 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.category.openAllInNewTabs.title"), + children: t("section.category.openAllInNewTabs.text"), + }); + break; + } + }, [category, t, openConfirmModal]); + return { addCategoryAbove, addCategoryBelow, @@ -104,5 +128,6 @@ export const useCategoryMenuActions = (category: CategorySection) => { moveCategoryDown, remove, edit, + openAllInNewTabs, }; }; diff --git a/apps/nextjs/src/components/board/sections/category/category-menu.tsx b/apps/nextjs/src/components/board/sections/category/category-menu.tsx index 80e9cd96e..69c72a347 100644 --- a/apps/nextjs/src/components/board/sections/category/category-menu.tsx +++ b/apps/nextjs/src/components/board/sections/category/category-menu.tsx @@ -5,6 +5,7 @@ import { ActionIcon, Menu } from "@mantine/core"; import { IconDotsVertical, IconEdit, + IconExternalLink, IconRowInsertBottom, IconRowInsertTop, IconTransitionBottom, @@ -12,6 +13,7 @@ import { IconTrash, } from "@tabler/icons-react"; +import type { MaybePromise } from "@homarr/common/types"; import { useScopedI18n } from "@homarr/translation/client"; import type { TablerIcon } from "@homarr/ui"; @@ -27,8 +29,6 @@ export const CategoryMenu = ({ category }: Props) => { const actions = useActions(category); const t = useScopedI18n("section.category"); - if (actions.length === 0) return null; - return (
); @@ -106,15 +108,21 @@ const useEditModeActions = (category: CategorySection) => { ] as const satisfies ActionDefinition[]; }; -// TODO: once apps are added we can use this for the open many apps action -const useNonEditModeActions = (_category: CategorySection) => { - return [] as const satisfies ActionDefinition[]; +const useNonEditModeActions = (category: CategorySection) => { + const { openAllInNewTabs } = useCategoryMenuActions(category); + return [ + { + icon: IconExternalLink, + label: "action.openAllInNewTabs", + onClick: openAllInNewTabs, + }, + ] as const satisfies ActionDefinition[]; }; interface ActionDefinition { icon: TablerIcon; label: string; - onClick: () => void; + onClick: () => MaybePromise