feat(boards): add responsive layout system (#2271)
This commit is contained in:
@@ -29,7 +29,7 @@ export const analyseOldmarrImportAsync = async (file: File) => {
|
||||
}
|
||||
|
||||
return {
|
||||
name: entry.name,
|
||||
name: entry.name.replace(".json", ""),
|
||||
config: result.data ?? null,
|
||||
isError: !result.success,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import SuperJSON from "superjson";
|
||||
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { boardSizes } from "@homarr/old-schema";
|
||||
|
||||
// We don't have access to the API client here, so we need to import it from the API package
|
||||
// In the future we should consider having the used router also in this package
|
||||
@@ -13,7 +12,6 @@ import type { AnalyseResult } from "../analyse/analyse-oldmarr-import";
|
||||
import { prepareMultipleImports } from "../prepare/prepare-multiple";
|
||||
import type { InitialOldmarrImportSettings } from "../settings";
|
||||
import { defaultSidebarBehaviour } from "../settings";
|
||||
import type { BoardSelectionMap, BoardSizeRecord } from "./initial/board-selection-card";
|
||||
import { BoardSelectionCard } from "./initial/board-selection-card";
|
||||
import { ImportSettingsCard } from "./initial/import-settings-card";
|
||||
import { ImportSummaryCard } from "./initial/import-summary-card";
|
||||
@@ -25,8 +23,8 @@ interface InitialOldmarrImportProps {
|
||||
}
|
||||
|
||||
export const InitialOldmarrImport = ({ file, analyseResult }: InitialOldmarrImportProps) => {
|
||||
const [boardSelections, setBoardSelections] = useState<BoardSelectionMap>(
|
||||
new Map(createDefaultSelections(analyseResult.configs)),
|
||||
const [boardSelections, setBoardSelections] = useState<Map<string, boolean>>(
|
||||
new Map(analyseResult.configs.filter(({ config }) => config !== null).map(({ name }) => [name, true])),
|
||||
);
|
||||
const [settings, setSettings] = useState<InitialOldmarrImportSettings>({
|
||||
onlyImportApps: false,
|
||||
@@ -94,19 +92,3 @@ export const InitialOldmarrImport = ({ file, analyseResult }: InitialOldmarrImpo
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const createDefaultSelections = (configs: AnalyseResult["configs"]) => {
|
||||
return configs
|
||||
.map(({ name, config }) => {
|
||||
if (!config) return null;
|
||||
|
||||
const shapes = config.apps.flatMap((app) => app.shape).concat(config.widgets.flatMap((widget) => widget.shape));
|
||||
const boardSizeRecord = boardSizes.reduce<BoardSizeRecord>((acc, size) => {
|
||||
const allInclude = shapes.every((shape) => Boolean(shape[size]));
|
||||
acc[size] = allInclude ? true : null;
|
||||
return acc;
|
||||
}, {} as BoardSizeRecord);
|
||||
return [name, boardSizeRecord];
|
||||
})
|
||||
.filter((selection): selection is [string, BoardSizeRecord] => Boolean(selection));
|
||||
};
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import type { ChangeEvent } from "react";
|
||||
import { Anchor, Card, Checkbox, Group, Stack, Text } from "@mantine/core";
|
||||
|
||||
import { objectEntries, objectKeys } from "@homarr/common";
|
||||
import { boardSizes } from "@homarr/old-schema";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
type BoardSize = (typeof boardSizes)[number];
|
||||
|
||||
export type BoardSizeRecord = Record<BoardSize, boolean | null>;
|
||||
export type BoardSelectionMap = Map<string, BoardSizeRecord>;
|
||||
export type BoardSelectionMap = Map<string, boolean>;
|
||||
|
||||
interface BoardSelectionCardProps {
|
||||
selections: BoardSelectionMap;
|
||||
@@ -16,12 +11,9 @@ interface BoardSelectionCardProps {
|
||||
}
|
||||
|
||||
const allChecked = (map: BoardSelectionMap) => {
|
||||
return [...map.values()].every((selection) => groupChecked(selection));
|
||||
return [...map.values()].every((selection) => selection);
|
||||
};
|
||||
|
||||
const groupChecked = (selection: BoardSizeRecord) =>
|
||||
objectEntries(selection).every(([_, value]) => value === true || value === null);
|
||||
|
||||
export const BoardSelectionCard = ({ selections, updateSelections }: BoardSelectionCardProps) => {
|
||||
const tBoardSelection = useScopedI18n("init.step.import.boardSelection");
|
||||
const t = useI18n();
|
||||
@@ -29,50 +21,14 @@ export const BoardSelectionCard = ({ selections, updateSelections }: BoardSelect
|
||||
|
||||
const handleToggleAll = () => {
|
||||
updateSelections((selections) => {
|
||||
const updated = new Map(selections);
|
||||
|
||||
[...selections.entries()].forEach(([name, selection]) => {
|
||||
objectKeys(selection).forEach((size) => {
|
||||
if (selection[size] === null) return;
|
||||
selection[size] = !areAllChecked;
|
||||
});
|
||||
|
||||
updated.set(name, selection);
|
||||
});
|
||||
|
||||
return updated;
|
||||
return new Map([...selections.keys()].map((name) => [name, !areAllChecked] as const));
|
||||
});
|
||||
};
|
||||
|
||||
const registerToggleGroup = (name: string) => (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const registerToggle = (name: string) => (event: ChangeEvent<HTMLInputElement>) => {
|
||||
updateSelections((selections) => {
|
||||
const updated = new Map(selections);
|
||||
const selection = selections.get(name);
|
||||
|
||||
if (!selection) return updated;
|
||||
|
||||
objectKeys(selection).forEach((size) => {
|
||||
if (selection[size] === null) return;
|
||||
selection[size] = event.target.checked;
|
||||
});
|
||||
|
||||
updated.set(name, selection);
|
||||
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
const registerToggle = (name: string, size: BoardSize) => (event: ChangeEvent<HTMLInputElement>) => {
|
||||
updateSelections((selections) => {
|
||||
const updated = new Map(selections);
|
||||
const selection = selections.get(name);
|
||||
|
||||
if (!selection) return updated;
|
||||
|
||||
selection[size] = event.target.checked;
|
||||
|
||||
updated.set(name, selection);
|
||||
|
||||
updated.set(name, event.target.checked);
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
@@ -100,53 +56,17 @@ export const BoardSelectionCard = ({ selections, updateSelections }: BoardSelect
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm">
|
||||
{[...selections.entries()].map(([name, selection]) => (
|
||||
{[...selections.entries()].map(([name, selected]) => (
|
||||
<Card key={name} withBorder>
|
||||
<Group justify="space-between" align="center" visibleFrom="md">
|
||||
<Checkbox
|
||||
checked={groupChecked(selection)}
|
||||
onChange={registerToggleGroup(name)}
|
||||
label={
|
||||
<Text fw={500} size="sm">
|
||||
{name}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<Group>
|
||||
{boardSizes.map((size) => (
|
||||
<Checkbox
|
||||
key={size}
|
||||
disabled={selection[size] === null}
|
||||
checked={selection[size] ?? undefined}
|
||||
onChange={registerToggle(name, size)}
|
||||
label={t(`board.action.oldImport.form.screenSize.option.${size}`)}
|
||||
/>
|
||||
))}
|
||||
</Group>
|
||||
</Group>
|
||||
<Stack hiddenFrom="md">
|
||||
<Checkbox
|
||||
checked={groupChecked(selection)}
|
||||
onChange={registerToggleGroup(name)}
|
||||
label={
|
||||
<Text fw={500} size="sm">
|
||||
{name}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<Stack gap="sm" ps="sm">
|
||||
{objectEntries(selection)
|
||||
.filter(([_, value]) => value !== null)
|
||||
.map(([size, value]) => (
|
||||
<Checkbox
|
||||
key={size}
|
||||
checked={value ?? undefined}
|
||||
onChange={registerToggle(name, size)}
|
||||
label={`screenSize.${size}`}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
onChange={registerToggle(name)}
|
||||
label={
|
||||
<Text fw={500} size="sm">
|
||||
{name}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
import { createId, inArray } from "@homarr/db";
|
||||
import type { Database, InferInsertModel, InferSelectModel } from "@homarr/db";
|
||||
import { apps as appsTable } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { OldmarrApp } from "@homarr/old-schema";
|
||||
|
||||
import type { BookmarkApp } from "./widgets/definitions/bookmark";
|
||||
|
||||
type DbAppWithoutId = Omit<InferSelectModel<typeof appsTable>, "id">;
|
||||
|
||||
interface AppMapping extends DbAppWithoutId {
|
||||
ids: string[];
|
||||
newId: string;
|
||||
exists: boolean;
|
||||
}
|
||||
|
||||
export const insertAppsAsync = async (
|
||||
db: Database,
|
||||
apps: OldmarrApp[],
|
||||
bookmarkApps: BookmarkApp[],
|
||||
distinctAppsByHref: boolean,
|
||||
configName: string,
|
||||
) => {
|
||||
logger.info(
|
||||
`Importing old homarr apps configuration=${configName} distinctAppsByHref=${distinctAppsByHref} apps=${apps.length}`,
|
||||
);
|
||||
|
||||
const existingAppsWithHref = distinctAppsByHref
|
||||
? await db.query.apps.findMany({
|
||||
where: inArray(appsTable.href, [
|
||||
...new Set(apps.map((app) => app.url).concat(bookmarkApps.map((app) => app.href))),
|
||||
]),
|
||||
})
|
||||
: [];
|
||||
|
||||
logger.debug(`Found existing apps with href count=${existingAppsWithHref.length}`);
|
||||
|
||||
// Generate mappings for all apps from old to new ids
|
||||
const appMappings: AppMapping[] = [];
|
||||
addMappingFor(apps, appMappings, existingAppsWithHref, convertApp);
|
||||
addMappingFor(bookmarkApps, appMappings, existingAppsWithHref, convertBookmarkApp);
|
||||
|
||||
logger.debug(`Mapping apps count=${appMappings.length}`);
|
||||
|
||||
const appsToCreate = appMappings
|
||||
.filter((app) => !app.exists)
|
||||
.map(
|
||||
(app) =>
|
||||
({
|
||||
id: app.newId,
|
||||
name: app.name,
|
||||
iconUrl: app.iconUrl,
|
||||
href: app.href,
|
||||
description: app.description,
|
||||
}) satisfies InferInsertModel<typeof appsTable>,
|
||||
);
|
||||
|
||||
logger.debug(`Creating apps count=${appsToCreate.length}`);
|
||||
|
||||
if (appsToCreate.length > 0) {
|
||||
await db.insert(appsTable).values(appsToCreate);
|
||||
}
|
||||
|
||||
logger.info(`Imported apps count=${appsToCreate.length}`);
|
||||
|
||||
// Generates a map from old key to new key for all apps
|
||||
return new Map(
|
||||
appMappings
|
||||
.map((app) => app.ids.map((id) => ({ id, newId: app.newId })))
|
||||
.flat()
|
||||
.map(({ id, newId }) => [id, newId]),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a callback to be used in a find method that compares the old app with the new app
|
||||
* @param app either an oldmarr app or a bookmark app
|
||||
* @param convertApp a function that converts the app to a new app
|
||||
* @returns a callback that compares the old app with the new app and returns true if they are the same
|
||||
*/
|
||||
const createFindCallback = <TApp extends OldmarrApp | BookmarkApp>(
|
||||
app: TApp,
|
||||
convertApp: (app: TApp) => DbAppWithoutId,
|
||||
) => {
|
||||
const oldApp = convertApp(app);
|
||||
|
||||
return (dbApp: DbAppWithoutId) =>
|
||||
oldApp.href === dbApp.href &&
|
||||
oldApp.name === dbApp.name &&
|
||||
oldApp.iconUrl === dbApp.iconUrl &&
|
||||
oldApp.description === dbApp.description;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds mappings for the given apps to the appMappings array
|
||||
* @param apps apps to add mappings for
|
||||
* @param appMappings existing app mappings
|
||||
* @param existingAppsWithHref existing apps with href
|
||||
* @param convertApp a function that converts the app to a new app
|
||||
*/
|
||||
const addMappingFor = <TApp extends OldmarrApp | BookmarkApp>(
|
||||
apps: TApp[],
|
||||
appMappings: AppMapping[],
|
||||
existingAppsWithHref: InferSelectModel<typeof appsTable>[],
|
||||
convertApp: (app: TApp) => DbAppWithoutId,
|
||||
) => {
|
||||
for (const app of apps) {
|
||||
const previous = appMappings.find(createFindCallback(app, convertApp));
|
||||
if (previous) {
|
||||
previous.ids.push(app.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = existingAppsWithHref.find(createFindCallback(app, convertApp));
|
||||
if (existing) {
|
||||
appMappings.push({
|
||||
ids: [app.id],
|
||||
newId: existing.id,
|
||||
name: existing.name,
|
||||
href: existing.href,
|
||||
iconUrl: existing.iconUrl,
|
||||
description: existing.description,
|
||||
pingUrl: existing.pingUrl,
|
||||
exists: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
appMappings.push({
|
||||
ids: [app.id],
|
||||
newId: createId(),
|
||||
...convertApp(app),
|
||||
exists: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an oldmarr app to a new app
|
||||
* @param app oldmarr app
|
||||
* @returns new app
|
||||
*/
|
||||
const convertApp = (app: OldmarrApp): DbAppWithoutId => ({
|
||||
name: app.name,
|
||||
href: app.behaviour.externalUrl === "" ? app.url : app.behaviour.externalUrl,
|
||||
iconUrl: app.appearance.iconUrl,
|
||||
description: app.behaviour.tooltipDescription ?? null,
|
||||
pingUrl: app.url.length > 0 ? app.url : null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts a bookmark app to a new app
|
||||
* @param app bookmark app
|
||||
* @returns new app
|
||||
*/
|
||||
const convertBookmarkApp = (app: BookmarkApp): DbAppWithoutId => ({
|
||||
...app,
|
||||
description: null,
|
||||
pingUrl: null,
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { Database } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import { boards } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { OldmarrConfig } from "@homarr/old-schema";
|
||||
|
||||
import { mapColor } from "./mappers/map-colors";
|
||||
import { mapColumnCount } from "./mappers/map-column-count";
|
||||
import type { OldmarrImportConfiguration } from "./settings";
|
||||
|
||||
export const insertBoardAsync = async (db: Database, old: OldmarrConfig, configuration: OldmarrImportConfiguration) => {
|
||||
logger.info(`Importing old homarr board configuration=${old.configProperties.name}`);
|
||||
const boardId = createId();
|
||||
await db.insert(boards).values({
|
||||
id: boardId,
|
||||
name: configuration.name,
|
||||
backgroundImageAttachment: old.settings.customization.backgroundImageAttachment,
|
||||
backgroundImageUrl: old.settings.customization.backgroundImageUrl,
|
||||
backgroundImageRepeat: old.settings.customization.backgroundImageRepeat,
|
||||
backgroundImageSize: old.settings.customization.backgroundImageSize,
|
||||
columnCount: mapColumnCount(old, configuration.screenSize),
|
||||
faviconImageUrl: old.settings.customization.faviconUrl,
|
||||
isPublic: old.settings.access.allowGuests,
|
||||
logoImageUrl: old.settings.customization.logoImageUrl,
|
||||
pageTitle: old.settings.customization.pageTitle,
|
||||
metaTitle: old.settings.customization.metaTitle,
|
||||
opacity: old.settings.customization.appOpacity,
|
||||
primaryColor: mapColor(old.settings.customization.colors.primary, "#fa5252"),
|
||||
secondaryColor: mapColor(old.settings.customization.colors.secondary, "#fd7e14"),
|
||||
});
|
||||
|
||||
logger.info(`Imported board id=${boardId}`);
|
||||
|
||||
return boardId;
|
||||
};
|
||||
@@ -1,6 +1,4 @@
|
||||
import type { OldmarrConfig } from "@homarr/old-schema";
|
||||
|
||||
import type { OldmarrImportConfiguration } from "./settings";
|
||||
import type { BoardSize, OldmarrConfig } from "@homarr/old-schema";
|
||||
|
||||
export class OldHomarrImportError extends Error {
|
||||
constructor(oldConfig: OldmarrConfig, cause: unknown) {
|
||||
@@ -11,7 +9,7 @@ export class OldHomarrImportError extends Error {
|
||||
}
|
||||
|
||||
export class OldHomarrScreenSizeError extends Error {
|
||||
constructor(type: "app" | "widget", id: string, screenSize: OldmarrImportConfiguration["screenSize"]) {
|
||||
constructor(type: "app" | "widget", id: string, screenSize: BoardSize) {
|
||||
super(`Screen size not found for type=${type} id=${id} screenSize=${screenSize}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import type { Database } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { OldmarrApp, OldmarrWidget } from "@homarr/old-schema";
|
||||
|
||||
import type { WidgetComponentProps } from "../../widgets/src/definition";
|
||||
import { OldHomarrScreenSizeError } from "./import-error";
|
||||
import type { OldmarrImportConfiguration } from "./settings";
|
||||
import { mapKind } from "./widgets/definitions";
|
||||
import { mapOptions } from "./widgets/options";
|
||||
|
||||
export const insertItemsAsync = async (
|
||||
db: Database,
|
||||
widgets: OldmarrWidget[],
|
||||
apps: OldmarrApp[],
|
||||
appsMap: Map<string, string>,
|
||||
sectionIdMaps: Map<string, string>,
|
||||
configuration: OldmarrImportConfiguration,
|
||||
) => {
|
||||
logger.info(`Importing old homarr items widgets=${widgets.length} apps=${apps.length}`);
|
||||
|
||||
for (const widget of widgets) {
|
||||
// All items should have been moved to the last wrapper
|
||||
if (widget.area.type === "sidebar") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const kind = mapKind(widget.type);
|
||||
|
||||
logger.debug(`Mapped widget kind id=${widget.id} previous=${widget.type} current=${kind}`);
|
||||
|
||||
if (!kind) {
|
||||
logger.error(`Widget has no kind id=${widget.id} type=${widget.type}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const sectionId = sectionIdMaps.get(widget.area.properties.id)!;
|
||||
|
||||
logger.debug(`Inserting widget id=${widget.id} sectionId=${sectionId}`);
|
||||
|
||||
const screenSizeShape = widget.shape[configuration.screenSize];
|
||||
if (!screenSizeShape) {
|
||||
throw new OldHomarrScreenSizeError("widget", widget.id, configuration.screenSize);
|
||||
}
|
||||
|
||||
await db.insert(items).values({
|
||||
id: createId(),
|
||||
sectionId,
|
||||
height: screenSizeShape.size.height,
|
||||
width: screenSizeShape.size.width,
|
||||
xOffset: screenSizeShape.location.x,
|
||||
yOffset: screenSizeShape.location.y,
|
||||
kind,
|
||||
options: SuperJSON.stringify(mapOptions(widget.type, widget.properties, appsMap)),
|
||||
});
|
||||
|
||||
logger.debug(`Inserted widget id=${widget.id} sectionId=${sectionId}`);
|
||||
}
|
||||
|
||||
for (const app of apps) {
|
||||
// All items should have been moved to the last wrapper
|
||||
if (app.area.type === "sidebar") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const sectionId = sectionIdMaps.get(app.area.properties.id)!;
|
||||
|
||||
logger.debug(`Inserting app name=${app.name} sectionId=${sectionId}`);
|
||||
|
||||
const screenSizeShape = app.shape[configuration.screenSize];
|
||||
if (!screenSizeShape) {
|
||||
throw new OldHomarrScreenSizeError("app", app.id, configuration.screenSize);
|
||||
}
|
||||
|
||||
await db.insert(items).values({
|
||||
id: createId(),
|
||||
sectionId,
|
||||
height: screenSizeShape.size.height,
|
||||
width: screenSizeShape.size.width,
|
||||
xOffset: screenSizeShape.location.x,
|
||||
yOffset: screenSizeShape.location.y,
|
||||
kind: "app",
|
||||
options: SuperJSON.stringify({
|
||||
// it's safe to assume that the app exists in the map
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
appId: appsMap.get(app.id)!,
|
||||
openInNewTab: app.behaviour.isOpeningNewTab,
|
||||
pingEnabled: app.network.enabledStatusChecker,
|
||||
showDescriptionTooltip: app.behaviour.tooltipDescription !== "",
|
||||
showTitle: app.appearance.appNameStatus === "normal",
|
||||
} satisfies WidgetComponentProps<"app">["options"]),
|
||||
});
|
||||
|
||||
logger.debug(`Inserted app name=${app.name} sectionId=${sectionId}`);
|
||||
}
|
||||
};
|
||||
@@ -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}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { boards } from "@homarr/db/schema";
|
||||
|
||||
import type { prepareMultipleImports } from "../prepare/prepare-multiple";
|
||||
import { mapColor } from "./map-colors";
|
||||
import { mapColumnCount } from "./map-column-count";
|
||||
|
||||
type PreparedBoard = ReturnType<typeof prepareMultipleImports>["preparedBoards"][number];
|
||||
|
||||
@@ -15,7 +14,6 @@ export const mapBoard = (preparedBoard: PreparedBoard): InferInsertModel<typeof
|
||||
backgroundImageUrl: preparedBoard.config.settings.customization.backgroundImageUrl,
|
||||
backgroundImageRepeat: preparedBoard.config.settings.customization.backgroundImageRepeat,
|
||||
backgroundImageSize: preparedBoard.config.settings.customization.backgroundImageSize,
|
||||
columnCount: mapColumnCount(preparedBoard.config, preparedBoard.size),
|
||||
faviconImageUrl: preparedBoard.config.settings.customization.faviconUrl,
|
||||
isPublic: preparedBoard.config.settings.access.allowGuests,
|
||||
logoImageUrl: preparedBoard.config.settings.customization.logoImageUrl,
|
||||
|
||||
18
packages/old-import/src/mappers/map-breakpoint.ts
Normal file
18
packages/old-import/src/mappers/map-breakpoint.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { BoardSize } from "@homarr/old-schema";
|
||||
|
||||
/**
|
||||
* Copied from https://github.com/ajnart/homarr/blob/274eaa92084a8be4d04a69a87f9920860a229128/src/components/Dashboard/Wrappers/gridstack/store.tsx#L21-L30
|
||||
* @param screenSize board size
|
||||
* @returns layout breakpoint for the board
|
||||
*/
|
||||
export const mapBreakpoint = (screenSize: BoardSize) => {
|
||||
switch (screenSize) {
|
||||
case "lg":
|
||||
return 1400;
|
||||
case "md":
|
||||
return 800;
|
||||
case "sm":
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { OldmarrConfig } from "@homarr/old-schema";
|
||||
import type { BoardSize, OldmarrConfig } from "@homarr/old-schema";
|
||||
|
||||
import type { OldmarrImportConfiguration } from "../settings";
|
||||
|
||||
export const mapColumnCount = (old: OldmarrConfig, screenSize: OldmarrImportConfiguration["screenSize"]) => {
|
||||
export const mapColumnCount = (old: OldmarrConfig, screenSize: BoardSize) => {
|
||||
switch (screenSize) {
|
||||
case "lg":
|
||||
return old.settings.customization.gridstack.columnCountLarge;
|
||||
|
||||
@@ -2,9 +2,10 @@ import SuperJSON from "superjson";
|
||||
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import type { items } from "@homarr/db/schema";
|
||||
import type { itemLayouts, items } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { BoardSize, OldmarrApp, OldmarrWidget } from "@homarr/old-schema";
|
||||
import { boardSizes } from "@homarr/old-schema";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../widgets/src/definition";
|
||||
import { mapKind } from "../widgets/definitions";
|
||||
@@ -12,30 +13,23 @@ import { mapOptions } from "../widgets/options";
|
||||
|
||||
export const mapApp = (
|
||||
app: OldmarrApp,
|
||||
boardSize: BoardSize,
|
||||
appsMap: Map<string, { id: string }>,
|
||||
sectionMap: Map<string, { id: string }>,
|
||||
): InferInsertModel<typeof items> | null => {
|
||||
layoutMap: Record<BoardSize, string>,
|
||||
boardId: string,
|
||||
): (InferInsertModel<typeof items> & { layouts: InferInsertModel<typeof itemLayouts>[] }) | null => {
|
||||
if (app.area.type === "sidebar") throw new Error("Mapping app in sidebar is not supported");
|
||||
|
||||
const shapeForSize = app.shape[boardSize];
|
||||
if (!shapeForSize) {
|
||||
throw new Error(`Failed to find a shape for appId='${app.id}' screenSize='${boardSize}'`);
|
||||
}
|
||||
|
||||
const sectionId = sectionMap.get(app.area.properties.id)?.id;
|
||||
if (!sectionId) {
|
||||
logger.warn(`Failed to find section for app appId='${app.id}' sectionId='${app.area.properties.id}'. Removing app`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const itemId = createId();
|
||||
return {
|
||||
id: createId(),
|
||||
sectionId,
|
||||
height: shapeForSize.size.height,
|
||||
width: shapeForSize.size.width,
|
||||
xOffset: shapeForSize.location.x,
|
||||
yOffset: shapeForSize.location.y,
|
||||
id: itemId,
|
||||
boardId,
|
||||
kind: "app",
|
||||
options: SuperJSON.stringify({
|
||||
// it's safe to assume that the app exists in the map
|
||||
@@ -46,22 +40,34 @@ export const mapApp = (
|
||||
showDescriptionTooltip: app.behaviour.tooltipDescription !== "",
|
||||
showTitle: app.appearance.appNameStatus === "normal",
|
||||
} satisfies WidgetComponentProps<"app">["options"]),
|
||||
layouts: boardSizes.map((size) => {
|
||||
const shapeForSize = app.shape[size];
|
||||
if (!shapeForSize) {
|
||||
throw new Error(`Failed to find a shape for appId='${app.id}' screenSize='${size}'`);
|
||||
}
|
||||
|
||||
return {
|
||||
itemId,
|
||||
height: shapeForSize.size.height,
|
||||
width: shapeForSize.size.width,
|
||||
xOffset: shapeForSize.location.x,
|
||||
yOffset: shapeForSize.location.y,
|
||||
sectionId,
|
||||
layoutId: layoutMap[size],
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapWidget = (
|
||||
widget: OldmarrWidget,
|
||||
boardSize: BoardSize,
|
||||
appsMap: Map<string, { id: string }>,
|
||||
sectionMap: Map<string, { id: string }>,
|
||||
): InferInsertModel<typeof items> | null => {
|
||||
layoutMap: Record<BoardSize, string>,
|
||||
boardId: string,
|
||||
): (InferInsertModel<typeof items> & { layouts: InferInsertModel<typeof itemLayouts>[] }) | null => {
|
||||
if (widget.area.type === "sidebar") throw new Error("Mapping widget in sidebar is not supported");
|
||||
|
||||
const shapeForSize = widget.shape[boardSize];
|
||||
if (!shapeForSize) {
|
||||
throw new Error(`Failed to find a shape for widgetId='${widget.id}' screenSize='${boardSize}'`);
|
||||
}
|
||||
|
||||
const kind = mapKind(widget.type);
|
||||
if (!kind) {
|
||||
logger.warn(`Failed to map widget type='${widget.type}'. It's no longer supported`);
|
||||
@@ -76,13 +82,10 @@ export const mapWidget = (
|
||||
return null;
|
||||
}
|
||||
|
||||
const itemId = createId();
|
||||
return {
|
||||
id: createId(),
|
||||
sectionId,
|
||||
height: shapeForSize.size.height,
|
||||
width: shapeForSize.size.width,
|
||||
xOffset: shapeForSize.location.x,
|
||||
yOffset: shapeForSize.location.y,
|
||||
id: itemId,
|
||||
boardId,
|
||||
kind,
|
||||
options: SuperJSON.stringify(
|
||||
mapOptions(
|
||||
@@ -91,5 +94,21 @@ export const mapWidget = (
|
||||
new Map([...appsMap.entries()].map(([key, value]) => [key, value.id])),
|
||||
),
|
||||
),
|
||||
layouts: boardSizes.map((size) => {
|
||||
const shapeForSize = widget.shape[size];
|
||||
if (!shapeForSize) {
|
||||
throw new Error(`Failed to find a shape for widgetId='${widget.id}' screenSize='${size}'`);
|
||||
}
|
||||
|
||||
return {
|
||||
itemId,
|
||||
height: shapeForSize.size.height,
|
||||
width: shapeForSize.size.width,
|
||||
xOffset: shapeForSize.location.x,
|
||||
yOffset: shapeForSize.location.y,
|
||||
sectionId,
|
||||
layoutId: layoutMap[size],
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { OldmarrApp, OldmarrConfig, OldmarrWidget } from "@homarr/old-schema";
|
||||
import type { BoardSize, OldmarrApp, OldmarrConfig, OldmarrWidget } from "@homarr/old-schema";
|
||||
import { boardSizes } from "@homarr/old-schema";
|
||||
|
||||
import { OldHomarrScreenSizeError } from "./import-error";
|
||||
import { mapColumnCount } from "./mappers/map-column-count";
|
||||
@@ -28,9 +29,21 @@ export const moveWidgetsAndAppsIfMerge = (
|
||||
|
||||
logger.debug(`Merging wrappers at the end of the board count=${wrapperIdsToMerge.length}`);
|
||||
|
||||
let offset = 0;
|
||||
const offsets = boardSizes.reduce(
|
||||
(previous, screenSize) => {
|
||||
previous[screenSize] = 0;
|
||||
return previous;
|
||||
},
|
||||
{} as Record<BoardSize, number>,
|
||||
);
|
||||
for (const id of wrapperIdsToMerge) {
|
||||
let requiredHeight = 0;
|
||||
const requiredHeights = boardSizes.reduce(
|
||||
(previous, screenSize) => {
|
||||
previous[screenSize] = 0;
|
||||
return previous;
|
||||
},
|
||||
{} as Record<BoardSize, number>,
|
||||
);
|
||||
const affected = affectedMap.get(id);
|
||||
if (!affected) {
|
||||
continue;
|
||||
@@ -44,18 +57,20 @@ export const moveWidgetsAndAppsIfMerge = (
|
||||
// Move item to first wrapper
|
||||
app.area.properties.id = firstId;
|
||||
|
||||
const screenSizeShape = app.shape[configuration.screenSize];
|
||||
if (!screenSizeShape) {
|
||||
throw new OldHomarrScreenSizeError("app", app.id, configuration.screenSize);
|
||||
}
|
||||
for (const screenSize of boardSizes) {
|
||||
const screenSizeShape = app.shape[screenSize];
|
||||
if (!screenSizeShape) {
|
||||
throw new OldHomarrScreenSizeError("app", app.id, screenSize);
|
||||
}
|
||||
|
||||
// Find the highest widget in the wrapper to increase the offset accordingly
|
||||
if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeight) {
|
||||
requiredHeight = screenSizeShape.location.y + screenSizeShape.size.height;
|
||||
}
|
||||
// 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 += offset;
|
||||
// Move item down as much as needed to not overlap with other items
|
||||
screenSizeShape.location.y += offsets[screenSize];
|
||||
}
|
||||
}
|
||||
|
||||
for (const widget of widgets) {
|
||||
@@ -63,21 +78,25 @@ export const moveWidgetsAndAppsIfMerge = (
|
||||
// Move item to first wrapper
|
||||
widget.area.properties.id = firstId;
|
||||
|
||||
const screenSizeShape = widget.shape[configuration.screenSize];
|
||||
if (!screenSizeShape) {
|
||||
throw new OldHomarrScreenSizeError("widget", widget.id, configuration.screenSize);
|
||||
}
|
||||
for (const screenSize of boardSizes) {
|
||||
const screenSizeShape = widget.shape[screenSize];
|
||||
if (!screenSizeShape) {
|
||||
throw new OldHomarrScreenSizeError("widget", widget.id, screenSize);
|
||||
}
|
||||
|
||||
// Find the highest widget in the wrapper to increase the offset accordingly
|
||||
if (screenSizeShape.location.y + screenSizeShape.size.height > requiredHeight) {
|
||||
requiredHeight = screenSizeShape.location.y + screenSizeShape.size.height;
|
||||
}
|
||||
// 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 += offset;
|
||||
// Move item down as much as needed to not overlap with other items
|
||||
screenSizeShape.location.y += offsets[screenSize];
|
||||
}
|
||||
}
|
||||
|
||||
offset += requiredHeight;
|
||||
for (const screenSize of boardSizes) {
|
||||
offsets[screenSize] += requiredHeights[screenSize];
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.sidebarBehaviour === "last-section") {
|
||||
@@ -86,14 +105,18 @@ export const moveWidgetsAndAppsIfMerge = (
|
||||
old.settings.customization.layout.enabledLeftSidebar ||
|
||||
areas.some((area) => area.type === "sidebar" && area.properties.location === "left")
|
||||
) {
|
||||
offset = moveWidgetsAndAppsInLeftSidebar(old, firstId, offset, configuration.screenSize);
|
||||
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")
|
||||
) {
|
||||
moveWidgetsAndAppsInRightSidebar(old, firstId, offset, configuration.screenSize);
|
||||
for (const screenSize of boardSizes) {
|
||||
moveWidgetsAndAppsInRightSidebar(old, firstId, offsets[screenSize], screenSize);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove all widgets and apps in the sidebar
|
||||
@@ -110,7 +133,7 @@ const moveWidgetsAndAppsInLeftSidebar = (
|
||||
old: OldmarrConfig,
|
||||
firstId: string,
|
||||
offset: number,
|
||||
screenSize: OldmarrImportConfiguration["screenSize"],
|
||||
screenSize: BoardSize,
|
||||
) => {
|
||||
const columnCount = mapColumnCount(old, screenSize);
|
||||
let requiredHeight = updateItems({
|
||||
@@ -186,7 +209,7 @@ const moveWidgetsAndAppsInRightSidebar = (
|
||||
old: OldmarrConfig,
|
||||
firstId: string,
|
||||
offset: number,
|
||||
screenSize: OldmarrImportConfiguration["screenSize"],
|
||||
screenSize: BoardSize,
|
||||
) => {
|
||||
const columnCount = mapColumnCount(old, screenSize);
|
||||
const xOffsetDelta = Math.max(columnCount - 2, 0);
|
||||
@@ -255,10 +278,7 @@ const moveWidgetsAndAppsInRightSidebar = (
|
||||
});
|
||||
};
|
||||
|
||||
const createItemSnapshot = (
|
||||
item: OldmarrApp | OldmarrWidget,
|
||||
screenSize: OldmarrImportConfiguration["screenSize"],
|
||||
) => ({
|
||||
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,
|
||||
@@ -285,7 +305,7 @@ const updateItems = (options: {
|
||||
items: (OldmarrApp | OldmarrWidget)[];
|
||||
filter: (item: OldmarrApp | OldmarrWidget) => boolean;
|
||||
update: (item: OldmarrApp | OldmarrWidget) => void;
|
||||
screenSize: OldmarrImportConfiguration["screenSize"];
|
||||
screenSize: BoardSize;
|
||||
}) => {
|
||||
const items = options.items.filter(options.filter);
|
||||
let requiredHeight = 0;
|
||||
|
||||
@@ -1,34 +1,6 @@
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import type { BoardSize } from "@homarr/old-schema";
|
||||
|
||||
import type { ValidAnalyseConfig } from "../analyse/types";
|
||||
import type { BoardSelectionMap } from "../components/initial/board-selection-card";
|
||||
|
||||
const boardSizeSuffix: Record<BoardSize, string> = {
|
||||
lg: "large",
|
||||
md: "medium",
|
||||
sm: "small",
|
||||
};
|
||||
|
||||
export const createBoardName = (fileName: string, boardSize: BoardSize) => {
|
||||
return `${fileName.replace(".json", "")}-${boardSizeSuffix[boardSize]}`;
|
||||
};
|
||||
|
||||
export const prepareBoards = (analyseConfigs: ValidAnalyseConfig[], selections: BoardSelectionMap) => {
|
||||
return analyseConfigs.flatMap(({ name, config }) => {
|
||||
const selectedSizes = selections.get(name);
|
||||
if (!selectedSizes) return [];
|
||||
|
||||
return objectEntries(selectedSizes)
|
||||
.map(([size, selected]) => {
|
||||
if (!selected) return null;
|
||||
|
||||
return {
|
||||
name: createBoardName(name, size),
|
||||
size,
|
||||
config,
|
||||
};
|
||||
})
|
||||
.filter((board) => board !== null);
|
||||
});
|
||||
return analyseConfigs.filter(({ name }) => selections.get(name));
|
||||
};
|
||||
|
||||
@@ -4,11 +4,12 @@ import { mapApp, mapWidget } from "../mappers/map-item";
|
||||
|
||||
export const prepareItems = (
|
||||
{ apps, widgets }: Pick<OldmarrConfig, "apps" | "widgets">,
|
||||
boardSize: BoardSize,
|
||||
appsMap: Map<string, { id: string }>,
|
||||
sectionMap: Map<string, { id: string }>,
|
||||
layoutMap: Record<BoardSize, string>,
|
||||
boardId: string,
|
||||
) =>
|
||||
widgets
|
||||
.map((widget) => mapWidget(widget, boardSize, appsMap, sectionMap))
|
||||
.concat(apps.map((app) => mapApp(app, boardSize, appsMap, sectionMap)))
|
||||
.map((widget) => mapWidget(widget, appsMap, sectionMap, layoutMap, boardId))
|
||||
.concat(apps.map((app) => mapApp(app, appsMap, sectionMap, layoutMap, boardId)))
|
||||
.filter((widget) => widget !== null);
|
||||
|
||||
@@ -8,14 +8,6 @@ export const prepareSingleImport = (config: OldmarrConfig, settings: OldmarrImpo
|
||||
|
||||
return {
|
||||
preparedApps: prepareApps(validAnalyseConfigs),
|
||||
preparedBoards: settings.onlyImportApps
|
||||
? []
|
||||
: [
|
||||
{
|
||||
name: settings.name,
|
||||
size: settings.screenSize,
|
||||
config,
|
||||
},
|
||||
],
|
||||
preparedBoards: settings.onlyImportApps ? [] : validAnalyseConfigs,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { zfd } from "zod-form-data";
|
||||
|
||||
import { boardSizes } from "@homarr/old-schema";
|
||||
import { validation, zodEnumFromArray } from "@homarr/validation";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { createCustomErrorParams } from "@homarr/validation/form";
|
||||
|
||||
export const sidebarBehaviours = ["remove-items", "last-section"] as const;
|
||||
@@ -12,7 +11,6 @@ export type SidebarBehaviour = (typeof sidebarBehaviours)[number];
|
||||
export const oldmarrImportConfigurationSchema = z.object({
|
||||
name: validation.board.name,
|
||||
onlyImportApps: z.boolean().default(false),
|
||||
screenSize: zodEnumFromArray(boardSizes).default("lg"),
|
||||
sidebarBehaviour: z.enum(sidebarBehaviours).default(defaultSidebarBehaviour),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user