mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-09 23:15:46 +01:00
🐛 Fix issues with gridstack
This commit is contained in:
@@ -111,38 +111,7 @@ export const AvailableElementTypes = ({
|
||||
id: getLowestWrapper()?.id ?? 'default',
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
sm: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
width: appTileDefinition.minWidth,
|
||||
height: appTileDefinition.minHeight,
|
||||
},
|
||||
},
|
||||
md: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
width: appTileDefinition.minWidth,
|
||||
height: appTileDefinition.minHeight,
|
||||
},
|
||||
},
|
||||
lg: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
width: appTileDefinition.minWidth,
|
||||
height: appTileDefinition.minHeight,
|
||||
},
|
||||
},
|
||||
},
|
||||
shape: {},
|
||||
integration: {
|
||||
type: null,
|
||||
properties: [],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
import { ReactNode, RefObject } from 'react';
|
||||
import widgets from '../../../widgets';
|
||||
|
||||
interface GridstackTileWrapperProps {
|
||||
id: string;
|
||||
@@ -29,19 +30,22 @@ export const GridstackTileWrapper = ({
|
||||
maxHeight,
|
||||
children,
|
||||
itemRef,
|
||||
}: GridstackTileWrapperProps) => (
|
||||
}: GridstackTileWrapperProps) => {
|
||||
const locationProperties = useLocationProperties(x, y);
|
||||
const normalizedWidth = width ?? minWidth;
|
||||
|
||||
const normalizedHeight = height ?? minHeight;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="grid-stack-item"
|
||||
data-type={type}
|
||||
data-id={id}
|
||||
gs-x={x}
|
||||
data-gridstack-x={x}
|
||||
gs-y={y}
|
||||
data-gridstack-y={y}
|
||||
gs-w={width}
|
||||
data-gridstack-w={width}
|
||||
gs-h={height}
|
||||
data-gridstack-h={height}
|
||||
{...locationProperties}
|
||||
gs-w={normalizedWidth}
|
||||
data-gridstack-w={normalizedWidth}
|
||||
gs-h={normalizedHeight}
|
||||
data-gridstack-h={normalizedHeight}
|
||||
gs-min-w={minWidth}
|
||||
gs-min-h={minHeight}
|
||||
gs-max-w={maxWidth}
|
||||
@@ -51,3 +55,21 @@ export const GridstackTileWrapper = ({
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useLocationProperties = (x: number | undefined, y: number | undefined) => {
|
||||
const isLocationDefined = x !== undefined && y !== undefined;
|
||||
|
||||
if (!isLocationDefined) {
|
||||
return {
|
||||
'gs-auto-position': 'true',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'gs-x': x.toString(),
|
||||
'data-gridstack-x': x.toString(),
|
||||
'gs-y': y.toString(),
|
||||
'data-gridstack-y': y.toString(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,8 +34,8 @@ export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) {
|
||||
key={app.id}
|
||||
itemRef={refs.items.current[app.id]}
|
||||
{...tile}
|
||||
{...app.shape[shapeSize]?.location}
|
||||
{...app.shape[shapeSize]?.size}
|
||||
{...(app.shape[shapeSize]?.location ?? {})}
|
||||
{...(app.shape[shapeSize]?.size ?? {})}
|
||||
>
|
||||
<TileComponent className="grid-stack-item-content" app={app} />
|
||||
</GridstackTileWrapper>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GridStack, GridStackNode } from 'fily-publish-gridstack';
|
||||
import { GridItemHTMLElement, GridStack, GridStackNode } from 'fily-publish-gridstack';
|
||||
import { MutableRefObject, RefObject } from 'react';
|
||||
import { AppType } from '../../../../types/app';
|
||||
import { ShapeType } from '../../../../types/shape';
|
||||
@@ -15,6 +15,7 @@ export const initializeGridstack = (
|
||||
isEditMode: boolean,
|
||||
wrapperColumnCount: 3 | 6 | 12,
|
||||
shapeSize: 'sm' | 'md' | 'lg',
|
||||
tilesWithUnknownLocation: TileWithUnknownLocation[],
|
||||
events: {
|
||||
onChange: (changedNode: GridStackNode) => void;
|
||||
onAdd: (addedNode: GridStackNode) => void;
|
||||
@@ -68,11 +69,25 @@ export const initializeGridstack = (
|
||||
const item = itemRefs.current[id]?.current;
|
||||
setAttributesFromShape(item, shape[shapeSize]);
|
||||
item && grid.makeWidget(item as HTMLDivElement);
|
||||
if (!shape[shapeSize] && item) {
|
||||
const gridItemElement = item as GridItemHTMLElement;
|
||||
if (gridItemElement.gridstackNode) {
|
||||
const { x, y, w, h } = gridItemElement.gridstackNode;
|
||||
tilesWithUnknownLocation.push({ x, y, w, h, type: 'app', id });
|
||||
}
|
||||
}
|
||||
});
|
||||
widgets.forEach(({ id, shape }) => {
|
||||
const item = itemRefs.current[id]?.current;
|
||||
setAttributesFromShape(item, shape[shapeSize]);
|
||||
item && grid.makeWidget(item as HTMLDivElement);
|
||||
if (!shape[shapeSize] && item) {
|
||||
const gridItemElement = item as GridItemHTMLElement;
|
||||
if (gridItemElement.gridstackNode) {
|
||||
const { x, y, w, h } = gridItemElement.gridstackNode;
|
||||
tilesWithUnknownLocation.push({ x, y, w, h, type: 'widget', id });
|
||||
}
|
||||
}
|
||||
});
|
||||
grid.batchUpdate(false);
|
||||
};
|
||||
@@ -84,3 +99,12 @@ function setAttributesFromShape(ref: HTMLDivElement | null, sizedShape: ShapeTyp
|
||||
ref.setAttribute('gs-w', sizedShape.size.width.toString());
|
||||
ref.setAttribute('gs-h', sizedShape.size.height.toString());
|
||||
}
|
||||
|
||||
export type TileWithUnknownLocation = {
|
||||
x?: number;
|
||||
y?: number;
|
||||
w?: number;
|
||||
h?: number;
|
||||
type: 'app' | 'widget';
|
||||
id: string;
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AppType } from '../../../../types/app';
|
||||
import { AreaType } from '../../../../types/area';
|
||||
import { IWidget } from '../../../../widgets/widgets';
|
||||
import { useEditModeStore } from '../../Views/useEditModeStore';
|
||||
import { initializeGridstack } from './init-gridstack';
|
||||
import { initializeGridstack, TileWithUnknownLocation } from './init-gridstack';
|
||||
import { useGridstackStore, useWrapperColumnCount } from './store';
|
||||
|
||||
interface UseGristackReturnType {
|
||||
@@ -232,6 +232,7 @@ export const useGridstack = (
|
||||
|
||||
// initialize the gridstack
|
||||
useEffect(() => {
|
||||
const tilesWithUnknownLocation: TileWithUnknownLocation[] = [];
|
||||
initializeGridstack(
|
||||
areaType,
|
||||
wrapperRef,
|
||||
@@ -243,11 +244,62 @@ export const useGridstack = (
|
||||
isEditMode,
|
||||
wrapperColumnCount,
|
||||
shapeSize,
|
||||
tilesWithUnknownLocation,
|
||||
{
|
||||
onChange,
|
||||
onAdd,
|
||||
}
|
||||
);
|
||||
if (!configName) return;
|
||||
updateConfig(configName, (prev) => ({
|
||||
...prev,
|
||||
apps: prev.apps.map((app) => {
|
||||
const currentUnknownLocation = tilesWithUnknownLocation.find(
|
||||
(x) => x.type === 'app' && x.id === app.id
|
||||
);
|
||||
if (!currentUnknownLocation) return app;
|
||||
|
||||
return {
|
||||
...app,
|
||||
shape: {
|
||||
...app.shape,
|
||||
[shapeSize]: {
|
||||
location: {
|
||||
x: currentUnknownLocation.x,
|
||||
y: currentUnknownLocation.y,
|
||||
},
|
||||
size: {
|
||||
width: currentUnknownLocation.w,
|
||||
height: currentUnknownLocation.h,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
widgets: prev.widgets.map((widget) => {
|
||||
const currentUnknownLocation = tilesWithUnknownLocation.find(
|
||||
(x) => x.type === 'widget' && x.id === widget.id
|
||||
);
|
||||
if (!currentUnknownLocation) return widget;
|
||||
|
||||
return {
|
||||
...widget,
|
||||
shape: {
|
||||
...widget.shape,
|
||||
[shapeSize]: {
|
||||
location: {
|
||||
x: currentUnknownLocation.x,
|
||||
y: currentUnknownLocation.y,
|
||||
},
|
||||
size: {
|
||||
width: currentUnknownLocation.w,
|
||||
height: currentUnknownLocation.h,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
}));
|
||||
}, [items, wrapperRef.current, widgets, wrapperColumnCount]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,16 +20,14 @@ export const ToggleEditModeAction = () => {
|
||||
const smallerThanSm = useScreenSmallerThan('sm');
|
||||
const { config } = useConfigContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (enabled || config === undefined || config?.schemaVersion === undefined) return;
|
||||
const toggleButtonClicked = () => {
|
||||
toggleEditMode();
|
||||
if (enabled || config === undefined || config?.schemaVersion === undefined) {
|
||||
const configName = getCookie('config-name')?.toString() ?? 'default';
|
||||
axios.put(`/api/configs/${configName}`, { ...config });
|
||||
Consola.log('Saved config to server', configName);
|
||||
}, [enabled]);
|
||||
|
||||
const toggleButtonClicked = () => {
|
||||
toggleEditMode();
|
||||
if (!enabled) {
|
||||
hideNotification('toggle-edit-mode');
|
||||
} else if (!enabled) {
|
||||
showNotification({
|
||||
styles: (theme) => ({
|
||||
root: {
|
||||
|
||||
@@ -189,38 +189,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
||||
id: getLowestWrapper()?.id ?? 'default',
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
sm: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
width: appTileDefinition.minWidth,
|
||||
height: appTileDefinition.minHeight,
|
||||
},
|
||||
},
|
||||
md: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
width: appTileDefinition.minWidth,
|
||||
height: appTileDefinition.minHeight,
|
||||
},
|
||||
},
|
||||
lg: {
|
||||
location: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
size: {
|
||||
width: appTileDefinition.minWidth,
|
||||
height: appTileDefinition.minHeight,
|
||||
},
|
||||
},
|
||||
},
|
||||
shape: {},
|
||||
integration: {
|
||||
type: null,
|
||||
properties: [],
|
||||
|
||||
@@ -34,9 +34,9 @@ export function migrateConfig(config: Config): ConfigType {
|
||||
},
|
||||
customization: {
|
||||
colors: {
|
||||
primary: config.settings.primaryColor,
|
||||
secondary: config.settings.secondaryColor,
|
||||
shade: config.settings.primaryShade,
|
||||
primary: config.settings.primaryColor ?? 'red',
|
||||
secondary: config.settings.secondaryColor ?? 'orange',
|
||||
shade: config.settings.primaryShade ?? 7,
|
||||
},
|
||||
layout: {
|
||||
enabledDocker: config.modules.docker?.enabled ?? false,
|
||||
@@ -60,7 +60,7 @@ export function migrateConfig(config: Config): ConfigType {
|
||||
|
||||
if (!categoryName) {
|
||||
newConfig.apps.push(
|
||||
migrateService(service, index, {
|
||||
migrateService(service, {
|
||||
type: 'wrapper',
|
||||
properties: {
|
||||
id: 'default',
|
||||
@@ -77,7 +77,7 @@ export function migrateConfig(config: Config): ConfigType {
|
||||
}
|
||||
|
||||
newConfig.apps.push(
|
||||
migrateService(service, index, { type: 'category', properties: { id: category.id } })
|
||||
migrateService(service, { type: 'category', properties: { id: category.id } })
|
||||
);
|
||||
});
|
||||
|
||||
@@ -114,11 +114,7 @@ const getShapeForColumnCount = (index: number, columnCount: number) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const migrateService = (
|
||||
oldService: serviceItem,
|
||||
serviceIndex: number,
|
||||
areaType: AreaType
|
||||
): AppType => ({
|
||||
const migrateService = (oldService: serviceItem, areaType: AreaType): AppType => ({
|
||||
id: uuidv4(),
|
||||
name: oldService.name,
|
||||
url: oldService.url,
|
||||
@@ -135,11 +131,7 @@ const migrateService = (
|
||||
},
|
||||
integration: migrateIntegration(oldService),
|
||||
area: areaType,
|
||||
shape: {
|
||||
lg: getShapeForColumnCount(serviceIndex, 12),
|
||||
md: getShapeForColumnCount(serviceIndex, 6),
|
||||
sm: getShapeForColumnCount(serviceIndex, 3),
|
||||
},
|
||||
shape: {},
|
||||
});
|
||||
|
||||
const migrateModules = (config: Config): IWidget<string, any>[] => {
|
||||
|
||||
Reference in New Issue
Block a user