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