mirror of
https://github.com/ajnart/homarr.git
synced 2026-05-06 16:26:31 +02:00
🐛 Issue with moving items arround with change-position-modal
This commit is contained in:
@@ -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 (
|
||||
<GridstackProvider gridstackRef={refs.gridstack}>
|
||||
@@ -29,6 +27,7 @@ export const DashboardWrapper = ({ section }: DashboardWrapperProps) => {
|
||||
data-empty={section.id}
|
||||
ref={refs.wrapper}
|
||||
>
|
||||
{section.items.length === 0 && <span></span>}
|
||||
<WrapperContent items={section.items} refs={refs} />
|
||||
</div>
|
||||
</GridstackProvider>
|
||||
204
src/components/Board/category-actions.ts
Normal file
204
src/components/Board/category-actions.ts
Normal file
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -11,8 +11,6 @@ const GridstackContext = createContext<GridstackContextProps>({
|
||||
|
||||
export const useGridstackRef = () => {
|
||||
const ctx = useContext(GridstackContext);
|
||||
if (!ctx || !ctx.ref)
|
||||
throw new Error('useGridstackContext must be used within a GridstackProvider');
|
||||
return ctx.ref;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' ? (
|
||||
<DashboardCategory key={item.id} section={item} />
|
||||
) : (
|
||||
<DashboardWrapper key={item.id} section={item} />
|
||||
<EmptySectionWrapper key={item.id} section={item} />
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
@@ -55,17 +55,6 @@ export const BoardView = () => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
// <DashboardCategory key={item.id} category={item as unknown as CategoryType} />
|
||||
// <DashboardWrapper key={item.id} wrapper={item as WrapperType} />
|
||||
/*
|
||||
{sidebarsVisible.left ? (
|
||||
<DashboardSidebar location="left" isGridstackReady={isReady} />
|
||||
) : null}
|
||||
|
||||
{sidebarsVisible.right ? (
|
||||
<DashboardSidebar location="right" isGridstackReady={isReady} />
|
||||
) : null}
|
||||
*/
|
||||
|
||||
const usePrepareGridstack = () => {
|
||||
const mainAreaRef = useRef<HTMLDivElement>(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 (
|
||||
<Tooltip width={100} label={<Text align="center">{t('description')}</Text>}>
|
||||
{screenLargerThanMd ? (
|
||||
<Button
|
||||
variant={isEditMode ? 'filled' : 'default'}
|
||||
h={44}
|
||||
w={180}
|
||||
leftIcon={isEditMode ? <IconEditOff /> : <IconEdit />}
|
||||
onClick={() => toggleEditMode()}
|
||||
color={isEditMode ? 'red' : undefined}
|
||||
radius="md"
|
||||
>
|
||||
<Text>{isEditMode ? t('button.enabled') : t('button.disabled')}</Text>
|
||||
</Button>
|
||||
) : (
|
||||
<ActionIcon
|
||||
onClick={() => toggleEditMode()}
|
||||
variant="default"
|
||||
radius="md"
|
||||
size="xl"
|
||||
color="blue"
|
||||
>
|
||||
{isEditMode ? <IconEditOff /> : <IconEdit />}
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<Menu withinPortal withArrow>
|
||||
@@ -33,28 +33,24 @@ export const CategoryEditMenu = ({ category }: CategoryEditMenuProps) => {
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item icon={<IconEdit size={20} />} onClick={edit}>
|
||||
{t('common:edit')}
|
||||
{t('common:edit')}
|
||||
</Menu.Item>
|
||||
<Menu.Item icon={<IconTrash size={20} />} onClick={remove}>
|
||||
{t('common:remove')}
|
||||
</Menu.Item>
|
||||
<Menu.Label>
|
||||
{t('common:changePosition')}
|
||||
</Menu.Label>
|
||||
<Menu.Label>{t('common:changePosition')}</Menu.Label>
|
||||
<Menu.Item icon={<IconTransitionTop size={20} />} onClick={moveCategoryUp}>
|
||||
{t('menu.moveUp')}
|
||||
</Menu.Item>
|
||||
<Menu.Item icon={<IconTransitionBottom size={20} />} onClick={moveCategoryDown}>
|
||||
{t('menu.moveDown')}
|
||||
</Menu.Item>
|
||||
<Menu.Label>
|
||||
{t('menu.addCategory',{location: ''})}
|
||||
</Menu.Label>
|
||||
<Menu.Label>{t('menu.addCategory', { location: '' })}</Menu.Label>
|
||||
<Menu.Item icon={<IconRowInsertTop size={20} />} onClick={addCategoryAbove}>
|
||||
{t('menu.addCategory',{location: t('menu.addAbove')})}
|
||||
{t('menu.addCategory', { location: t('menu.addAbove') })}
|
||||
</Menu.Item>
|
||||
<Menu.Item icon={<IconRowInsertBottom size={20} />} onClick={addCategoryBelow}>
|
||||
{t('menu.addCategory',{location: t('menu.addBelow')})}
|
||||
{t('menu.addCategory', { location: t('menu.addBelow') })}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
||||
@@ -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<string, any>): 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<string, any> => ({
|
||||
...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,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user