Files
homarr/packages/old-import/src/move-widgets-and-apps-merge.ts

325 lines
9.8 KiB
TypeScript

import { objectEntries } from "@homarr/common";
import { logger } from "@homarr/log";
import type { BoardSize, OldmarrApp, OldmarrConfig, OldmarrWidget } from "@homarr/old-schema";
import { boardSizes } from "@homarr/old-schema";
import { mapColumnCount } from "./mappers/map-column-count";
import type { OldmarrImportConfiguration } from "./settings";
export const moveWidgetsAndAppsIfMerge = (
old: OldmarrConfig,
wrapperIdsToMerge: string[],
configuration: OldmarrImportConfiguration,
) => {
const firstId = wrapperIdsToMerge[0];
if (!firstId) {
return { apps: old.apps, widgets: old.widgets };
}
const affectedMap = new Map<string, { apps: OldmarrApp[]; widgets: OldmarrWidget[] }>(
wrapperIdsToMerge.map((id) => [
id,
{
apps: old.apps.filter((app) => app.area.type !== "sidebar" && id === app.area.properties.id),
widgets: old.widgets.filter((app) => app.area.type !== "sidebar" && id === app.area.properties.id),
},
]),
);
logger.debug(`Merging wrappers at the end of the board count=${wrapperIdsToMerge.length}`);
const offsets = boardSizes.reduce(
(previous, screenSize) => {
previous[screenSize] = 0;
return previous;
},
{} as Record<BoardSize, number>,
);
for (const id of wrapperIdsToMerge) {
const requiredHeights = boardSizes.reduce(
(previous, screenSize) => {
previous[screenSize] = 0;
return previous;
},
{} as Record<BoardSize, number>,
);
const affected = affectedMap.get(id);
if (!affected) {
continue;
}
const apps = affected.apps;
const widgets = affected.widgets;
for (const app of apps) {
if (app.area.type === "sidebar") continue;
// Move item to first wrapper
app.area.properties.id = firstId;
for (const screenSize of boardSizes) {
const screenSizeShape = app.shape[screenSize];
if (!screenSizeShape) {
continue;
}
// Find the highest widget in the wrapper to increase the offset accordingly
if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeights[screenSize]) {
requiredHeights[screenSize] = screenSizeShape.location.y + screenSizeShape.size.height;
}
// Move item down as much as needed to not overlap with other items
screenSizeShape.location.y += offsets[screenSize];
}
}
for (const widget of widgets) {
if (widget.area.type === "sidebar") continue;
// Move item to first wrapper
widget.area.properties.id = firstId;
for (const screenSize of boardSizes) {
const screenSizeShape = widget.shape[screenSize];
if (!screenSizeShape) {
continue;
}
// Find the highest widget in the wrapper to increase the offset accordingly
if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeights[screenSize]) {
requiredHeights[screenSize] = screenSizeShape.location.y + screenSizeShape.size.height;
}
// Move item down as much as needed to not overlap with other items
screenSizeShape.location.y += offsets[screenSize];
}
}
for (const screenSize of boardSizes) {
offsets[screenSize] += requiredHeights[screenSize];
}
}
if (configuration.sidebarBehaviour === "last-section") {
const areas = [...old.apps.map((app) => app.area), ...old.widgets.map((widget) => widget.area)];
if (
old.settings.customization.layout.enabledLeftSidebar ||
areas.some((area) => area.type === "sidebar" && area.properties.location === "left")
) {
for (const screenSize of boardSizes) {
offsets[screenSize] = moveWidgetsAndAppsInLeftSidebar(old, firstId, offsets[screenSize], screenSize);
}
}
if (
old.settings.customization.layout.enabledRightSidebar ||
areas.some((area) => area.type === "sidebar" && area.properties.location === "right")
) {
for (const screenSize of boardSizes) {
moveWidgetsAndAppsInRightSidebar(old, firstId, offsets[screenSize], screenSize);
}
}
} else {
// Remove all widgets and apps in the sidebar
return {
apps: old.apps.filter((app) => app.area.type !== "sidebar"),
widgets: old.widgets.filter((app) => app.area.type !== "sidebar"),
};
}
return { apps: old.apps, widgets: old.widgets };
};
const moveWidgetsAndAppsInLeftSidebar = (
old: OldmarrConfig,
firstId: string,
offset: number,
screenSize: BoardSize,
) => {
const columnCount = mapColumnCount(old.settings.customization.gridstack, screenSize);
let requiredHeight = updateItems({
// This should work as the reference of the items did not change, only the array reference did
items: [...old.widgets, ...old.apps],
screenSize,
filter: (item) =>
item.area.type === "sidebar" &&
item.area.properties.location === "left" &&
(columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
update: (item) => {
item.area = {
type: "wrapper",
properties: {
id: firstId,
},
};
const screenSizeShape = item.shape[screenSize];
if (!screenSizeShape) return;
// Reduce width to one if column count is one
if (screenSizeShape.size.width > columnCount) {
screenSizeShape.size.width = columnCount;
}
screenSizeShape.location.y += offset;
},
});
// Only increase offset if there are less than 3 columns because then the items have to be stacked
if (columnCount <= 3) {
offset += requiredHeight;
}
// When column count is 0 we need to stack the items of the sidebar on top of each other
if (columnCount !== 1) {
return offset;
}
requiredHeight = updateItems({
// This should work as the reference of the items did not change, only the array reference did
items: [...old.widgets, ...old.apps],
screenSize,
filter: (item) =>
item.area.type === "sidebar" &&
item.area.properties.location === "left" &&
item.shape[screenSize]?.location.x === 1,
update: (item) => {
item.area = {
type: "wrapper",
properties: {
id: firstId,
},
};
const screenSizeShape = item.shape[screenSize];
if (!screenSizeShape) return;
screenSizeShape.location.x = 0;
screenSizeShape.location.y += offset;
},
});
offset += requiredHeight;
return offset;
};
const moveWidgetsAndAppsInRightSidebar = (
old: OldmarrConfig,
firstId: string,
offset: number,
screenSize: BoardSize,
) => {
const columnCount = mapColumnCount(old.settings.customization.gridstack, screenSize);
const xOffsetDelta = Math.max(columnCount - 2, 0);
const requiredHeight = updateItems({
// This should work as the reference of the items did not change, only the array reference did
items: [...old.widgets, ...old.apps],
screenSize,
filter: (item) =>
item.area.type === "sidebar" &&
item.area.properties.location === "right" &&
(columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
update: (item) => {
item.area = {
type: "wrapper",
properties: {
id: firstId,
},
};
const screenSizeShape = item.shape[screenSize];
if (!screenSizeShape) return;
// Reduce width to one if column count is one
if (screenSizeShape.size.width > columnCount) {
screenSizeShape.size.width = columnCount;
}
screenSizeShape.location.y += offset;
screenSizeShape.location.x += xOffsetDelta;
},
});
// When column count is 0 we need to stack the items of the sidebar on top of each other
if (columnCount !== 1) {
return;
}
offset += requiredHeight;
updateItems({
// This should work as the reference of the items did not change, only the array reference did
items: [...old.widgets, ...old.apps],
screenSize,
filter: (item) =>
item.area.type === "sidebar" &&
item.area.properties.location === "left" &&
item.shape[screenSize]?.location.x === 1,
update: (item) => {
item.area = {
type: "wrapper",
properties: {
id: firstId,
},
};
const screenSizeShape = item.shape[screenSize];
if (!screenSizeShape) return;
screenSizeShape.location.x = 0;
screenSizeShape.location.y += offset;
},
});
};
const createItemSnapshot = (item: OldmarrApp | OldmarrWidget, screenSize: BoardSize) => ({
x: item.shape[screenSize]?.location.x,
y: item.shape[screenSize]?.location.y,
height: item.shape[screenSize]?.size.height,
width: item.shape[screenSize]?.size.width,
section:
item.area.type === "sidebar"
? {
type: "sidebar",
location: item.area.properties.location,
}
: {
type: item.area.type,
id: item.area.properties.id,
},
toString(): string {
return objectEntries(this)
.filter(([key]) => key !== "toString")
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join(" ");
},
});
const updateItems = (options: {
items: (OldmarrApp | OldmarrWidget)[];
filter: (item: OldmarrApp | OldmarrWidget) => boolean;
update: (item: OldmarrApp | OldmarrWidget) => void;
screenSize: BoardSize;
}) => {
const items = options.items.filter(options.filter);
let requiredHeight = 0;
for (const item of items) {
const before = createItemSnapshot(item, options.screenSize);
options.update(item);
const screenSizeShape = item.shape[options.screenSize];
if (!screenSizeShape) return requiredHeight;
if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeight) {
requiredHeight = screenSizeShape.location.y + screenSizeShape.size.height;
}
const after = createItemSnapshot(item, options.screenSize);
logger.debug(
`Moved item ${item.id}\n [snapshot before]: ${before.toString()}\n [snapshot after]: ${after.toString()}`,
);
}
return requiredHeight;
};