From f8dfc3b23b3cc41569b7de08f10f315b9ae966b0 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sun, 8 Oct 2023 21:00:10 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Issue=20with=20moving=20items=20?= =?UTF-8?q?arround=20with=20change-position-modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sections/EmptySection.tsx} | 19 +- src/components/Board/category-actions.ts | 204 ++++++++++++++ src/components/Board/gridstack/context.tsx | 2 - .../Board/gridstack/useResizeGridItem.ts | 4 +- src/components/Board/item-actions.tsx | 12 +- .../Dashboard/Views/DashboardView.tsx | 15 +- .../Dashboard/Views/ViewToggleButton.tsx | 40 --- .../Wrappers/Category/CategoryEditMenu.tsx | 24 +- .../Wrappers/Category/useCategoryActions.tsx | 254 +++--------------- .../Wrappers/gridstack/init-gridstack.ts | 21 -- .../Wrappers/gridstack/use-gridstack.ts | 107 ++++---- src/server/api/routers/board.ts | 32 +-- 12 files changed, 334 insertions(+), 400 deletions(-) rename src/components/{Dashboard/Wrappers/Wrapper/Wrapper.tsx => Board/Sections/EmptySection.tsx} (53%) create mode 100644 src/components/Board/category-actions.ts delete mode 100644 src/components/Dashboard/Views/ViewToggleButton.tsx diff --git a/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx b/src/components/Board/Sections/EmptySection.tsx similarity index 53% rename from src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx rename to src/components/Board/Sections/EmptySection.tsx index 5649525c1..d9b20ce4b 100644 --- a/src/components/Dashboard/Wrappers/Wrapper/Wrapper.tsx +++ b/src/components/Board/Sections/EmptySection.tsx @@ -1,21 +1,19 @@ -import { Button } from '@mantine/core'; -import { GridItemHTMLElement } from 'fily-publish-gridstack'; -import { EmptySection, useRequiredBoard } from '~/components/Board/context'; +import { EmptySection } from '~/components/Board/context'; import { GridstackProvider } from '~/components/Board/gridstack/context'; -import { useItemActions } from '~/components/Board/item-actions'; -import { useEditModeStore } from '../../Views/useEditModeStore'; -import { WrapperContent } from '../WrapperContent'; -import { useGridstack } from '../gridstack/use-gridstack'; +import { useEditModeStore } from '../../Dashboard/Views/useEditModeStore'; +import { WrapperContent } from '../../Dashboard/Wrappers/WrapperContent'; +import { useGridstack } from '../../Dashboard/Wrappers/gridstack/use-gridstack'; -interface DashboardWrapperProps { +interface EmptySectionWrapperProps { section: EmptySection; } -export const DashboardWrapper = ({ section }: DashboardWrapperProps) => { +const defaultClasses = 'grid-stack grid-stack-empty min-row'; + +export const EmptySectionWrapper = ({ section }: EmptySectionWrapperProps) => { const { refs } = useGridstack({ section }); const isEditMode = useEditModeStore((x) => x.enabled); - const defaultClasses = 'grid-stack grid-stack-empty min-row'; return ( @@ -29,6 +27,7 @@ export const DashboardWrapper = ({ section }: DashboardWrapperProps) => { data-empty={section.id} ref={refs.wrapper} > + {section.items.length === 0 && } diff --git a/src/components/Board/category-actions.ts b/src/components/Board/category-actions.ts new file mode 100644 index 000000000..7e69ecf64 --- /dev/null +++ b/src/components/Board/category-actions.ts @@ -0,0 +1,204 @@ +import Consola from 'consola'; +import { useCallback } from 'react'; +import { v4 } from 'uuid'; +import { api } from '~/utils/api'; + +import { type CategorySection, type EmptySection } from './context'; + +type AddCategory = { + name: string; + position: number; +}; + +type RenameCategory = { + id: string; + name: string; +}; + +type MoveCategory = { + id: string; + direction: 'up' | 'down'; +}; + +type RemoveCategory = { + id: string; +}; + +export const useCategoryActions = ({ boardName }: { boardName: string }) => { + const utils = api.useContext(); + + const addCategory = useCallback( + ({ name, position }: AddCategory) => { + if (position <= 0) { + Consola.error('Cannot add category before first section'); + return; + } + utils.boards.byName.setData({ boardName }, (prev) => { + if (!prev) return prev; + return { + ...prev, + sections: [ + // Ignore sidebar and hidden sections + ...prev.sections.filter( + (section) => section.type === 'sidebar' || section.type === 'hidden' + ), + // Place sections before the new category + ...prev.sections.filter( + (section) => + (section.type === 'category' || section.type === 'empty') && + section.position < position + ), + { + id: v4(), + name, + type: 'category', + position, + items: [], + }, + { + id: v4(), + type: 'empty', + position: position + 1, + items: [], + }, + // Place sections after the new category + ...prev.sections + .filter( + (section): section is CategorySection | EmptySection => + (section.type === 'category' || section.type === 'empty') && + section.position >= position + ) + .map((section) => ({ + ...section, + position: section.position + 2, + })), + ], + }; + }); + }, + [boardName, utils] + ); + + const renameCategory = useCallback( + ({ id: categoryId, name }: RenameCategory) => { + utils.boards.byName.setData({ boardName }, (prev) => { + if (!prev) return prev; + return { + ...prev, + sections: prev.sections.map((section) => { + if (section.type !== 'category') return section; + if (section.id !== categoryId) return section; + return { + ...section, + name, + }; + }), + }; + }); + }, + [boardName, utils] + ); + + const moveCategory = useCallback( + ({ id: categoryId, direction }: MoveCategory) => { + utils.boards.byName.setData({ boardName }, (prev) => { + if (!prev) return prev; + + const currentCategory = prev.sections.find( + (section): section is CategorySection => + section.type === 'category' && section.id === categoryId + ); + if (!currentCategory) return prev; + if (currentCategory?.position === 1 && direction === 'up') return prev; + if (currentCategory?.position === prev.sections.length - 2 && direction === 'down') + return prev; + + return { + ...prev, + sections: prev.sections.map((section) => { + if (section.type !== 'category' && section.type !== 'empty') return section; + const offset = direction === 'up' ? -2 : 2; + // Move category and empty section + if ( + section.position === currentCategory.position || + section.position - 1 === currentCategory.position + ) { + return { + ...section, + position: section.position + offset, + }; + } + + // Move all sections behind + if (section.position >= currentCategory.position + offset) { + return { + ...section, + position: section.position + 2, + }; + } + + if ( + direction === 'up' && + (section.position === currentCategory.position + 2 || + section.position === currentCategory.position + 3) + ) { + return { + ...section, + position: section.position - 2, + }; + } + + return section; + }), + }; + }); + }, + [boardName, utils] + ); + + const removeCategory = useCallback( + ({ id: categoryId }: RemoveCategory) => { + utils.boards.byName.setData({ boardName }, (prev) => { + if (!prev) return prev; + + const currentCategory = prev.sections.find( + (section): section is CategorySection => + section.type === 'category' && section.id === categoryId + ); + if (!currentCategory) return prev; + + return { + ...prev, + sections: [ + ...prev.sections.filter( + (section) => section.type === 'sidebar' || section.type === 'hidden' + ), + ...prev.sections.filter( + (section) => + (section.type === 'category' || section.type === 'empty') && + section.position < currentCategory.position + ), + ...prev.sections + .filter( + (section): section is CategorySection | EmptySection => + (section.type === 'category' || section.type === 'empty') && + section.position >= currentCategory.position + 2 + ) + .map((section) => ({ + ...section, + position: section.position - 2, + })), + ], + }; + }); + }, + [boardName, utils] + ); + + return { + addCategory, + renameCategory, + moveCategory, + removeCategory, + }; +}; diff --git a/src/components/Board/gridstack/context.tsx b/src/components/Board/gridstack/context.tsx index e643de021..73664ea19 100644 --- a/src/components/Board/gridstack/context.tsx +++ b/src/components/Board/gridstack/context.tsx @@ -11,8 +11,6 @@ const GridstackContext = createContext({ export const useGridstackRef = () => { const ctx = useContext(GridstackContext); - if (!ctx || !ctx.ref) - throw new Error('useGridstackContext must be used within a GridstackProvider'); return ctx.ref; }; diff --git a/src/components/Board/gridstack/useResizeGridItem.ts b/src/components/Board/gridstack/useResizeGridItem.ts index 3e5504337..5e86d5723 100644 --- a/src/components/Board/gridstack/useResizeGridItem.ts +++ b/src/components/Board/gridstack/useResizeGridItem.ts @@ -13,12 +13,10 @@ export const useResizeGridItem = () => { const gridstackRef = useGridstackRef(); return ({ height, width, ...options }: ResizeGridItemProps) => { - gridstackRef.current?.batchUpdate(); - gridstackRef.current?.update(itemRef.current!, { + gridstackRef?.current?.update(itemRef.current!, { ...options, h: height, w: width, }); - gridstackRef.current?.batchUpdate(false); }; }; diff --git a/src/components/Board/item-actions.tsx b/src/components/Board/item-actions.tsx index 512df7602..026078f16 100644 --- a/src/components/Board/item-actions.tsx +++ b/src/components/Board/item-actions.tsx @@ -27,6 +27,7 @@ export const useItemActions = ({ boardName }: { boardName: string }) => { const utils = api.useContext(); const moveAndResizeItem = useCallback( ({ itemId, ...positionProps }: MoveAndResizeItem) => { + console.log(itemId, positionProps); utils.boards.byName.setData({ boardName }, (prev) => { if (!prev) return prev; return { @@ -54,6 +55,7 @@ export const useItemActions = ({ boardName }: { boardName: string }) => { const moveItemToSection = useCallback( ({ itemId, sectionId, ...positionProps }: MoveItemToSection) => { + console.log('moveItemToSection', itemId, sectionId, positionProps); utils.boards.byName.setData({ boardName }, (prev) => { if (!prev) return prev; @@ -62,10 +64,16 @@ export const useItemActions = ({ boardName }: { boardName: string }) => { ); // If item is in the same section (on initial loading) don't do anything - if (!currentSection || currentSection.id === sectionId) return prev; + if (!currentSection || currentSection.id === sectionId) { + console.log('Current section is undefined or same as target section'); + return prev; + } let currentItem = currentSection?.items.find((item) => item.id === itemId); - if (!currentItem) return prev; + if (!currentItem) { + console.log('Current item is undefined'); + return prev; + } return { ...prev, diff --git a/src/components/Dashboard/Views/DashboardView.tsx b/src/components/Dashboard/Views/DashboardView.tsx index 21091f2f5..304a72596 100644 --- a/src/components/Dashboard/Views/DashboardView.tsx +++ b/src/components/Dashboard/Views/DashboardView.tsx @@ -9,9 +9,9 @@ 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 { DashboardSidebar } from '../Wrappers/Sidebar/Sidebar'; -import { DashboardWrapper } from '../Wrappers/Wrapper/Wrapper'; import { useGridstackStore } from '../Wrappers/gridstack/store'; export const BoardView = () => { @@ -43,7 +43,7 @@ export const BoardView = () => { item.type === 'category' ? ( ) : ( - + ) )} @@ -55,17 +55,6 @@ export const BoardView = () => { ); }; -// -// -/* -{sidebarsVisible.left ? ( - - ) : null} - -{sidebarsVisible.right ? ( - - ) : null} -*/ const usePrepareGridstack = () => { const mainAreaRef = useRef(null); diff --git a/src/components/Dashboard/Views/ViewToggleButton.tsx b/src/components/Dashboard/Views/ViewToggleButton.tsx deleted file mode 100644 index 5502edcf9..000000000 --- a/src/components/Dashboard/Views/ViewToggleButton.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ActionIcon, Button, Text, Tooltip } from '@mantine/core'; -import { IconEdit, IconEditOff } from '@tabler/icons-react'; -import { useTranslation } from 'next-i18next'; - -import { useScreenLargerThan } from '~/hooks/useScreenLargerThan'; -import { useEditModeStore } from './useEditModeStore'; - -export const ViewToggleButton = () => { - const screenLargerThanMd = useScreenLargerThan('md'); - const { enabled: isEditMode, toggleEditMode } = useEditModeStore(); - const { t } = useTranslation('layout/header/actions/toggle-edit-mode'); - - return ( - {t('description')}}> - {screenLargerThanMd ? ( - - ) : ( - toggleEditMode()} - variant="default" - radius="md" - size="xl" - color="blue" - > - {isEditMode ? : } - - )} - - ); -}; diff --git a/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx b/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx index 514f36e67..05ce1db76 100644 --- a/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx +++ b/src/components/Dashboard/Wrappers/Category/CategoryEditMenu.tsx @@ -8,11 +8,11 @@ import { IconTransitionTop, IconTrash, } from '@tabler/icons-react'; - +import { useTranslation } from 'next-i18next'; import { useConfigContext } from '~/config/provider'; import { CategoryType } from '~/types/category'; -import { useCategoryActions } from './useCategoryActions'; -import { useTranslation } from 'next-i18next'; + +import { useCategoryActionHelper } from './useCategoryActions'; interface CategoryEditMenuProps { category: CategoryType; @@ -21,8 +21,8 @@ interface CategoryEditMenuProps { export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => { const { name: configName } = useConfigContext(); const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } = - useCategoryActions(configName, category); - const { t } = useTranslation(['layout/common','common']); + useCategoryActionHelper(configName, category); + const { t } = useTranslation(['layout/common', 'common']); return ( @@ -33,28 +33,24 @@ export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => { } onClick={edit}> - {t('common:edit')} + {t('common:edit')} } onClick={remove}> {t('common:remove')} - - {t('common:changePosition')} - + {t('common:changePosition')} } onClick={moveCategoryUp}> {t('menu.moveUp')} } onClick={moveCategoryDown}> {t('menu.moveDown')} - - {t('menu.addCategory',{location: ''})} - + {t('menu.addCategory', { location: '' })} } onClick={addCategoryAbove}> - {t('menu.addCategory',{location: t('menu.addAbove')})} + {t('menu.addCategory', { location: t('menu.addAbove') })} } onClick={addCategoryBelow}> - {t('menu.addCategory',{location: t('menu.addBelow')})} + {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 e954a2c9a..4d87817ae 100644 --- a/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx +++ b/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx @@ -1,15 +1,20 @@ import { v4 as uuidv4 } from 'uuid'; - +import { useCategoryActions } from '~/components/Board/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 useCategoryActions = (configName: string | undefined, category: CategoryType) => { - const updateConfig = useConfigStore((x) => x.updateConfig); +export const useCategoryActionHelper = (configName: string | undefined, category: CategoryType) => { + const boardName = useRequiredBoard().name; + const { addCategory, moveCategory, removeCategory, renameCategory } = useCategoryActions({ + boardName, + }); // creates a new category above the current const addCategoryAbove = () => { @@ -24,43 +29,10 @@ export const useCategoryActions = (configName: string | undefined, category: Cat position: abovePosition + 1, }, onSuccess: async (category) => { - if (!configName) return; - - const newWrapper: WrapperType = { - id: uuidv4(), - position: abovePosition + 2, - }; - - // Adding category and wrapper and moving other items down - updateConfig( - configName, - (previous) => { - const aboveWrappers = previous.wrappers.filter((x) => x.position <= abovePosition); - const aboveCategories = previous.categories.filter( - (x) => x.position <= abovePosition - ); - - const belowWrappers = previous.wrappers.filter((x) => x.position > abovePosition); - const belowCategories = previous.categories.filter((x) => x.position > abovePosition); - - return { - ...previous, - categories: [ - ...aboveCategories, - category, - // Move categories below down - ...belowCategories.map((x) => ({ ...x, position: x.position + 1 })), - ], - wrappers: [ - ...aboveWrappers, - newWrapper, - // Move wrappers below down - ...belowWrappers.map((x) => ({ ...x, position: x.position + 1 })), - ], - }; - }, - true - ); + addCategory({ + name: category.name, + position: category.position, + }); }, }, }); @@ -79,194 +51,35 @@ export const useCategoryActions = (configName: string | undefined, category: Cat position: belowPosition + 1, }, onSuccess: async (category) => { - if (!configName) return; - - const newWrapper: WrapperType = { - id: uuidv4(), - position: belowPosition, - }; - - // Adding category and wrapper and moving other items down - updateConfig( - configName, - (previous) => { - const aboveWrappers = previous.wrappers.filter((x) => x.position < belowPosition); - const aboveCategories = previous.categories.filter((x) => x.position < belowPosition); - - const belowWrappers = previous.wrappers.filter((x) => x.position >= belowPosition); - const belowCategories = previous.categories.filter( - (x) => x.position >= belowPosition - ); - - return { - ...previous, - categories: [ - ...aboveCategories, - category, - // Move categories below down - ...belowCategories.map((x) => ({ ...x, position: x.position + 2 })), - ], - wrappers: [ - ...aboveWrappers, - newWrapper, - // Move wrappers below down - ...belowWrappers.map((x) => ({ ...x, position: x.position + 2 })), - ], - }; - }, - true - ); + addCategory({ + name: category.name, + position: category.position, + }); }, }, }); }; const moveCategoryUp = () => { - if (!configName) return; - - updateConfig( - configName, - (previous) => { - const currentItem = previous.categories.find((x) => x.id === category.id); - if (!currentItem) return previous; - - const upperItem = previous.categories.find((x) => x.position === currentItem.position - 1); - - if (!upperItem) return previous; - - currentItem.position -= 1; - upperItem.position += 1; - - return { - ...previous, - categories: [ - ...previous.categories.filter((c) => ![currentItem.id, upperItem.id].includes(c.id)), - { ...upperItem }, - { ...currentItem }, - ], - }; - }, - true - ); + moveCategory({ + id: category.id, + direction: 'up', + }); }; const moveCategoryDown = () => { - if (!configName) return; - - updateConfig( - configName, - (previous) => { - const currentItem = previous.categories.find((x) => x.id === category.id); - if (!currentItem) return previous; - - const belowItem = previous.categories.find((x) => x.position === currentItem.position + 1); - - if (!belowItem) return previous; - - currentItem.position += 1; - belowItem.position -= 1; - - return { - ...previous, - categories: [ - ...previous.categories.filter((c) => ![currentItem.id, belowItem.id].includes(c.id)), - { ...currentItem }, - { ...belowItem }, - ], - }; - }, - true - ); + moveCategory({ + id: category.id, + direction: 'down', + }); }; // Removes the current category const remove = () => { - if (!configName) return; - updateConfig( - configName, - (previous) => { - const currentItem = previous.categories.find((x) => x.id === category.id); - if (!currentItem) return previous; - // Find the main wrapper - const mainWrapper = previous.wrappers.find((x) => x.position === 0); - const mainWrapperId = mainWrapper?.id ?? 'default'; - - const isAppAffectedFilter = (app: AppType): boolean => { - if (!app.area) { - return false; - } - - if (app.area.type !== 'category') { - return false; - } - - if (app.area.properties.id === mainWrapperId) { - return false; - } - - return app.area.properties.id === currentItem.id; - }; - - const isWidgetAffectedFilter = (widget: IWidget): boolean => { - if (!widget.area) { - return false; - } - - if (widget.area.type !== 'category') { - return false; - } - - if (widget.area.properties.id === mainWrapperId) { - return false; - } - - return widget.area.properties.id === currentItem.id; - }; - - return { - ...previous, - apps: [ - ...previous.apps.filter((x) => !isAppAffectedFilter(x)), - ...previous.apps - .filter((x) => isAppAffectedFilter(x)) - .map( - (app): AppType => ({ - ...app, - area: { - ...app.area, - type: 'wrapper', - properties: { - ...app.area.properties, - id: mainWrapperId, - }, - }, - }) - ), - ], - widgets: [ - ...previous.widgets.filter((widget) => !isWidgetAffectedFilter(widget)), - ...previous.widgets - .filter((widget) => isWidgetAffectedFilter(widget)) - .map( - (widget): IWidget => ({ - ...widget, - area: { - ...widget.area, - type: 'wrapper', - properties: { - ...widget.area.properties, - id: mainWrapperId, - }, - }, - }) - ), - ], - categories: previous.categories.filter((x) => x.id !== category.id), - wrappers: previous.wrappers.filter((x) => x.position !== currentItem.position), - }; - }, - true - ); + // TODO: contained apps are currently just deleted + removeCategory({ + id: category.id, + }); }; const edit = async () => { @@ -276,14 +89,9 @@ export const useCategoryActions = (configName: string | undefined, category: Cat innerProps: { category, onSuccess: async (category) => { - if (!configName) return; - await updateConfig(configName, (prev) => { - const currentCategory = prev.categories.find((c) => c.id === category.id); - if (!currentCategory) return prev; - return { - ...prev, - categories: [...prev.categories.filter((c) => c.id !== category.id), { ...category }], - }; + renameCategory({ + id: category.id, + name: category.name, }); }, }, diff --git a/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts index 3745812a1..8dba1646b 100644 --- a/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts +++ b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts @@ -11,10 +11,6 @@ type InitializeGridstackProps = { }; isEditMode: boolean; sectionColumnCount: number; - events: { - onChange: (changedNode: GridStackNode) => void; - onAdd: (addedNode: GridStackNode) => void; - }; }; export const initializeGridstack = ({ @@ -22,7 +18,6 @@ export const initializeGridstack = ({ refs, isEditMode, sectionColumnCount, - events, }: InitializeGridstackProps) => { if (!refs.wrapper.current) return; // calculates the currently available count of columns @@ -52,22 +47,6 @@ export const initializeGridstack = ({ // Must be used to update the column count after the initialization grid.column(columnCount, 'none'); - // Add listener for moving items around in a wrapper - grid.on('change', (_, el) => { - const nodes = el as GridStackNode[]; - const firstNode = nodes.at(0); - if (!firstNode) return; - events.onChange(firstNode); - }); - - // Add listener for moving items in config from one wrapper to another - grid.on('added', (_, el) => { - const nodes = el as GridStackNode[]; - const firstNode = nodes.at(0); - if (!firstNode) return; - events.onAdd(firstNode); - }); - grid.batchUpdate(); grid.removeAll(false); section.items.forEach((item) => { diff --git a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts index 43bd7885c..ed1ced9be 100644 --- a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts +++ b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts @@ -1,19 +1,13 @@ +import { type GridItemHTMLElement, GridStack, type GridStackNode } from 'fily-publish-gridstack'; import { - GridItemHTMLElement, - GridStack, - GridStackElement, - GridStackNode, -} from 'fily-publish-gridstack'; -import { - MutableRefObject, - RefObject, + type MutableRefObject, + type RefObject, createRef, - useCallback, useEffect, useMemo, useRef, } from 'react'; -import { Section, useRequiredBoard } from '~/components/Board/context'; +import { type Section, useRequiredBoard } from '~/components/Board/context'; import { useItemActions } from '~/components/Board/item-actions'; import { useEditModeStore } from '../../Views/useEditModeStore'; @@ -59,7 +53,7 @@ export const useGridstack = ({ section }: UseGridstackProps): UseGristackReturnT // define items in itemRefs for easy access and reference to items if (Object.keys(itemRefs.current).length !== items.length) { items.forEach(({ id }: { id: keyof typeof itemRefs.current }) => { - itemRefs.current[id] = itemRefs.current[id] || createRef(); + itemRefs.current[id] = itemRefs.current[id] ?? createRef(); }); } @@ -80,53 +74,60 @@ export const useGridstack = ({ section }: UseGridstackProps): UseGristackReturnT gridRef.current?.setStatic(!isEditMode); }, [isEditMode]); - const onChange = useCallback( - (changedNode: GridStackNode) => { - if (!isEditMode) return; + const onChange = (changedNode: GridStackNode) => { + const itemType = changedNode.el?.getAttribute('data-type'); + const itemId = changedNode.el?.getAttribute('data-id'); + if (!itemType || !itemId) return; - const itemType = changedNode.el?.getAttribute('data-type'); - const itemId = changedNode.el?.getAttribute('data-id'); - if (!itemType || !itemId) return; + // Updates the react-query state + moveAndResizeItem({ + itemId, + x: changedNode.x!, + y: changedNode.y!, + width: changedNode.w!, + height: changedNode.h!, + }); + }; + const onAdd = (addedNode: GridStackNode) => { + const itemType = addedNode.el?.getAttribute('data-type'); + const itemId = addedNode.el?.getAttribute('data-id'); + if (!itemType || !itemId) return; - // Updates the config and defines the new position of the item - moveAndResizeItem({ - itemId, - x: changedNode.x!, - y: changedNode.y!, - width: changedNode.w!, - height: changedNode.h!, - }); - }, - [isEditMode] - ); + // Updates the react-query state + moveItemToSection({ + itemId, + sectionId: section.id, + x: addedNode.x!, + y: addedNode.y!, + width: addedNode.w!, + height: addedNode.h!, + }); + }; - const onAdd = useCallback( - (addedNode: GridStackNode) => { - if (!isEditMode) return; + useEffect(() => { + if (!isEditMode) return; + // Add listener for moving items around in a wrapper + gridRef.current?.on('change', (_, nodes) => { + (nodes as GridStackNode[]).forEach(onChange); + }); - const itemType = addedNode.el?.getAttribute('data-type'); - const itemId = addedNode.el?.getAttribute('data-id'); - if (!itemType || !itemId) return; + // Add listener for moving items in config from one wrapper to another + gridRef.current?.on('added', (_, el) => { + const nodes = el as GridStackNode[]; + const firstNode = nodes.at(0); + if (!firstNode) return; + onAdd(firstNode); + }); - moveItemToSection({ - itemId, - sectionId: section.id, - x: addedNode.x!, - y: addedNode.y!, - width: addedNode.w!, - height: addedNode.h!, - }); - }, - [isEditMode] - ); + return () => { + gridRef.current?.off('change'); + gridRef.current?.off('added'); + }; + }, [isEditMode]); // initialize the gridstack useEffect(() => { initializeGridstack({ - events: { - onChange, - onAdd, - }, isEditMode, section, refs: { @@ -136,13 +137,7 @@ export const useGridstack = ({ section }: UseGridstackProps): UseGristackReturnT }, sectionColumnCount, }); - - // Remove event listeners on unmount - return () => { - gridRef.current?.off('change'); - gridRef.current?.off('added'); - }; - }, [items, wrapperRef.current, sectionColumnCount]); + }, [items.length, wrapperRef.current, sectionColumnCount]); return { refs: { diff --git a/src/server/api/routers/board.ts b/src/server/api/routers/board.ts index c4ad1aab7..1486c2dfa 100644 --- a/src/server/api/routers/board.ts +++ b/src/server/api/routers/board.ts @@ -245,8 +245,8 @@ export const boardRouter = createTRPCRouter({ iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/github.png', width: 2, height: 1, - x: 3, - y: 1, + x: 2, + y: 0, }); await addApp({ @@ -257,8 +257,8 @@ export const boardRouter = createTRPCRouter({ iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/discord.png', width: 2, height: 1, - x: 5, - y: 1, + x: 4, + y: 0, }); await addApp({ @@ -269,8 +269,8 @@ export const boardRouter = createTRPCRouter({ iconUrl: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/ko-fi.png', width: 2, height: 1, - x: 7, - y: 1, + x: 6, + y: 0, }); await addApp({ @@ -281,8 +281,8 @@ export const boardRouter = createTRPCRouter({ iconUrl: '/imgs/logo/logo.png', width: 2, height: 2, - x: 7, - y: 2, + x: 6, + y: 1, }); await addWidget({ @@ -291,8 +291,8 @@ export const boardRouter = createTRPCRouter({ type: 'weather', width: 2, height: 1, - x: 1, - y: 1, + x: 0, + y: 0, }); await addWidget({ @@ -301,8 +301,8 @@ export const boardRouter = createTRPCRouter({ type: 'date', width: 2, height: 1, - x: 9, - y: 1, + x: 8, + y: 0, }); await addWidget({ @@ -311,8 +311,8 @@ export const boardRouter = createTRPCRouter({ type: 'date', width: 2, height: 1, - x: 9, - y: 2, + x: 8, + y: 1, }); await addWidget({ @@ -321,8 +321,8 @@ export const boardRouter = createTRPCRouter({ type: 'notebook', width: 6, height: 3, - x: 1, - y: 2, + x: 0, + y: 1, }); }), });