feat(boards): add responsive layout system (#2271)

This commit is contained in:
Meier Lukas
2025-02-23 17:34:56 +01:00
committed by GitHub
parent 2085b5ece2
commit 7761dc29c8
98 changed files with 11770 additions and 1694 deletions

View File

@@ -1,20 +1,31 @@
import { createId } from "@homarr/db";
import { createDbInsertCollectionForTransaction } from "@homarr/db/collection";
import { logger } from "@homarr/log";
import type { BoardSize } from "@homarr/old-schema";
import { boardSizes, getBoardSizeName } from "@homarr/old-schema";
import { fixSectionIssues } from "../../fix-section-issues";
import { mapBoard } from "../../mappers/map-board";
import { mapBreakpoint } from "../../mappers/map-breakpoint";
import { mapColumnCount } from "../../mappers/map-column-count";
import { moveWidgetsAndAppsIfMerge } from "../../move-widgets-and-apps-merge";
import { prepareItems } from "../../prepare/prepare-items";
import type { prepareMultipleImports } from "../../prepare/prepare-multiple";
import { prepareSections } from "../../prepare/prepare-sections";
import type { InitialOldmarrImportSettings } from "../../settings";
import { createDbInsertCollection } from "./common";
export const createBoardInsertCollection = (
{ preparedApps, preparedBoards }: Omit<ReturnType<typeof prepareMultipleImports>, "preparedIntegrations">,
settings: InitialOldmarrImportSettings,
) => {
const insertCollection = createDbInsertCollection(["apps", "boards", "sections", "items"]);
const insertCollection = createDbInsertCollectionForTransaction([
"apps",
"boards",
"layouts",
"sections",
"items",
"itemLayouts",
]);
logger.info("Preparing boards for insert collection");
const appsMap = new Map(
@@ -49,7 +60,6 @@ export const createBoardInsertCollection = (
const { wrappers, categories, wrapperIdsToMerge } = fixSectionIssues(board.config);
const { apps, widgets } = moveWidgetsAndAppsIfMerge(board.config, wrapperIdsToMerge, {
...settings,
screenSize: board.size,
name: board.name,
});
@@ -58,6 +68,25 @@ export const createBoardInsertCollection = (
const mappedBoard = mapBoard(board);
logger.debug(`Mapped board fileName=${board.name} boardId=${mappedBoard.id}`);
insertCollection.boards.push(mappedBoard);
const layoutMapping = boardSizes.reduce(
(acc, size) => {
acc[size] = createId();
return acc;
},
{} as Record<BoardSize, string>,
);
insertCollection.layouts.push(
...boardSizes.map((size) => ({
id: layoutMapping[size],
boardId: mappedBoard.id,
columnCount: mapColumnCount(board.config, size),
breakpoint: mapBreakpoint(size),
name: getBoardSizeName(size),
})),
);
const preparedSections = prepareSections(mappedBoard.id, { wrappers, categories });
for (const section of preparedSections.values()) {
@@ -65,8 +94,11 @@ export const createBoardInsertCollection = (
}
logger.debug(`Added sections to board insert collection count=${insertCollection.sections.length}`);
const preparedItems = prepareItems({ apps, widgets }, board.size, appsMap, preparedSections);
preparedItems.forEach((item) => insertCollection.items.push(item));
const preparedItems = prepareItems({ apps, widgets }, appsMap, preparedSections, layoutMapping, mappedBoard.id);
preparedItems.forEach(({ layouts, ...item }) => {
insertCollection.items.push(item);
insertCollection.itemLayouts.push(...layouts);
});
logger.debug(`Added items to board insert collection count=${insertCollection.items.length}`);
});

View File

@@ -1,43 +0,0 @@
import { objectEntries } from "@homarr/common";
import type { Database, HomarrDatabaseMysql, InferInsertModel } from "@homarr/db";
import * as schema from "@homarr/db/schema";
type TableKey = {
[K in keyof typeof schema]: (typeof schema)[K] extends { _: { brand: "Table" } } ? K : never;
}[keyof typeof schema];
export const createDbInsertCollection = <TTableKey extends TableKey>(tablesInInsertOrder: TTableKey[]) => {
const context = tablesInInsertOrder.reduce(
(acc, key) => {
acc[key] = [];
return acc;
},
{} as { [K in TTableKey]: InferInsertModel<(typeof schema)[K]>[] },
);
return {
...context,
insertAll: (db: Database) => {
db.transaction((transaction) => {
for (const [key, values] of objectEntries(context)) {
if (values.length >= 1) {
transaction
.insert(schema[key])
.values(values as never)
.run();
}
}
});
},
insertAllAsync: async (db: HomarrDatabaseMysql) => {
await db.transaction(async (transaction) => {
for (const [key, values] of objectEntries(context)) {
if (values.length >= 1) {
// Below is actually the mysqlSchema when the driver is mysql
await transaction.insert(schema[key] as never).values(values as never);
}
}
});
},
};
};

View File

@@ -1,15 +1,15 @@
import { encryptSecret } from "@homarr/common/server";
import { createDbInsertCollectionForTransaction } from "@homarr/db/collection";
import { logger } from "@homarr/log";
import { mapAndDecryptIntegrations } from "../../mappers/map-integration";
import type { PreparedIntegration } from "../../prepare/prepare-integrations";
import { createDbInsertCollection } from "./common";
export const createIntegrationInsertCollection = (
preparedIntegrations: PreparedIntegration[],
encryptionToken: string | null | undefined,
) => {
const insertCollection = createDbInsertCollection(["integrations", "integrationSecrets"]);
const insertCollection = createDbInsertCollectionForTransaction(["integrations", "integrationSecrets"]);
if (preparedIntegrations.length === 0) {
return insertCollection;

View File

@@ -1,16 +1,21 @@
import { createId } from "@homarr/db";
import { createDbInsertCollectionForTransaction } from "@homarr/db/collection";
import { credentialsAdminGroup } from "@homarr/definitions";
import { logger } from "@homarr/log";
import { mapAndDecryptUsers } from "../../mappers/map-user";
import type { OldmarrImportUser } from "../../user-schema";
import { createDbInsertCollection } from "./common";
export const createUserInsertCollection = (
importUsers: OldmarrImportUser[],
encryptionToken: string | null | undefined,
) => {
const insertCollection = createDbInsertCollection(["users", "groups", "groupMembers", "groupPermissions"]);
const insertCollection = createDbInsertCollectionForTransaction([
"users",
"groups",
"groupMembers",
"groupPermissions",
]);
if (importUsers.length === 0) {
return insertCollection;

View File

@@ -4,14 +4,7 @@ import { zfd } from "zod-form-data";
import { initialOldmarrImportSettings } from "../settings";
const boardSelectionMapSchema = z.map(
z.string(),
z.object({
sm: z.boolean().nullable(),
md: z.boolean().nullable(),
lg: z.boolean().nullable(),
}),
);
const boardSelectionMapSchema = z.map(z.string(), z.boolean());
export const importInitialOldmarrInputSchema = zfd.formData({
file: zfd.file(),