fix: address PR review feedback

- Show error notification on setup failure instead of silent reset
- Fix toggleKind stale closure by using prop array directly
- Remove duplicate translations from en-gb.json (falls back to en.json)
- Align seed board columnCount to 12 (codebase default)
- Extract shared placeAllWidgetsAsync helper to @homarr/db/queries
- Remove redundant onboardingFormSchema; reuse formSchema for both paths
- Fix lint errors (optional chain, unused imports, unnecessary conditions)
This commit is contained in:
Thomas Camlong committed 2026-05-27 15:53:14 +02:00
1 parent c97513a61c
commit a6566f74c9
15 files changed
+233 -338

No files matched your search

+9 -108
View File
@@ -1,25 +1,19 @@
import superjson from "superjson";
import { createId, objectKeys } from "@homarr/common";
import {
createDocumentationLink,
credentialsAdminGroup,
defaultBookmarkApps,
defaultWidgetConfigs,
emptySuperJSON,
everyoneGroup,
getIntegrationDefaultUrl,
getIntegrationName,
getWidgetKindsForIntegration,
integrationDefs,
integrationKinds,
} from "@homarr/definitions";
import type { WidgetKind } from "@homarr/definitions";
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
import type { Database } from "..";
import { eq } from "..";
import { getMaxGroupPositionAsync } from "../queries";
import { getMaxGroupPositionAsync, placeAllWidgetsAsync } from "../queries";
import {
getServerSettingByKeyAsync,
insertServerSettingByKeyAsync,
@@ -31,9 +25,7 @@ import {
groupMembers,
groupPermissions,
groups,
integrationItems,
integrations,
itemLayouts,
items,
layouts,
onboarding,
@@ -265,7 +257,7 @@ const seedDefaultBoardAsync = async (db: Database) => {
await db.insert(layouts).values({
id: createId(),
name: "Base",
columnCount: 10,
columnCount: 12,
breakpoint: 0,
boardId,
});
@@ -351,103 +343,12 @@ const seedBoardWidgetsAsync = async (db: Database) => {
const allIntegrations = await db.query.integrations.findMany();
const allApps = await db.query.apps.findMany();
const ctx = {
xOffset: 0,
yOffset: 0,
rowMaxHeight: 0,
columnCount: layout.columnCount,
};
const count = await placeAllWidgetsAsync(
db,
{ boardId: board.id, sectionId: section.id, layoutId: layout.id, columnCount: layout.columnCount },
allIntegrations,
allApps,
);
const placedWidgets = new Set<WidgetKind>();
const widgetConfigMap = new Map(defaultWidgetConfigs.map((cfg) => [cfg.kind, cfg]));
const placeWidgetAsync = async (
kind: WidgetKind,
linkedIntegrationIds: string[],
options?: string,
size?: { width: number; height: number },
) => {
const width = size?.width ?? 2;
const height = size?.height ?? 2;
if (ctx.xOffset + width > ctx.columnCount) {
ctx.xOffset = 0;
ctx.yOffset += ctx.rowMaxHeight;
ctx.rowMaxHeight = 0;
}
const itemId = createId();
await db.insert(items).values({
id: itemId,
boardId: board.id,
kind,
options: options ?? emptySuperJSON,
advancedOptions: emptySuperJSON,
});
await db.insert(itemLayouts).values({
itemId,
sectionId: section.id,
layoutId: layout.id,
xOffset: ctx.xOffset,
yOffset: ctx.yOffset,
width,
height,
});
for (const integrationId of linkedIntegrationIds) {
await db.insert(integrationItems).values({ itemId, integrationId });
}
ctx.rowMaxHeight = Math.max(ctx.rowMaxHeight, height);
ctx.xOffset += width;
};
for (const integration of allIntegrations) {
for (const widgetKind of getWidgetKindsForIntegration(integration.kind)) {
if (placedWidgets.has(widgetKind)) continue;
const config = widgetConfigMap.get(widgetKind);
if (config?.skip) continue;
placedWidgets.add(widgetKind);
const matchingIds = allIntegrations
.filter((row) => getWidgetKindsForIntegration(row.kind).includes(widgetKind))
.map((row) => row.id);
const options = config?.options ? superjson.stringify(config.options) : undefined;
await placeWidgetAsync(
widgetKind,
matchingIds,
options,
config && { width: config.width, height: config.height },
);
}
}
for (const app of allApps) {
await placeWidgetAsync("app", [], superjson.stringify({ appId: app.id, openInNewTab: true, showTitle: true }), {
width: 1,
height: 1,
});
}
const bookmarkAppNames = new Set(defaultBookmarkApps.map((bookmark) => bookmark.name));
const bookmarkAppIds = allApps.filter((app) => bookmarkAppNames.has(app.name)).map((app) => app.id);
for (const config of defaultWidgetConfigs) {
if (config.skip || placedWidgets.has(config.kind)) continue;
placedWidgets.add(config.kind);
let options = config.options ? { ...config.options } : undefined;
if (config.kind === "bookmarks" && bookmarkAppIds.length > 0) {
options = { ...options, items: bookmarkAppIds };
}
await placeWidgetAsync(config.kind, [], options ? superjson.stringify(options) : undefined, {
width: config.width,
height: config.height,
});
}
console.log(`Placed ${placedWidgets.size + allApps.length} widgets on board`);
console.log(`Placed ${count} widgets on board`);
};
+1
View File
@@ -1,3 +1,4 @@
export * from "./item";
export * from "./server-setting";
export * from "./group";
export * from "./widget-placement";
+148
View File
@@ -0,0 +1,148 @@
import superjson from "superjson";
import { createId } from "@homarr/common";
import {
defaultBookmarkApps,
defaultWidgetConfigs,
emptySuperJSON,
getWidgetKindsForIntegration,
} from "@homarr/definitions";
import type { WidgetKind } from "@homarr/definitions";
import type { Database } from "..";
import { integrationItems, itemLayouts, items } from "../schema";
import type { Integration } from "../schema";
const widgetConfigMap = new Map(defaultWidgetConfigs.map((config) => [config.kind, config]));
interface BoardTarget {
boardId: string;
sectionId: string;
layoutId: string;
columnCount: number;
}
interface PlacementContext extends BoardTarget {
xOffset: number;
yOffset: number;
rowMaxHeight: number;
}
const placeWidgetAsync = async (
db: Database,
ctx: PlacementContext,
kind: WidgetKind,
linkedIntegrationIds: string[],
options?: string,
size?: { width: number; height: number },
) => {
const itemWidth = size?.width ?? 2;
const itemHeight = size?.height ?? 2;
if (ctx.xOffset + itemWidth > ctx.columnCount) {
ctx.xOffset = 0;
ctx.yOffset += ctx.rowMaxHeight;
ctx.rowMaxHeight = 0;
}
const itemId = createId();
await db.insert(items).values({
id: itemId,
boardId: ctx.boardId,
kind,
options: options ?? emptySuperJSON,
advancedOptions: emptySuperJSON,
});
await db.insert(itemLayouts).values({
itemId,
sectionId: ctx.sectionId,
layoutId: ctx.layoutId,
xOffset: ctx.xOffset,
yOffset: ctx.yOffset,
width: itemWidth,
height: itemHeight,
});
for (const integrationId of linkedIntegrationIds) {
await db.insert(integrationItems).values({ itemId, integrationId });
}
ctx.rowMaxHeight = Math.max(ctx.rowMaxHeight, itemHeight);
ctx.xOffset += itemWidth;
};
interface App {
id: string;
name: string;
}
export const placeAllWidgetsAsync = async (
db: Database,
target: BoardTarget,
allIntegrations: Integration[],
allApps: App[],
) => {
const ctx: PlacementContext = {
...target,
xOffset: 0,
yOffset: 0,
rowMaxHeight: 0,
};
const placedWidgets = new Set<WidgetKind>();
for (const integration of allIntegrations) {
for (const widgetKind of getWidgetKindsForIntegration(integration.kind)) {
if (placedWidgets.has(widgetKind)) continue;
const config = widgetConfigMap.get(widgetKind);
if (config?.skip) continue;
placedWidgets.add(widgetKind);
const matchingIds = allIntegrations
.filter((row) => getWidgetKindsForIntegration(row.kind).includes(widgetKind))
.map((row) => row.id);
const options = config?.options ? superjson.stringify(config.options) : undefined;
await placeWidgetAsync(
db,
ctx,
widgetKind,
matchingIds,
options,
config && { width: config.width, height: config.height },
);
}
}
for (const app of allApps) {
await placeWidgetAsync(
db,
ctx,
"app",
[],
superjson.stringify({ appId: app.id, openInNewTab: true, showTitle: true }),
{ width: 1, height: 1 },
);
}
const bookmarkAppNames = new Set(defaultBookmarkApps.map((bookmark) => bookmark.name));
const bookmarkAppIds = allApps.filter((app) => bookmarkAppNames.has(app.name)).map((app) => app.id);
for (const config of defaultWidgetConfigs) {
if (config.skip || placedWidgets.has(config.kind)) continue;
placedWidgets.add(config.kind);
let options = config.options ? { ...config.options } : undefined;
if (config.kind === "bookmarks" && bookmarkAppIds.length > 0) {
options = { ...options, items: bookmarkAppIds };
}
await placeWidgetAsync(db, ctx, config.kind, [], options ? superjson.stringify(options) : undefined, {
width: config.width,
height: config.height,
});
}
return placedWidgets.size + allApps.length;
};