feat: open all apps in category (#2212)
This commit is contained in:
@@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
14
apps/nextjs/src/components/board/sections/category/filter.ts
Normal file
14
apps/nextjs/src/components/board/sections/category/filter.ts
Normal 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"],
|
||||||
|
}));
|
||||||
|
};
|
||||||
@@ -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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user