diff --git a/public/locales/en/boards/common.json b/public/locales/en/boards/common.json
index 18131dfb1..d4cd5a2ec 100644
--- a/public/locales/en/boards/common.json
+++ b/public/locales/en/boards/common.json
@@ -1,5 +1,12 @@
{
"header": {
"customize": "Customize board"
+ },
+ "category": {
+ "actions": {
+ "add": "Add category",
+ "addAbove": "Add category above",
+ "addBelow": "Add category below"
+ }
}
}
\ No newline at end of file
diff --git a/public/locales/en/layout/common.json b/public/locales/en/layout/common.json
index e11d064d4..b0faeb2e7 100644
--- a/public/locales/en/layout/common.json
+++ b/public/locales/en/layout/common.json
@@ -17,9 +17,6 @@
},
"menu": {
"moveUp": "Move up",
- "moveDown": "Move down",
- "addCategory": "Add category {{location}}",
- "addAbove": "above",
- "addBelow": "below"
+ "moveDown": "Move down"
}
}
\ No newline at end of file
diff --git a/src/components/Board/Sections/Category/CategoryMenu.tsx b/src/components/Board/Sections/Category/CategoryMenu.tsx
new file mode 100644
index 000000000..3ce01813c
--- /dev/null
+++ b/src/components/Board/Sections/Category/CategoryMenu.tsx
@@ -0,0 +1,177 @@
+import { ActionIcon, List, Menu, Stack, Text, createStyles } from '@mantine/core';
+import { modals } from '@mantine/modals';
+import {
+ IconDotsVertical,
+ IconEdit,
+ IconRowInsertBottom,
+ IconRowInsertTop,
+ IconSettings,
+ IconShare3,
+ IconTransitionBottom,
+ IconTransitionTop,
+ IconTrash,
+ TablerIconsProps,
+} from '@tabler/icons-react';
+import { useTranslation } from 'next-i18next';
+import { useCallback, useMemo } from 'react';
+import { useEditModeStore } from '~/components/Dashboard/Views/useEditModeStore';
+import { useCategoryActionHelper } from '~/components/Dashboard/Wrappers/Category/useCategoryActions';
+
+import { AppItem, CategorySection } from '../../context';
+
+type CategoryMenuProps = {
+ category: CategorySection;
+};
+
+export const CategoryMenu = ({ category }: CategoryMenuProps) => {
+ const isEditMode = useEditModeStore((x) => x.enabled);
+ const editModeActions = useEditModeActions(category);
+ const nonEditModeActions = useNonEditModeActions(category);
+ const { t } = useTranslation(['layout/common', 'boards/common', 'common']);
+
+ const Icon = isEditMode ? IconSettings : IconDotsVertical;
+ const actions = isEditMode ? editModeActions : nonEditModeActions;
+
+ return (
+
+ );
+};
+
+const useEditModeActions = (category: CategorySection): ActionDefinition[] => {
+ const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } =
+ useCategoryActionHelper(category);
+
+ return [
+ {
+ icon: IconEdit,
+ label: 'common:edit',
+ onClick: edit,
+ },
+ {
+ icon: IconTrash,
+ color: 'red',
+ label: 'common:remove',
+ onClick: remove,
+ },
+ {
+ group: 'common:changePosition',
+ icon: IconTransitionTop,
+ label: 'menu.moveUp',
+ onClick: moveCategoryUp,
+ },
+ {
+ icon: IconTransitionBottom,
+ label: 'menu.moveDown',
+ onClick: moveCategoryDown,
+ },
+ {
+ group: 'boards/common:category.actions.add',
+ icon: IconRowInsertTop,
+ label: 'boards/common:category.actions.addAbove',
+ onClick: addCategoryAbove,
+ },
+ {
+ icon: IconRowInsertBottom,
+ label: 'boards/common:category.actions.addBelow',
+ onClick: addCategoryBelow,
+ },
+ ];
+};
+
+const useNonEditModeActions = (category: CategorySection): ActionDefinition[] => {
+ const openAllApps = useOpenAllApps();
+ const apps = useMemo(
+ () => category.items.filter((x): x is AppItem => x.type === 'app'),
+ [category.items.length]
+ );
+
+ return [
+ {
+ icon: IconShare3,
+ label: 'actions.category.openAllInNewTab',
+ onClick: openAllApps(apps),
+ },
+ ];
+};
+
+type ActionDefinition = {
+ icon: (props: TablerIconsProps) => JSX.Element;
+ label: string;
+ onClick: () => void;
+ color?: string;
+ group?: string;
+};
+
+const useStyles = createStyles(() => ({
+ listItem: {
+ '& div': {
+ maxWidth: 'calc(100% - 23px)',
+ },
+ },
+}));
+
+const useOpenAllApps = () => {
+ const { classes } = useStyles();
+ const { t } = useTranslation(['layout/common', 'common']);
+
+ return useCallback((apps: AppItem[]) => {
+ return () => {
+ for (let i = 0; i < apps.length; i += 1) {
+ const app = apps[i];
+ const popUp = window.open(app.externalUrl ?? app.internalUrl, app.id);
+
+ if (popUp !== null) continue;
+
+ modals.openConfirmModal({
+ title: {t('modals.blockedPopups.title')},
+ children: (
+
+ {t('modals.blockedPopups.text')}
+
+
+ {t('modals.blockedPopups.list.browserPermission')}
+
+
+ {t('modals.blockedPopups.list.adBlockers')}
+
+
+ {t('modals.blockedPopups.list.otherBrowser')}
+
+
+
+ ),
+ labels: {
+ confirm: t('common:close'),
+ cancel: '',
+ },
+ cancelProps: {
+ display: 'none',
+ },
+ closeOnClickOutside: false,
+ });
+ break;
+ }
+ };
+ }, []);
+};
diff --git a/src/components/Board/category-actions.ts b/src/components/Board/Sections/Category/category-actions.ts
similarity index 98%
rename from src/components/Board/category-actions.ts
rename to src/components/Board/Sections/Category/category-actions.ts
index 7e69ecf64..49158af25 100644
--- a/src/components/Board/category-actions.ts
+++ b/src/components/Board/Sections/Category/category-actions.ts
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
import { v4 } from 'uuid';
import { api } from '~/utils/api';
-import { type CategorySection, type EmptySection } from './context';
+import { type CategorySection, type EmptySection } from '../../context';
type AddCategory = {
name: string;
diff --git a/src/components/Board/Sections/CategorySection.tsx b/src/components/Board/Sections/CategorySection.tsx
new file mode 100644
index 000000000..159983a14
--- /dev/null
+++ b/src/components/Board/Sections/CategorySection.tsx
@@ -0,0 +1,115 @@
+import { Accordion, Group, List, Stack, Text, Title, createStyles } from '@mantine/core';
+import { modals } from '@mantine/modals';
+import { useTranslation } from 'next-i18next';
+import { useCallback, useMemo } from 'react';
+import { AppItem, CategorySection } from '~/components/Board/context';
+
+import { useEditModeStore } from '../../Dashboard/Views/useEditModeStore';
+import { WrapperContent } from '../../Dashboard/Wrappers/WrapperContent';
+import { useGridstack } from '../../Dashboard/Wrappers/gridstack/use-gridstack';
+import { useCardStyles } from '../../layout/Common/useCardStyles';
+import { CategoryMenu } from './Category/CategoryMenu';
+
+interface DashboardCategoryProps {
+ section: CategorySection;
+ isOpened: boolean;
+ toggle: (categoryId: string) => void;
+}
+
+export const BoardCategorySection = ({ section, isOpened, toggle }: DashboardCategoryProps) => {
+ const { refs } = useGridstack({ section });
+ const isEditMode = useEditModeStore((x) => x.enabled);
+ const { classes: cardClasses, cx } = useCardStyles(true);
+ const { t } = useTranslation(['layout/common', 'common']);
+ const openAllApps = useOpenAllApps();
+ const apps = useMemo(
+ () => section.items.filter((x): x is AppItem => x.type === 'app'),
+ [section.items.length]
+ );
+
+ return (
+ !isEditMode && toggle(section.id)}
+ >
+
+
+
+ {section.name}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const useStyles = createStyles(() => ({
+ listItem: {
+ '& div': {
+ maxWidth: 'calc(100% - 23px)',
+ },
+ },
+}));
+
+const useOpenAllApps = () => {
+ const { classes } = useStyles();
+ const { t } = useTranslation(['layout/common', 'common']);
+
+ return useCallback((apps: AppItem[]) => {
+ return () => {
+ for (let i = 0; i < apps.length; i += 1) {
+ const app = apps[i];
+ const popUp = window.open(app.externalUrl ?? app.internalUrl, app.id);
+
+ if (popUp !== null) continue;
+
+ modals.openConfirmModal({
+ title: {t('modals.blockedPopups.title')},
+ children: (
+
+ {t('modals.blockedPopups.text')}
+
+
+ {t('modals.blockedPopups.list.browserPermission')}
+
+
+ {t('modals.blockedPopups.list.adBlockers')}
+
+
+ {t('modals.blockedPopups.list.otherBrowser')}
+
+
+
+ ),
+ labels: {
+ confirm: t('common:close'),
+ cancel: '',
+ },
+ cancelProps: {
+ display: 'none',
+ },
+ closeOnClickOutside: false,
+ });
+ break;
+ }
+ };
+ }, []);
+};
diff --git a/src/components/Board/Sections/EmptySection.tsx b/src/components/Board/Sections/EmptySection.tsx
index d9b20ce4b..4eb96a363 100644
--- a/src/components/Board/Sections/EmptySection.tsx
+++ b/src/components/Board/Sections/EmptySection.tsx
@@ -11,7 +11,7 @@ interface EmptySectionWrapperProps {
const defaultClasses = 'grid-stack grid-stack-empty min-row';
-export const EmptySectionWrapper = ({ section }: EmptySectionWrapperProps) => {
+export const BoardEmptySection = ({ section }: EmptySectionWrapperProps) => {
const { refs } = useGridstack({ section });
const isEditMode = useEditModeStore((x) => x.enabled);
diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx
index 821e49bb1..273959309 100644
--- a/src/components/Dashboard/Dashboard.tsx
+++ b/src/components/Dashboard/Dashboard.tsx
@@ -1,10 +1,7 @@
import { MobileRibbons } from './Mobile/Ribbon/MobileRibbon';
import { BoardView } from './Views/DashboardView';
-import { useEditModeStore } from './Views/useEditModeStore';
export const Board = () => {
- const isEditMode = useEditModeStore((x) => x.enabled);
-
return (
<>
{/* The following elemens are splitted because gridstack doesn't reinitialize them when using same item. */}
diff --git a/src/components/Dashboard/Views/DashboardView.tsx b/src/components/Dashboard/Views/DashboardView.tsx
index 304a72596..a7aff51e3 100644
--- a/src/components/Dashboard/Views/DashboardView.tsx
+++ b/src/components/Dashboard/Views/DashboardView.tsx
@@ -1,5 +1,6 @@
import { Box, Group, LoadingOverlay, Stack } from '@mantine/core';
-import { useEffect, useRef } from 'react';
+import { useLocalStorage } from '@mantine/hooks';
+import { useCallback, useEffect, useRef } from 'react';
import {
CategorySection,
EmptySection,
@@ -9,17 +10,32 @@ import {
import { useResize } from '~/hooks/use-resize';
import { useScreenLargerThan } from '~/hooks/useScreenLargerThan';
-import { EmptySectionWrapper } from '../../Board/Sections/EmptySection';
-import { DashboardCategory } from '../Wrappers/Category/Category';
+import { BoardCategorySection } from '../../Board/Sections/CategorySection';
+import { BoardEmptySection } from '../../Board/Sections/EmptySection';
import { DashboardSidebar } from '../Wrappers/Sidebar/Sidebar';
import { useGridstackStore } from '../Wrappers/gridstack/store';
export const BoardView = () => {
+ const boardName = useRequiredBoard().name;
const stackedSections = useStackedSections();
const sidebarsVisible = useSidebarVisibility();
const { isReady, mainAreaRef } = usePrepareGridstack();
const leftSidebarSection = useSidebarSection('left');
const rightSidebarSection = useSidebarSection('right');
+ const [toggledCategories, setToggledCategories] = useLocalStorage({
+ key: `${boardName}-category-section-toggled`,
+ // This is a bit of a hack to toggle the categories on the first load, return a string[] of the categories
+ defaultValue: stackedSections.filter((s) => s.type === 'category').map((s) => s.id),
+ });
+ const toggleCategory = useCallback((categoryId: string) => {
+ setToggledCategories((current) => {
+ console.log('toggle', current, categoryId);
+ if (current.includes(categoryId)) {
+ return current.filter((x) => x !== categoryId);
+ }
+ return [...current, categoryId];
+ });
+ }, []);
return (
@@ -41,9 +57,14 @@ export const BoardView = () => {
{stackedSections.map((item) =>
item.type === 'category' ? (
-
+
) : (
-
+
)
)}
diff --git a/src/components/Dashboard/Views/DetailView.tsx b/src/components/Dashboard/Views/DetailView.tsx
deleted file mode 100644
index 76cde8055..000000000
--- a/src/components/Dashboard/Views/DetailView.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import { BoardView } from './DashboardView';
-
-export const DashboardDetailView = () => ;
diff --git a/src/components/Dashboard/Views/EditView.tsx b/src/components/Dashboard/Views/EditView.tsx
deleted file mode 100644
index 11a4b180d..000000000
--- a/src/components/Dashboard/Views/EditView.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import { BoardView } from './DashboardView';
-
-export const DashboardEditView = () => ;
diff --git a/src/components/Dashboard/Wrappers/Category/Category.tsx b/src/components/Dashboard/Wrappers/Category/Category.tsx
deleted file mode 100644
index 7ed18d061..000000000
--- a/src/components/Dashboard/Wrappers/Category/Category.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import {
- Accordion,
- ActionIcon,
- Box,
- List,
- Menu,
- Stack,
- Text,
- Title,
- createStyles,
-} from '@mantine/core';
-import { modals } from '@mantine/modals';
-import { IconDotsVertical, IconShare3 } from '@tabler/icons-react';
-import { useTranslation } from 'next-i18next';
-import { CategorySection } from '~/components/Board/context';
-
-import { useCardStyles } from '../../../layout/Common/useCardStyles';
-import { useEditModeStore } from '../../Views/useEditModeStore';
-import { WrapperContent } from '../WrapperContent';
-import { useGridstack } from '../gridstack/use-gridstack';
-import { CategoryEditMenu } from './CategoryEditMenu';
-
-interface DashboardCategoryProps {
- section: CategorySection;
-}
-
-export const DashboardCategory = ({ section }: DashboardCategoryProps) => {
- const { refs } = useGridstack({ section });
- const isEditMode = useEditModeStore((x) => x.enabled);
- const { classes: cardClasses, cx } = useCardStyles(true);
- const { classes } = useStyles();
- const { t } = useTranslation(['layout/common', 'common']);
-
- //const categoryList = config?.categories.map((x) => x.name) ?? [];
- /*const [toggledCategories, setToggledCategories] = useLocalStorage({
- key: `${config?.configProperties.name}-app-shelf-toggled`,
- // This is a bit of a hack to toggle the categories on the first load, return a string[] of the categories
- defaultValue: categoryList,
- });*/
-
- /*const handleMenuClick = () => {
- for (let i = 0; i < apps.length; i += 1) {
- const app = apps[i];
- const popUp = window.open(app.url, app.id);
-
- if (popUp === null) {
- modals.openConfirmModal({
- title: {t('modals.blockedPopups.title')},
- children: (
-
- {t('modals.blockedPopups.text')}
-
-
- {t('modals.blockedPopups.list.browserPermission')}
-
-
- {t('modals.blockedPopups.list.adBlockers')}
-
-
- {t('modals.blockedPopups.list.otherBrowser')}
-
-
-
- ),
- labels: {
- confirm: t('common:close'),
- cancel: '',
- },
- cancelProps: {
- display: 'none',
- },
- closeOnClickOutside: false,
- });
- break;
- }
- }
- };
-
- // value={isEditMode ? categoryList : toggledCategories}
- /*
- onChange={(state) => {
- // Cancel if edit mode is on
- if (isEditMode) return;
- setToggledCategories([...state]);
- }}
- */
-
- return (
-
-
-
- }>
- {section.name}
-
- {!isEditMode && (
-
- )}
-
-
-
-
-
-
-
-
- );
-};
-
-const useStyles = createStyles(() => ({
- listItem: {
- '& div': {
- maxWidth: 'calc(100% - 23px)',
- },
- },
-}));
diff --git a/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx b/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx
deleted file mode 100644
index 05ce1db76..000000000
--- a/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { ActionIcon, Menu } from '@mantine/core';
-import {
- IconEdit,
- IconRowInsertBottom,
- IconRowInsertTop,
- IconSettings,
- IconTransitionBottom,
- IconTransitionTop,
- IconTrash,
-} from '@tabler/icons-react';
-import { useTranslation } from 'next-i18next';
-import { useConfigContext } from '~/config/provider';
-import { CategoryType } from '~/types/category';
-
-import { useCategoryActionHelper } from './useCategoryActions';
-
-interface CategoryEditMenuProps {
- category: CategoryType;
-}
-
-export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => {
- const { name: configName } = useConfigContext();
- const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } =
- useCategoryActionHelper(configName, category);
- const { t } = useTranslation(['layout/common', 'common']);
-
- return (
-
- );
-};
diff --git a/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx b/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx
index 4d87817ae..71d9e4335 100644
--- a/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx
+++ b/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx
@@ -1,16 +1,12 @@
import { v4 as uuidv4 } from 'uuid';
-import { useCategoryActions } from '~/components/Board/category-actions';
+import { useCategoryActions } from '~/components/Board/Sections/Category/category-actions';
import { useRequiredBoard } from '~/components/Board/context';
-import { useConfigStore } from '~/config/store';
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
-import { AppType } from '~/types/app';
import { CategoryType } from '~/types/category';
-import { WrapperType } from '~/types/wrapper';
-import { IWidget } from '~/widgets/widgets';
import { CategoryEditModalInnerProps } from './CategoryEditModal';
-export const useCategoryActionHelper = (configName: string | undefined, category: CategoryType) => {
+export const useCategoryActionHelper = (category: CategoryType) => {
const boardName = useRequiredBoard().name;
const { addCategory, moveCategory, removeCategory, renameCategory } = useCategoryActions({
boardName,