fix(import): autofix missing shapes for sidebars and some sections as well (#2723)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import type { BoardSize, OldmarrConfig } from "@homarr/old-schema";
|
import type { OldmarrConfig } from "@homarr/old-schema";
|
||||||
|
|
||||||
export class OldHomarrImportError extends Error {
|
export class OldHomarrImportError extends Error {
|
||||||
constructor(oldConfig: OldmarrConfig, cause: unknown) {
|
constructor(oldConfig: OldmarrConfig, cause: unknown) {
|
||||||
@@ -7,9 +7,3 @@ export class OldHomarrImportError extends Error {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OldHomarrScreenSizeError extends Error {
|
|
||||||
constructor(type: "app" | "widget", id: string, screenSize: BoardSize) {
|
|
||||||
super(`Screen size not found for type=${type} id=${id} screenSize=${screenSize}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { isWidgetRestricted } from "@homarr/auth/shared";
|
|||||||
import { createId } from "@homarr/db";
|
import { createId } from "@homarr/db";
|
||||||
import { createDbInsertCollectionForTransaction } from "@homarr/db/collection";
|
import { createDbInsertCollectionForTransaction } from "@homarr/db/collection";
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
import type { BoardSize } from "@homarr/old-schema";
|
import type { BoardSize, OldmarrConfig } from "@homarr/old-schema";
|
||||||
import { boardSizes, getBoardSizeName } from "@homarr/old-schema";
|
import { boardSizes, getBoardSizeName } from "@homarr/old-schema";
|
||||||
|
|
||||||
import { widgetImports } from "../../../../widgets/src";
|
import { widgetImports } from "../../../../widgets/src";
|
||||||
import { fixSectionIssues } from "../../fix-section-issues";
|
import { fixSectionIssues } from "../../fix-section-issues";
|
||||||
|
import { OldHomarrImportError } from "../../import-error";
|
||||||
import { mapBoard } from "../../mappers/map-board";
|
import { mapBoard } from "../../mappers/map-board";
|
||||||
import { mapBreakpoint } from "../../mappers/map-breakpoint";
|
import { mapBreakpoint } from "../../mappers/map-breakpoint";
|
||||||
import { mapColumnCount } from "../../mappers/map-column-count";
|
import { mapColumnCount } from "../../mappers/map-column-count";
|
||||||
@@ -61,6 +62,13 @@ export const createBoardInsertCollection = (
|
|||||||
logger.debug(`Added apps to board insert collection count=${insertCollection.apps.length}`);
|
logger.debug(`Added apps to board insert collection count=${insertCollection.apps.length}`);
|
||||||
|
|
||||||
preparedBoards.forEach((board) => {
|
preparedBoards.forEach((board) => {
|
||||||
|
if (!hasEnoughItemShapes(board.config)) {
|
||||||
|
throw new OldHomarrImportError(
|
||||||
|
board.config,
|
||||||
|
new Error("Your config contains items without shapes for all board sizes."),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { wrappers, categories, wrapperIdsToMerge } = fixSectionIssues(board.config);
|
const { wrappers, categories, wrapperIdsToMerge } = fixSectionIssues(board.config);
|
||||||
const { apps, widgets } = moveWidgetsAndAppsIfMerge(board.config, wrapperIdsToMerge, {
|
const { apps, widgets } = moveWidgetsAndAppsIfMerge(board.config, wrapperIdsToMerge, {
|
||||||
...settings,
|
...settings,
|
||||||
@@ -130,3 +138,26 @@ export const createBoardInsertCollection = (
|
|||||||
|
|
||||||
return insertCollection;
|
return insertCollection;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasEnoughItemShapes = (config: {
|
||||||
|
apps: Pick<OldmarrConfig["apps"][number], "shape">[];
|
||||||
|
widgets: Pick<OldmarrConfig["widgets"][number], "shape">[];
|
||||||
|
}) => {
|
||||||
|
const invalidSizes: BoardSize[] = [];
|
||||||
|
|
||||||
|
for (const size of boardSizes) {
|
||||||
|
if (invalidSizes.includes(size)) continue;
|
||||||
|
|
||||||
|
if (config.apps.some((app) => app.shape[size] === undefined)) {
|
||||||
|
invalidSizes.push(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidSizes.includes(size)) continue;
|
||||||
|
|
||||||
|
if (config.widgets.some((widget) => widget.shape[size] === undefined)) {
|
||||||
|
invalidSizes.push(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidSizes.length <= 2;
|
||||||
|
};
|
||||||
|
|||||||
53
packages/old-import/src/import/test/board-collection.spec.ts
Normal file
53
packages/old-import/src/import/test/board-collection.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import type { BoardSize } from "@homarr/old-schema";
|
||||||
|
|
||||||
|
import { hasEnoughItemShapes } from "../collections/board-collection";
|
||||||
|
|
||||||
|
const defaultShape = {
|
||||||
|
location: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("hasEnoughItemShapes should check if there are more than one shape available for automatic reconstruction", () => {
|
||||||
|
test.each([
|
||||||
|
[true, [], []], // no items, so nothing to check
|
||||||
|
[true, [{ lg: true }], []], // lg always exists
|
||||||
|
[true, [], [{ md: true }]], // md always exists
|
||||||
|
[true, [{ md: true, sm: true }], [{ md: true, lg: true }]], // md always exists
|
||||||
|
[true, [{ md: true }], [{ md: true }]], // md always exists
|
||||||
|
[false, [{ md: true }, { md: true }], [{ lg: true }]], // md is missing for widgets
|
||||||
|
[false, [{ md: true }], [{ lg: true }]], // md is missing for widgets
|
||||||
|
[false, [{ md: true }], [{ md: true, lg: true }, { lg: true }]], // md is missing for 2. widget
|
||||||
|
] as [boolean, Shape[], Shape[]][])(
|
||||||
|
"should return %s if there are more than one shape available",
|
||||||
|
(returnValue, appShapes, widgetShapes) => {
|
||||||
|
const result = hasEnoughItemShapes({
|
||||||
|
apps: appShapes.map((shapes) => ({
|
||||||
|
shape: {
|
||||||
|
sm: shapes.sm ? defaultShape : undefined,
|
||||||
|
md: shapes.md ? defaultShape : undefined,
|
||||||
|
lg: shapes.lg ? defaultShape : undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
widgets: widgetShapes.map((shapes) => ({
|
||||||
|
shape: {
|
||||||
|
sm: shapes.sm ? defaultShape : undefined,
|
||||||
|
md: shapes.md ? defaultShape : undefined,
|
||||||
|
lg: shapes.lg ? defaultShape : undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(returnValue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
type Shape = Partial<Record<BoardSize, true>>;
|
||||||
@@ -3,7 +3,6 @@ import { logger } from "@homarr/log";
|
|||||||
import type { BoardSize, OldmarrApp, OldmarrConfig, OldmarrWidget } from "@homarr/old-schema";
|
import type { BoardSize, OldmarrApp, OldmarrConfig, OldmarrWidget } from "@homarr/old-schema";
|
||||||
import { boardSizes } from "@homarr/old-schema";
|
import { boardSizes } from "@homarr/old-schema";
|
||||||
|
|
||||||
import { OldHomarrScreenSizeError } from "./import-error";
|
|
||||||
import { mapColumnCount } from "./mappers/map-column-count";
|
import { mapColumnCount } from "./mappers/map-column-count";
|
||||||
import type { OldmarrImportConfiguration } from "./settings";
|
import type { OldmarrImportConfiguration } from "./settings";
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ export const moveWidgetsAndAppsIfMerge = (
|
|||||||
for (const screenSize of boardSizes) {
|
for (const screenSize of boardSizes) {
|
||||||
const screenSizeShape = app.shape[screenSize];
|
const screenSizeShape = app.shape[screenSize];
|
||||||
if (!screenSizeShape) {
|
if (!screenSizeShape) {
|
||||||
throw new OldHomarrScreenSizeError("app", app.id, screenSize);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the highest widget in the wrapper to increase the offset accordingly
|
// Find the highest widget in the wrapper to increase the offset accordingly
|
||||||
@@ -81,7 +80,7 @@ export const moveWidgetsAndAppsIfMerge = (
|
|||||||
for (const screenSize of boardSizes) {
|
for (const screenSize of boardSizes) {
|
||||||
const screenSizeShape = widget.shape[screenSize];
|
const screenSizeShape = widget.shape[screenSize];
|
||||||
if (!screenSizeShape) {
|
if (!screenSizeShape) {
|
||||||
throw new OldHomarrScreenSizeError("widget", widget.id, screenSize);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the highest widget in the wrapper to increase the offset accordingly
|
// Find the highest widget in the wrapper to increase the offset accordingly
|
||||||
@@ -145,15 +144,6 @@ const moveWidgetsAndAppsInLeftSidebar = (
|
|||||||
item.area.properties.location === "left" &&
|
item.area.properties.location === "left" &&
|
||||||
(columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
|
(columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
|
||||||
update: (item) => {
|
update: (item) => {
|
||||||
const screenSizeShape = item.shape[screenSize];
|
|
||||||
if (!screenSizeShape) {
|
|
||||||
throw new OldHomarrScreenSizeError("kind" in item ? "widget" : "app", item.id, screenSize);
|
|
||||||
}
|
|
||||||
// Reduce width to one if column count is one
|
|
||||||
if (screenSizeShape.size.width > columnCount) {
|
|
||||||
screenSizeShape.size.width = columnCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.area = {
|
item.area = {
|
||||||
type: "wrapper",
|
type: "wrapper",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -161,6 +151,14 @@ const moveWidgetsAndAppsInLeftSidebar = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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.y += offset;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -184,11 +182,6 @@ const moveWidgetsAndAppsInLeftSidebar = (
|
|||||||
item.area.properties.location === "left" &&
|
item.area.properties.location === "left" &&
|
||||||
item.shape[screenSize]?.location.x === 1,
|
item.shape[screenSize]?.location.x === 1,
|
||||||
update: (item) => {
|
update: (item) => {
|
||||||
const screenSizeShape = item.shape[screenSize];
|
|
||||||
if (!screenSizeShape) {
|
|
||||||
throw new OldHomarrScreenSizeError("kind" in item ? "widget" : "app", item.id, screenSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.area = {
|
item.area = {
|
||||||
type: "wrapper",
|
type: "wrapper",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -196,6 +189,9 @@ const moveWidgetsAndAppsInLeftSidebar = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const screenSizeShape = item.shape[screenSize];
|
||||||
|
if (!screenSizeShape) return;
|
||||||
|
|
||||||
screenSizeShape.location.x = 0;
|
screenSizeShape.location.x = 0;
|
||||||
screenSizeShape.location.y += offset;
|
screenSizeShape.location.y += offset;
|
||||||
},
|
},
|
||||||
@@ -222,16 +218,6 @@ const moveWidgetsAndAppsInRightSidebar = (
|
|||||||
item.area.properties.location === "right" &&
|
item.area.properties.location === "right" &&
|
||||||
(columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
|
(columnCount >= 2 || item.shape[screenSize]?.location.x === 0),
|
||||||
update: (item) => {
|
update: (item) => {
|
||||||
const screenSizeShape = item.shape[screenSize];
|
|
||||||
if (!screenSizeShape) {
|
|
||||||
throw new OldHomarrScreenSizeError("kind" in item ? "widget" : "app", item.id, screenSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce width to one if column count is one
|
|
||||||
if (screenSizeShape.size.width > columnCount) {
|
|
||||||
screenSizeShape.size.width = columnCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.area = {
|
item.area = {
|
||||||
type: "wrapper",
|
type: "wrapper",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -239,6 +225,14 @@ const moveWidgetsAndAppsInRightSidebar = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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.y += offset;
|
||||||
screenSizeShape.location.x += xOffsetDelta;
|
screenSizeShape.location.x += xOffsetDelta;
|
||||||
},
|
},
|
||||||
@@ -260,11 +254,6 @@ const moveWidgetsAndAppsInRightSidebar = (
|
|||||||
item.area.properties.location === "left" &&
|
item.area.properties.location === "left" &&
|
||||||
item.shape[screenSize]?.location.x === 1,
|
item.shape[screenSize]?.location.x === 1,
|
||||||
update: (item) => {
|
update: (item) => {
|
||||||
const screenSizeShape = item.shape[screenSize];
|
|
||||||
if (!screenSizeShape) {
|
|
||||||
throw new OldHomarrScreenSizeError("kind" in item ? "widget" : "app", item.id, screenSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.area = {
|
item.area = {
|
||||||
type: "wrapper",
|
type: "wrapper",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -272,6 +261,9 @@ const moveWidgetsAndAppsInRightSidebar = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const screenSizeShape = item.shape[screenSize];
|
||||||
|
if (!screenSizeShape) return;
|
||||||
|
|
||||||
screenSizeShape.location.x = 0;
|
screenSizeShape.location.x = 0;
|
||||||
screenSizeShape.location.y += offset;
|
screenSizeShape.location.y += offset;
|
||||||
},
|
},
|
||||||
@@ -312,16 +304,15 @@ const updateItems = (options: {
|
|||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const before = createItemSnapshot(item, options.screenSize);
|
const before = createItemSnapshot(item, options.screenSize);
|
||||||
|
|
||||||
|
options.update(item);
|
||||||
|
|
||||||
const screenSizeShape = item.shape[options.screenSize];
|
const screenSizeShape = item.shape[options.screenSize];
|
||||||
if (!screenSizeShape) {
|
if (!screenSizeShape) return requiredHeight;
|
||||||
throw new OldHomarrScreenSizeError("kind" in item ? "widget" : "app", item.id, options.screenSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeight) {
|
if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeight) {
|
||||||
requiredHeight = screenSizeShape.location.y + screenSizeShape.size.height;
|
requiredHeight = screenSizeShape.location.y + screenSizeShape.size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.update(item);
|
|
||||||
const after = createItemSnapshot(item, options.screenSize);
|
const after = createItemSnapshot(item, options.screenSize);
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|||||||
Reference in New Issue
Block a user