From 224648a708a166c1129e207a688103f0060f736f Mon Sep 17 00:00:00 2001 From: hirrrooo <99264185+hirrrooo@users.noreply.github.com> Date: Mon, 29 Jun 2026 06:47:34 +0000 Subject: [PATCH] feat(boards): add board-wide open all apps in new tabs action --- .../boards/(content)/_header-actions.tsx | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index cc3868e50..091243d23 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -9,6 +9,7 @@ import { IconBox, IconBoxAlignTop, IconChevronDown, + IconExternalLink, IconLayoutBoard, IconPencil, IconPencilOff, @@ -19,10 +20,12 @@ import { IconSettings, } from "@tabler/icons-react"; -import { clientApi } from "@homarr/api/client"; +import { clientApi, fetchApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; -import { useRequiredBoard } from "@homarr/boards/context"; +import { getCurrentLayout, useRequiredBoard } from "@homarr/boards/context"; import { useEditMode } from "@homarr/boards/edit-mode"; +import { useSettings } from "@homarr/settings"; +import { filterByItemKind } from "~/components/board/sections/category/filter"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { env } from "@homarr/common/env"; import { hotkeys } from "@homarr/definitions"; @@ -46,9 +49,48 @@ export const BoardContentHeaderActions = () => { const board = useRequiredBoard(); const { hasChangeAccess } = useBoardPermissions(board); const { data: demoMode, isLoading } = clientApi.info.isDemoMode.useQuery(); + const settings = useSettings(); + const t = useI18n(); + const { openConfirmModal } = useConfirmModal(); + + const openAllInNewTabs = useCallback(async () => { + const currentLayoutId = getCurrentLayout(board); + const appIds = filterByItemKind( + board.items.filter((item) => item.layouts.some((layout) => layout.layoutId === currentLayoutId)), + settings, + "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; + } + }, [board, t, openConfirmModal, settings]); if (!hasChangeAccess || isLoading) { - return ; + return ( + <> + {!isLoading && ( + + + + )} + + + ); } return ( @@ -57,6 +99,12 @@ export const BoardContentHeaderActions = () => { + {!isEditMode && ( + + + + )} + {!demoMode && (