feat: open all apps in category (#2212)

This commit is contained in:
Manuel
2025-02-03 21:28:01 +01:00
committed by GitHub
parent be603badfe
commit bb67bf5fa2
4 changed files with 71 additions and 19 deletions

View File

@@ -1,5 +1,6 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { fetchApi } from "@homarr/api/client";
import { createId } from "@homarr/db/client"; import { createId } from "@homarr/db/client";
import { useConfirmModal, useModalAction } from "@homarr/modals"; import { useConfirmModal, useModalAction } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client"; 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 type { CategorySection } from "~/app/[locale]/boards/_types";
import { useCategoryActions } from "./category-actions"; import { useCategoryActions } from "./category-actions";
import { CategoryEditModal } from "./category-edit-modal"; import { CategoryEditModal } from "./category-edit-modal";
import { filterByItemKind } from "./filter";
export const useCategoryMenuActions = (category: CategorySection) => { export const useCategoryMenuActions = (category: CategorySection) => {
const { openModal } = useModalAction(CategoryEditModal); const { openModal } = useModalAction(CategoryEditModal);
@@ -97,6 +99,28 @@ export const useCategoryMenuActions = (category: CategorySection) => {
); );
}, [category, openModal, renameCategory, t]); }, [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 { return {
addCategoryAbove, addCategoryAbove,
addCategoryBelow, addCategoryBelow,
@@ -104,5 +128,6 @@ export const useCategoryMenuActions = (category: CategorySection) => {
moveCategoryDown, moveCategoryDown,
remove, remove,
edit, edit,
openAllInNewTabs,
}; };
}; };

View File

@@ -5,6 +5,7 @@ import { ActionIcon, Menu } from "@mantine/core";
import { import {
IconDotsVertical, IconDotsVertical,
IconEdit, IconEdit,
IconExternalLink,
IconRowInsertBottom, IconRowInsertBottom,
IconRowInsertTop, IconRowInsertTop,
IconTransitionBottom, IconTransitionBottom,
@@ -12,6 +13,7 @@ import {
IconTrash, IconTrash,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import type { MaybePromise } from "@homarr/common/types";
import { useScopedI18n } from "@homarr/translation/client"; import { useScopedI18n } from "@homarr/translation/client";
import type { TablerIcon } from "@homarr/ui"; import type { TablerIcon } from "@homarr/ui";
@@ -27,8 +29,6 @@ export const CategoryMenu = ({ category }: Props) => {
const actions = useActions(category); const actions = useActions(category);
const t = useScopedI18n("section.category"); const t = useScopedI18n("section.category");
if (actions.length === 0) return null;
return ( return (
<Menu withArrow> <Menu withArrow>
<Menu.Target> <Menu.Target>
@@ -37,18 +37,20 @@ export const CategoryMenu = ({ category }: Props) => {
</ActionIcon> </ActionIcon>
</Menu.Target> </Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
{actions.map((action) => ( {actions.map((action) => {
<React.Fragment key={action.label}> return (
{"group" in action && <Menu.Label>{t(action.group)}</Menu.Label>} <React.Fragment key={action.label}>
<Menu.Item {"group" in action && <Menu.Label>{t(action.group)}</Menu.Label>}
leftSection={<action.icon size="1rem" />} <Menu.Item
onClick={action.onClick} leftSection={<action.icon size="1rem" />}
color={"color" in action ? action.color : undefined} onClick={action.onClick}
> color={"color" in action ? action.color : undefined}
{t(action.label)} >
</Menu.Item> {t(action.label)}
</React.Fragment> </Menu.Item>
))} </React.Fragment>
);
})}
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>
); );
@@ -106,15 +108,21 @@ const useEditModeActions = (category: CategorySection) => {
] as const satisfies ActionDefinition[]; ] as const satisfies ActionDefinition[];
}; };
// TODO: once apps are added we can use this for the open many apps action const useNonEditModeActions = (category: CategorySection) => {
const useNonEditModeActions = (_category: CategorySection) => { const { openAllInNewTabs } = useCategoryMenuActions(category);
return [] as const satisfies ActionDefinition[]; return [
{
icon: IconExternalLink,
label: "action.openAllInNewTabs",
onClick: openAllInNewTabs,
},
] as const satisfies ActionDefinition[];
}; };
interface ActionDefinition { interface ActionDefinition {
icon: TablerIcon; icon: TablerIcon;
label: string; label: string;
onClick: () => void; onClick: () => MaybePromise<void>;
color?: string; color?: string;
group?: string; group?: string;
} }

View File

@@ -0,0 +1,14 @@
import type { WidgetKind } from "@homarr/definitions";
import type { WidgetComponentProps } from "@homarr/widgets";
import { reduceWidgetOptionsWithDefaultValues } from "@homarr/widgets";
import type { Item } from "~/app/[locale]/boards/_types";
export const filterByItemKind = <TKind extends WidgetKind>(items: Item[], kind: TKind) => {
return items
.filter((item) => item.kind === kind)
.map((item) => ({
...item,
options: reduceWidgetOptionsWithDefaultValues(kind, item.options) as WidgetComponentProps<TKind>["options"],
}));
};

View File

@@ -947,7 +947,8 @@
"moveUp": "Move up", "moveUp": "Move up",
"moveDown": "Move down", "moveDown": "Move down",
"createAbove": "New category above", "createAbove": "New category above",
"createBelow": "New category below" "createBelow": "New category below",
"openAllInNewTabs": "Open all in tabs"
}, },
"create": { "create": {
"title": "New category", "title": "New category",
@@ -966,6 +967,10 @@
"create": "New category", "create": "New category",
"changePosition": "Change position" "changePosition": "Change position"
} }
},
"openAllInNewTabs": {
"title": "Open all in tabs",
"text": "Some browsers may block the bulk-opening of tabs for security reasons. Homarr was unable to open all windows, because your browser blocked this action. Please allow \"Open pop-up windows\" and re-try."
} }
} }
}, },