Files
Homarr/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts

267 lines
8.9 KiB
TypeScript
Raw Normal View History

2022-12-04 17:36:30 +01:00
import { GridStack, GridStackNode } from 'fily-publish-gridstack';
import {
createRef,
MutableRefObject,
RefObject,
2023-01-06 22:46:07 +01:00
useEffect, useMemo,
2022-12-04 17:36:30 +01:00
useRef,
} from 'react';
import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store';
import { AppType } from '../../../../types/app';
import { AreaType } from '../../../../types/area';
import { IWidget } from '../../../../widgets/widgets';
2022-12-04 19:10:07 +01:00
import { useEditModeStore } from '../../Views/useEditModeStore';
2022-12-04 17:36:30 +01:00
import { initializeGridstack } from './init-gridstack';
2023-01-06 22:46:07 +01:00
import { useGridstackStore, useWrapperColumnCount } from './store';
2023-01-05 06:01:54 +01:00
2022-12-04 17:36:30 +01:00
interface UseGristackReturnType {
apps: AppType[];
widgets: IWidget<string, any>[];
2022-12-04 17:36:30 +01:00
refs: {
wrapper: RefObject<HTMLDivElement>;
items: MutableRefObject<Record<string, RefObject<HTMLDivElement>>>;
gridstack: MutableRefObject<GridStack | undefined>;
};
}
export const useGridstack = (
areaType: 'wrapper' | 'category' | 'sidebar',
areaId: string
): UseGristackReturnType => {
const isEditMode = useEditModeStore((x) => x.enabled);
const { config, configVersion, name: configName } = useConfigContext();
2022-12-04 17:36:30 +01:00
const updateConfig = useConfigStore((x) => x.updateConfig);
// define reference for wrapper - is used to calculate the width of the wrapper
const wrapperRef = useRef<HTMLDivElement>(null);
// references to the diffrent items contained in the gridstack
const itemRefs = useRef<Record<string, RefObject<HTMLDivElement>>>({});
// reference of the gridstack object for modifications after initialization
const gridRef = useRef<GridStack>();
2023-01-06 22:46:07 +01:00
const wrapperColumnCount = useWrapperColumnCount();
const shapeSize = useGridstackStore(x => x.currentShapeSize);
const mainAreaWidth = useGridstackStore(x => x.mainAreaWidth);
2022-12-04 17:36:30 +01:00
// width of the wrapper (updating on page resize)
2023-01-03 16:40:15 +01:00
const root: HTMLHtmlElement = useMemo(() => document.querySelector(':root')!, []);
2022-12-04 17:36:30 +01:00
2023-01-06 22:46:07 +01:00
if (!mainAreaWidth || !shapeSize || !wrapperColumnCount) throw new Error('UseGridstack should not be executed before mainAreaWidth has been set!');
2022-12-04 17:36:30 +01:00
const items = useMemo(
() =>
config?.apps.filter(
2022-12-04 17:36:30 +01:00
(x) =>
x.area.type === areaType &&
(x.area.type === 'sidebar'
? x.area.properties.location === areaId
: x.area.properties.id === areaId)
) ?? [],
[configVersion, config?.apps.length]
2022-12-04 17:36:30 +01:00
);
const widgets = useMemo(() => {
if (!config) return [];
return config.widgets.filter(
(w) =>
w.area.type === areaType &&
(w.area.type === 'sidebar'
? w.area.properties.location === areaId
: w.area.properties.id === areaId)
);
}, [configVersion, config?.widgets.length]);
2022-12-04 17:36:30 +01:00
// define items in itemRefs for easy access and reference to items
if (Object.keys(itemRefs.current).length !== items.length + (widgets ?? []).length) {
2022-12-04 17:36:30 +01:00
items.forEach(({ id }: { id: keyof typeof itemRefs.current }) => {
itemRefs.current[id] = itemRefs.current[id] || createRef();
});
(widgets ?? []).forEach(({ id }) => {
itemRefs.current[id] = itemRefs.current[id] || createRef();
2022-12-04 17:36:30 +01:00
});
}
useEffect(() => {
2023-01-06 22:46:07 +01:00
const widgetWidth = mainAreaWidth / wrapperColumnCount;
2023-01-03 16:40:15 +01:00
// widget width is used to define sizes of gridstack items within global.scss
root.style.setProperty('--gridstack-widget-width', widgetWidth.toString());
gridRef.current?.cellHeight(widgetWidth);
}, [mainAreaWidth, wrapperColumnCount, gridRef.current]);
2023-01-03 16:40:15 +01:00
useEffect(() => {
// column count is used to define count of columns of gridstack within global.scss
root.style.setProperty('--gridstack-column-count', wrapperColumnCount.toString());
}, [wrapperColumnCount]);
2022-12-04 17:36:30 +01:00
const onChange = isEditMode
? (changedNode: GridStackNode) => {
if (!configName) return;
const itemType = changedNode.el?.getAttribute('data-type');
const itemId = changedNode.el?.getAttribute('data-id');
if (!itemType || !itemId) return;
// Updates the config and defines the new position of the item
updateConfig(configName, (previous) => {
const currentItem =
itemType === 'app'
? previous.apps.find((x) => x.id === itemId)
: previous.widgets.find((x) => x.id === itemId);
2022-12-04 17:36:30 +01:00
if (!currentItem) return previous;
2023-01-06 22:46:07 +01:00
currentItem.shape[shapeSize] = {
2022-12-04 17:36:30 +01:00
location: {
2023-01-06 22:46:07 +01:00
x: changedNode.x ?? currentItem.shape[shapeSize].location.x,
y: changedNode.y ?? currentItem.shape[shapeSize].location.y,
2022-12-04 17:36:30 +01:00
},
size: {
2023-01-06 22:46:07 +01:00
width: changedNode.w ?? currentItem.shape[shapeSize].size.width,
height: changedNode.h ?? currentItem.shape[shapeSize].size.height,
2022-12-04 17:36:30 +01:00
},
};
if (itemType === 'app') {
2022-12-04 17:36:30 +01:00
return {
...previous,
apps: [
...previous.apps.filter((x) => x.id !== itemId),
{ ...(currentItem as AppType) },
2022-12-04 17:36:30 +01:00
],
};
}
return {
...previous,
widgets: [
...previous.widgets.filter((x) => x.id !== itemId),
{ ...(currentItem as IWidget<string, any>) },
],
2022-12-04 17:36:30 +01:00
};
});
}
: () => {};
const onAdd = isEditMode
? (addedNode: GridStackNode) => {
if (!configName) return;
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 and wrapper of the item
updateConfig(
configName,
(previous) => {
const currentItem =
itemType === 'app'
? previous.apps.find((x) => x.id === itemId)
: previous.widgets.find((x) => x.id === itemId);
if (!currentItem) return previous;
if (areaType === 'sidebar') {
currentItem.area = {
type: areaType,
properties: {
location: areaId as 'right' | 'left',
},
};
} else {
currentItem.area = {
type: areaType,
properties: {
id: areaId,
},
};
}
2023-01-06 22:46:07 +01:00
currentItem.shape[shapeSize] = {
location: {
2023-01-06 22:46:07 +01:00
x: addedNode.x ?? currentItem.shape[shapeSize].location.x,
y: addedNode.y ?? currentItem.shape[shapeSize].location.y,
2022-12-04 17:36:30 +01:00
},
size: {
2023-01-06 22:46:07 +01:00
width: addedNode.w ?? currentItem.shape[shapeSize].size.width,
height: addedNode.h ?? currentItem.shape[shapeSize].size.height,
2022-12-04 17:36:30 +01:00
},
};
if (itemType === 'app') {
return {
...previous,
apps: [
...previous.apps.filter((x) => x.id !== itemId),
{ ...(currentItem as AppType) },
],
};
}
2022-12-04 17:36:30 +01:00
return {
...previous,
widgets: [
...previous.widgets.filter((x) => x.id !== itemId),
{ ...(currentItem as IWidget<string, any>) },
2022-12-04 17:36:30 +01:00
],
};
},
(prev, curr) => {
const isApp = itemType === 'app';
if (isApp) {
const currItem = curr.apps.find((x) => x.id === itemId);
const prevItem = prev.apps.find((x) => x.id === itemId);
if (!currItem || !prevItem) return false;
return (
currItem.area.type !== prevItem.area.type ||
Object.entries(currItem.area.properties).some(
([key, value]) =>
prevItem.area.properties[key as keyof AreaType['properties']] !== value
)
);
}
const currItem = curr.widgets.find((x) => x.id === itemId);
const prevItem = prev.widgets.find((x) => x.id === itemId);
if (!currItem || !prevItem) return false;
return (
currItem.area.type !== prevItem.area.type ||
Object.entries(currItem.area.properties).some(
([key, value]) =>
prevItem.area.properties[key as keyof AreaType['properties']] !== value
)
);
2022-12-04 17:36:30 +01:00
}
);
2022-12-04 17:36:30 +01:00
}
: () => {};
// initialize the gridstack
2023-01-06 22:46:07 +01:00
useEffect(() => {
2022-12-04 17:36:30 +01:00
initializeGridstack(
areaType,
wrapperRef,
gridRef,
itemRefs,
areaId,
items,
widgets ?? [],
2022-12-04 17:36:30 +01:00
isEditMode,
2023-01-04 19:06:19 +01:00
wrapperColumnCount,
2023-01-06 23:50:08 +01:00
shapeSize,
2022-12-04 17:36:30 +01:00
{
onChange,
onAdd,
}
);
2023-01-06 22:46:07 +01:00
}, [items, wrapperRef.current, widgets, wrapperColumnCount]);
2022-12-04 17:36:30 +01:00
return {
apps: items,
widgets: widgets ?? [],
2022-12-04 17:36:30 +01:00
refs: {
items: itemRefs,
wrapper: wrapperRef,
gridstack: gridRef,
},
};
};