Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: open all apps in category #2212

Merged
merged 1 commit into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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";

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);
Expand Down Expand Up @@ -97,12 +99,35 @@ 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,
moveCategoryUp,
moveCategoryDown,
remove,
edit,
openAllInNewTabs,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { ActionIcon, Menu } from "@mantine/core";
import {
IconDotsVertical,
IconEdit,
IconExternalLink,
IconRowInsertBottom,
IconRowInsertTop,
IconTransitionBottom,
IconTransitionTop,
IconTrash,
} from "@tabler/icons-react";

import type { MaybePromise } from "@homarr/common/types";
import { useScopedI18n } from "@homarr/translation/client";
import type { TablerIcon } from "@homarr/ui";

Expand All @@ -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 (
<Menu withArrow>
<Menu.Target>
Expand All @@ -37,18 +37,20 @@ export const CategoryMenu = ({ category }: Props) => {
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
{actions.map((action) => (
<React.Fragment key={action.label}>
{"group" in action && <Menu.Label>{t(action.group)}</Menu.Label>}
<Menu.Item
leftSection={<action.icon size="1rem" />}
onClick={action.onClick}
color={"color" in action ? action.color : undefined}
>
{t(action.label)}
</Menu.Item>
</React.Fragment>
))}
{actions.map((action) => {
return (
<React.Fragment key={action.label}>
{"group" in action && <Menu.Label>{t(action.group)}</Menu.Label>}
<Menu.Item
leftSection={<action.icon size="1rem" />}
onClick={action.onClick}
color={"color" in action ? action.color : undefined}
>
{t(action.label)}
</Menu.Item>
</React.Fragment>
);
})}
</Menu.Dropdown>
</Menu>
);
Expand Down Expand Up @@ -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<void>;
color?: string;
group?: string;
}
14 changes: 14 additions & 0 deletions apps/nextjs/src/components/board/sections/category/filter.ts
Original file line number Diff line number Diff line change
@@ -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"],
}));
};
7 changes: 6 additions & 1 deletion packages/translation/src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,8 @@
"moveUp": "Move up",
"moveDown": "Move down",
"createAbove": "New category above",
"createBelow": "New category below"
"createBelow": "New category below",
"openAllInNewTabs": "Open all in tabs"
},
"create": {
"title": "New category",
Expand All @@ -965,6 +966,10 @@
"create": "New category",
"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."
}
}
},
Expand Down