From a5d31dd3ec5aa57c2d0189db8130ea5495ccd8ce Mon Sep 17 00:00:00 2001 From: Meierschlumpf Date: Mon, 19 Dec 2022 21:27:44 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20Change=20Position=20not=20?= =?UTF-8?q?working=20with=20gridstack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangePosition/ChangeAppPositionModal.tsx | 18 +- .../ChangeWidgetPositionModal.tsx | 39 ++-- .../Modals/EditAppModal/EditAppModal.tsx | 12 +- .../WidgetsTab/WidgetElementType.tsx | 58 +++--- .../Tiles/Widgets/WidgetsEditModal.tsx | 20 ++- .../Tiles/Widgets/WidgetsRemoveModal.tsx | 12 +- .../Wrappers/Category/useCategoryActions.tsx | 168 ++++++++++-------- .../Dashboard/Wrappers/WrapperContent.tsx | 1 + .../Wrappers/gridstack/init-gridstack.ts | 1 + .../Wrappers/gridstack/use-gridstack.ts | 8 +- .../Customization/Layout/LayoutSelector.tsx | 30 ++-- src/config/init.ts | 4 +- src/config/provider.tsx | 9 +- src/config/store.ts | 35 ++-- 14 files changed, 242 insertions(+), 173 deletions(-) diff --git a/src/components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal.tsx b/src/components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal.tsx index e98008e40..820223405 100644 --- a/src/components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal.tsx +++ b/src/components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal.tsx @@ -22,13 +22,17 @@ export const ChangeAppPositionModal = ({ return; } - updateConfig(configName, (previousConfig) => ({ - ...previousConfig, - apps: [ - ...previousConfig.apps.filter((x) => x.id !== innerProps.app.id), - { ...innerProps.app, shape: { location: { x, y }, size: { width, height } } }, - ], - })); + updateConfig( + configName, + (previousConfig) => ({ + ...previousConfig, + apps: [ + ...previousConfig.apps.filter((x) => x.id !== innerProps.app.id), + { ...innerProps.app, shape: { location: { x, y }, size: { width, height } } }, + ], + }), + true + ); context.closeModal(id); }; diff --git a/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx b/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx index 05f3142a3..2e5325634 100644 --- a/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx +++ b/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx @@ -19,25 +19,28 @@ export const ChangeWidgetPositionModal = ({ return; } - updateConfig(configName, (prev) => { - let currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId); - currentWidget!.shape = { - location: { - x, - y, - }, - size: { - height, - width, - }, - }; - - return { - ...prev, - widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!], - }; - }); + updateConfig( + configName, + (prev) => { + let currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId); + currentWidget!.shape = { + location: { + x, + y, + }, + size: { + height, + width, + }, + }; + return { + ...prev, + widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!], + }; + }, + true + ); context.closeModal(id); }; diff --git a/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx index cadb1c028..1edcc2189 100644 --- a/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx +++ b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx @@ -83,10 +83,14 @@ export const EditAppModal = ({ return; } - updateConfig(configName, (previousConfig) => ({ - ...previousConfig, - apps: [...previousConfig.apps.filter((x) => x.id !== form.values.id), form.values], - })); + updateConfig( + configName, + (previousConfig) => ({ + ...previousConfig, + apps: [...previousConfig.apps.filter((x) => x.id !== form.values.id), form.values], + }), + true + ); // also close the parent modal context.closeAll(); diff --git a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx index 78aa40384..209f5d1d8 100644 --- a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx +++ b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx @@ -26,35 +26,39 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement }; const handleAddition = async () => { - updateConfig(configName, (prev) => ({ - ...prev, - widgets: [ - ...prev.widgets.filter((w) => w.id !== widget.id), - { - id: widget.id, - properties: Object.entries(widget.options).reduce((prev, [k, v]) => { - prev[k] = v.defaultValue; - return prev; - }, {} as IWidget['properties']), - area: { - type: 'wrapper', - properties: { - id: getLowestWrapper()?.id ?? '', + updateConfig( + configName, + (prev) => ({ + ...prev, + widgets: [ + ...prev.widgets.filter((w) => w.id !== widget.id), + { + id: widget.id, + properties: Object.entries(widget.options).reduce((prev, [k, v]) => { + prev[k] = v.defaultValue; + return prev; + }, {} as IWidget['properties']), + area: { + type: 'wrapper', + properties: { + id: getLowestWrapper()?.id ?? '', + }, + }, + shape: { + location: { + x: 0, + y: 0, + }, + size: { + width: widget.gridstack.minWidth, + height: widget.gridstack.minHeight, + }, }, }, - shape: { - location: { - x: 0, - y: 0, - }, - size: { - width: widget.gridstack.minWidth, - height: widget.gridstack.minHeight, - }, - }, - }, - ], - })); + ], + }), + true + ); // TODO: safe to file system closeModal('selectElement'); }; diff --git a/src/components/Dashboard/Tiles/Widgets/WidgetsEditModal.tsx b/src/components/Dashboard/Tiles/Widgets/WidgetsEditModal.tsx index 62328e31b..77d958060 100644 --- a/src/components/Dashboard/Tiles/Widgets/WidgetsEditModal.tsx +++ b/src/components/Dashboard/Tiles/Widgets/WidgetsEditModal.tsx @@ -45,15 +45,19 @@ export const WidgetsEditModal = ({ }; const handleSave = () => { - updateConfig(configName, (prev) => { - let currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId); - currentWidget!.properties = moduleProperties; + updateConfig( + configName, + (prev) => { + let currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId); + currentWidget!.properties = moduleProperties; - return { - ...prev, - widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!], - }; - }); + return { + ...prev, + widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!], + }; + }, + true + ); context.closeModal(id); }; diff --git a/src/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal.tsx b/src/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal.tsx index 5740875ee..f33207f93 100644 --- a/src/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal.tsx +++ b/src/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal.tsx @@ -19,10 +19,14 @@ export const WidgetsRemoveModal = ({ if (!configName) return null; const updateConfig = useConfigStore((x) => x.updateConfig); const handleDeletion = () => { - updateConfig(configName, (prev) => ({ - ...prev, - widgets: prev.widgets.filter((w) => w.id !== innerProps.widgetId), - })); + updateConfig( + configName, + (prev) => ({ + ...prev, + widgets: prev.widgets.filter((w) => w.id !== innerProps.widgetId), + }), + true + ); context.closeModal(id); }; diff --git a/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx b/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx index 383d877a4..3c1452da6 100644 --- a/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx +++ b/src/components/Dashboard/Wrappers/Category/useCategoryActions.tsx @@ -29,29 +29,35 @@ export const useCategoryActions = (configName: string | undefined, category: Cat }; // 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); + 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); + 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 + 2 })), - ], - wrappers: [ - ...aboveWrappers, - newWrapper, - // Move wrappers below down - ...belowWrappers.map((x) => ({ ...x, position: x.position + 2 })), - ], - }; - }); + 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 + ); }, }, }); @@ -78,29 +84,35 @@ export const useCategoryActions = (configName: string | undefined, category: Cat }; // 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); + 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); + 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 })), - ], - }; - }); + 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 + ); }, }, }); @@ -109,51 +121,59 @@ export const useCategoryActions = (configName: string | undefined, category: Cat const moveCategoryUp = () => { if (!configName) return; - updateConfig(configName, (previous) => { - const currentItem = previous.categories.find((x) => x.id === category.id); - if (!currentItem) return previous; + 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 - 2); + const upperItem = previous.categories.find((x) => x.position === currentItem.position - 2); - if (!upperItem) return previous; + if (!upperItem) return previous; - currentItem.position -= 2; - upperItem.position += 2; + currentItem.position -= 2; + upperItem.position += 2; - return { - ...previous, - categories: [ - ...previous.categories.filter((c) => ![currentItem.id, upperItem.id].includes(c.id)), - { ...upperItem }, - { ...currentItem }, - ], - }; - }); + return { + ...previous, + categories: [ + ...previous.categories.filter((c) => ![currentItem.id, upperItem.id].includes(c.id)), + { ...upperItem }, + { ...currentItem }, + ], + }; + }, + true + ); }; const moveCategoryDown = () => { if (!configName) return; - updateConfig(configName, (previous) => { - const currentItem = previous.categories.find((x) => x.id === category.id); - if (!currentItem) return previous; + 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 + 2); + const belowItem = previous.categories.find((x) => x.position === currentItem.position + 2); - if (!belowItem) return previous; + if (!belowItem) return previous; - currentItem.position += 2; - belowItem.position -= 2; + currentItem.position += 2; + belowItem.position -= 2; - return { - ...previous, - categories: [ - ...previous.categories.filter((c) => ![currentItem.id, belowItem.id].includes(c.id)), - { ...currentItem }, - { ...belowItem }, - ], - }; - }); + return { + ...previous, + categories: [ + ...previous.categories.filter((c) => ![currentItem.id, belowItem.id].includes(c.id)), + { ...currentItem }, + { ...belowItem }, + ], + }; + }, + true + ); }; const edit = async () => { diff --git a/src/components/Dashboard/Wrappers/WrapperContent.tsx b/src/components/Dashboard/Wrappers/WrapperContent.tsx index d34eadfa3..a0a3227c0 100644 --- a/src/components/Dashboard/Wrappers/WrapperContent.tsx +++ b/src/components/Dashboard/Wrappers/WrapperContent.tsx @@ -14,6 +14,7 @@ interface WrapperContentProps { wrapper: RefObject; items: MutableRefObject>>; gridstack: MutableRefObject; + updateGridstackRef: MutableRefObject<(() => void) | undefined>; }; } diff --git a/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts index 3fdadbd66..550d62d91 100644 --- a/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts +++ b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts @@ -54,6 +54,7 @@ export const initializeGridstack = ( if (!firstNode) return; events.onAdd(firstNode); }); + grid.batchUpdate(); grid.removeAll(false); items.forEach( diff --git a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts index f68c24e09..b6483c363 100644 --- a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts +++ b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts @@ -31,7 +31,7 @@ export const useGridstack = ( areaId: string ): UseGristackReturnType => { const isEditMode = useEditModeStore((x) => x.enabled); - const { config, name: configName } = useConfigContext(); + const { config, configVersion, name: configName } = useConfigContext(); const updateConfig = useConfigStore((x) => x.updateConfig); // define reference for wrapper - is used to calculate the width of the wrapper const wrapperRef = useRef(null); @@ -51,7 +51,7 @@ export const useGridstack = ( ? x.area.properties.location === areaId : x.area.properties.id === areaId) ) ?? [], - [config] + [configVersion] ); const widgets = useMemo(() => { if (!config) return []; @@ -62,7 +62,7 @@ export const useGridstack = ( ? w.area.properties.location === areaId : w.area.properties.id === areaId) ); - }, [config]); + }, [configVersion]); // define items in itemRefs for easy access and reference to items if (Object.keys(itemRefs.current).length !== items.length + (widgets ?? []).length) { @@ -208,7 +208,7 @@ export const useGridstack = ( onAdd, } ); - }, [items.length, wrapperRef.current, (widgets ?? []).length]); + }, [items, wrapperRef.current, widgets]); return { apps: items, diff --git a/src/components/Settings/Customization/Layout/LayoutSelector.tsx b/src/components/Settings/Customization/Layout/LayoutSelector.tsx index bd3eb3203..576886898 100644 --- a/src/components/Settings/Customization/Layout/LayoutSelector.tsx +++ b/src/components/Settings/Customization/Layout/LayoutSelector.tsx @@ -45,22 +45,26 @@ export const LayoutSelector = ({ defaultLayout }: LayoutSelectorProps) => { ) => { const value = event.target.checked; setState(value); - updateConfig(configName, (prev) => { - const { layout } = prev.settings.customization; + updateConfig( + configName, + (prev) => { + const { layout } = prev.settings.customization; - layout[key] = value; + layout[key] = value; - return { - ...prev, - settings: { - ...prev.settings, - customization: { - ...prev.settings.customization, - layout, + return { + ...prev, + settings: { + ...prev.settings, + customization: { + ...prev.settings.customization, + layout, + }, }, - }, - }; - }); + }; + }, + true + ); }; return ( diff --git a/src/config/init.ts b/src/config/init.ts index 47b039f56..7255c7e37 100644 --- a/src/config/init.ts +++ b/src/config/init.ts @@ -4,12 +4,12 @@ import { useConfigContext } from './provider'; import { useConfigStore } from './store'; export const useInitConfig = (initialConfig: ConfigType) => { - const { setConfigName } = useConfigContext(); + const { setConfigName, increaseVersion } = useConfigContext(); const configName = initialConfig.configProperties?.name ?? 'default'; const initConfig = useConfigStore((x) => x.initConfig); useEffect(() => { setConfigName(configName); - initConfig(configName, initialConfig); + initConfig(configName, initialConfig, increaseVersion); }, [configName]); }; diff --git a/src/config/provider.tsx b/src/config/provider.tsx index 3ad6b75a3..3559287d9 100644 --- a/src/config/provider.tsx +++ b/src/config/provider.tsx @@ -7,21 +7,26 @@ import { useConfigStore } from './store'; export type ConfigContextType = { config: ConfigType | undefined; name: string | undefined; + configVersion: number | undefined; + increaseVersion: () => void; setConfigName: (name: string) => void; }; const ConfigContext = createContext({ name: 'unknown', config: undefined, + configVersion: undefined, + increaseVersion: () => console.error('Provider not set'), setConfigName: () => console.error('Provider not set'), }); export const ConfigProvider = ({ children }: { children: ReactNode }) => { const [configName, setConfigName] = useState(); + const [configVersion, setConfigVersion] = useState(0); const { configs } = useConfigStore((s) => ({ configs: s.configs }), shallow); const { setPrimaryColor, setSecondaryColor, setPrimaryShade } = useColorTheme(); - const currentConfig = configs.find((c) => c.configProperties.name === configName); + const currentConfig = configs.find((c) => c.value.configProperties.name === configName)?.value; useEffect(() => { setPrimaryColor(currentConfig?.settings.customization.colors.primary || 'red'); @@ -34,6 +39,8 @@ export const ConfigProvider = ({ children }: { children: ReactNode }) => { value={{ name: configName, config: currentConfig, + configVersion, + increaseVersion: () => setConfigVersion((v) => v + 1), setConfigName: (name: string) => setConfigName(name), }} > diff --git a/src/config/store.ts b/src/config/store.ts index 473a9985d..04342245e 100644 --- a/src/config/store.ts +++ b/src/config/store.ts @@ -3,33 +3,46 @@ import { ConfigType } from '../types/config'; export const useConfigStore = create((set, get) => ({ configs: [], - initConfig: (name, config) => { + initConfig: (name, config, increaseVersion) => { set((old) => ({ ...old, - configs: [...old.configs.filter((x) => x.configProperties?.name !== name), config], + configs: [ + ...old.configs.filter((x) => x.value.configProperties?.name !== name), + { increaseVersion, value: config }, + ], })); }, - // TODO: use callback with current config as input - updateConfig: async (name, updateCallback: (previous: ConfigType) => ConfigType) => { + updateConfig: async ( + name, + updateCallback: (previous: ConfigType) => ConfigType, + shouldRegenerateGridstack = false + ) => { const { configs } = get(); - const currentConfig = configs.find((x) => x.configProperties.name === name); + const currentConfig = configs.find((x) => x.value.configProperties.name === name); if (!currentConfig) return; // TODO: update config on server - const updatedConfig = updateCallback(currentConfig); - + const updatedConfig = updateCallback(currentConfig.value); set((old) => ({ ...old, - configs: [...old.configs.filter((x) => x.configProperties.name !== name), updatedConfig], + configs: [ + ...old.configs.filter((x) => x.value.configProperties.name !== name), + { value: updatedConfig, increaseVersion: currentConfig.increaseVersion }, + ], })); + + if (shouldRegenerateGridstack) { + currentConfig.increaseVersion(); + } }, })); interface UseConfigStoreType { - configs: ConfigType[]; - initConfig: (name: string, config: ConfigType) => void; + configs: { increaseVersion: () => void; value: ConfigType }[]; + initConfig: (name: string, config: ConfigType, increaseVersion: () => void) => void; updateConfig: ( name: string, - updateCallback: (previous: ConfigType) => ConfigType + updateCallback: (previous: ConfigType) => ConfigType, + shouldRegenerateGridstack?: boolean ) => Promise; }