From afe4ae54fae5d7f59a91039c2b63790ac086ab71 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sun, 8 Oct 2023 22:19:53 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Migrate=20category=20toggl?= =?UTF-8?q?e=20and=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/boards/common.json | 7 + public/locales/en/layout/common.json | 5 +- .../Board/Sections/Category/CategoryMenu.tsx | 177 ++++++++++++++++++ .../Category}/category-actions.ts | 2 +- .../Board/Sections/CategorySection.tsx | 115 ++++++++++++ .../Board/Sections/EmptySection.tsx | 2 +- src/components/Dashboard/Dashboard.tsx | 3 - .../Dashboard/Views/DashboardView.tsx | 31 ++- src/components/Dashboard/Views/DetailView.tsx | 3 - src/components/Dashboard/Views/EditView.tsx | 3 - .../Dashboard/Wrappers/Category/Category.tsx | 142 -------------- .../Wrappers/Category/CategoryEditMenu.tsx | 58 ------ .../Wrappers/Category/useCategoryActions.tsx | 8 +- 13 files changed, 330 insertions(+), 226 deletions(-) create mode 100644 src/components/Board/Sections/Category/CategoryMenu.tsx rename src/components/Board/{ => Sections/Category}/category-actions.ts (98%) create mode 100644 src/components/Board/Sections/CategorySection.tsx delete mode 100644 src/components/Dashboard/Views/DetailView.tsx delete mode 100644 src/components/Dashboard/Views/EditView.tsx delete mode 100644 src/components/Dashboard/Wrappers/Category/Category.tsx delete mode 100644 src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx 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 ( + + + + + + + + {actions.map((action) => ( + <> + {action.group && {t(action.group)}} + } + onClick={action.onClick} + color={action.color} + > + {t(action.label)} + + + ))} + + + ); +}; + +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 && ( - - - - - - - - } - > - {t('actions.category.openAllInNewTab')} - - - - )} - - -
- -
-
-
-
- ); -}; - -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 ( - - - - - - - - } onClick={edit}> - {t('common:edit')} - - } onClick={remove}> - {t('common:remove')} - - {t('common:changePosition')} - } onClick={moveCategoryUp}> - {t('menu.moveUp')} - - } onClick={moveCategoryDown}> - {t('menu.moveDown')} - - {t('menu.addCategory', { location: '' })} - } onClick={addCategoryAbove}> - {t('menu.addCategory', { location: t('menu.addAbove') })} - - } onClick={addCategoryBelow}> - {t('menu.addCategory', { location: t('menu.addBelow') })} - - - - ); -}; 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,