Files
homarr/packages/old-import/src/prepare/prepare-sections.ts
T

116 lines
3.6 KiB
TypeScript

import { createId } from "@homarr/common";
import type { InferInsertModel } from "@homarr/db";
import type { itemLayouts, sectionLayouts, sections } from "@homarr/db/schema";
import type { OldmarrConfig } from "@homarr/old-schema";
import { mapLegacyCategoryContainer, mapMainRootSection } from "../mappers/map-section";
interface PreparedSectionBlock {
id: string;
legacyId: string;
position: number;
type: "container" | "root-content";
}
interface PreparedSections {
blocks: PreparedSectionBlock[];
byLegacyId: Map<string, { id: string }>;
mainRootId: string;
sections: InferInsertModel<typeof sections>[];
}
interface PreparedItemWithLayouts {
layouts: InferInsertModel<typeof itemLayouts>[];
}
export const prepareSections = (
boardId: string,
{ categories, wrappers }: Pick<OldmarrConfig, "categories" | "wrappers">,
): PreparedSections => {
const mainRoot = mapMainRootSection(boardId);
const containers = categories.map((category) => ({
category,
section: mapLegacyCategoryContainer(boardId, category),
}));
const rootContent = wrappers.map((wrapper) => ({ wrapper, id: createId() }));
return {
mainRootId: mainRoot.id,
sections: [mainRoot, ...containers.map(({ section }) => section)],
byLegacyId: new Map([
...containers.map(({ category, section }) => [category.id, { id: section.id }] as const),
...rootContent.map(({ wrapper, id }) => [wrapper.id, { id }] as const),
]),
blocks: [
...containers.map(({ category, section }) => ({
id: section.id,
legacyId: category.id,
position: category.position,
type: "container" as const,
})),
...rootContent.map(({ wrapper, id }) => ({
id,
legacyId: wrapper.id,
position: wrapper.position,
type: "root-content" as const,
})),
].toSorted(
(first, second) =>
first.position - second.position ||
(first.type === second.type
? first.legacyId.localeCompare(second.legacyId)
: first.type === "container"
? -1
: 1),
),
};
};
/**
* Old Homarr stored each top-level block in its own coordinate space. The
* current model has one main root and places labelled containers inside it.
* This compacts those blocks in their legacy order without changing the
* coordinates of content inside a container.
*/
export const placePreparedSections = (
preparedSections: PreparedSections,
preparedItems: PreparedItemWithLayouts[],
preparedLayouts: { id: string; columnCount: number }[],
): InferInsertModel<typeof sectionLayouts>[] => {
const result: InferInsertModel<typeof sectionLayouts>[] = [];
for (const layout of preparedLayouts) {
let nextRow = 0;
for (const block of preparedSections.blocks) {
const content = preparedItems.flatMap((item) =>
item.layouts.filter((itemLayout) => itemLayout.layoutId === layout.id && itemLayout.sectionId === block.id),
);
const contentHeight = Math.max(0, ...content.map((itemLayout) => itemLayout.yOffset + itemLayout.height));
if (block.type === "container") {
const height = Math.max(1, contentHeight);
result.push({
sectionId: block.id,
layoutId: layout.id,
parentSectionId: preparedSections.mainRootId,
xOffset: 0,
yOffset: nextRow,
width: layout.columnCount,
height,
});
nextRow += height;
continue;
}
for (const itemLayout of content) {
itemLayout.sectionId = preparedSections.mainRootId;
itemLayout.yOffset += nextRow;
}
nextRow += contentHeight;
}
}
return result;
};