Files
homarr/packages/db/queries/widget-placement.ts
T
+12 fcd7c979df feat!: release v2 (#6545)
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com>
Co-authored-by: Danura30082 <danura30082+github@gmail.com>
Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com>
Co-authored-by: ajnart <thomas@ajnart.fr>
Co-authored-by: ajnart <thomas@ajnart.dev>
Co-authored-by: chenjun <chenjun@foxmail.com>
Co-authored-by: Wikibear <wikibear@users.noreply.github.com>
Co-authored-by: Bergasha <bergasha@gmail.com>
Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com>
Co-authored-by: Lucas Araujo <lucas.lra@gmail.com>
Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com>
Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social>
Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com>
Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com>
Co-authored-by: thrawnx <tight.bulb9384@fastmail.com>
Co-authored-by: Chris <58918656+nordada@users.noreply.github.com>
Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com>
Co-authored-by: Yanis <naamaneyanis@gmail.com>
Co-authored-by: Wikibear <me@sej.de>
Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
2026-10-02 21:00:14 +02:00

173 lines
4.7 KiB
TypeScript

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,
contexts: PlacementContext[],
kind: WidgetKind,
linkedIntegrationIds: string[],
options?: string,
size?: { width: number; height: number },
) => {
const boardId = contexts.at(0)?.boardId;
if (!boardId) return;
const desiredItemWidth = size?.width ?? 2;
const itemHeight = size?.height ?? 2;
const itemId = createId();
await db.insert(items).values({
id: itemId,
boardId,
kind,
options: options ?? emptySuperJSON,
advancedOptions: emptySuperJSON,
});
await db.insert(itemLayouts).values(
contexts.map((ctx) => {
const itemWidth = Math.min(ctx.columnCount, desiredItemWidth);
if (ctx.xOffset + itemWidth > ctx.columnCount) {
ctx.xOffset = 0;
ctx.yOffset += ctx.rowMaxHeight;
ctx.rowMaxHeight = 0;
}
const placement = {
itemId,
sectionId: ctx.sectionId,
layoutId: ctx.layoutId,
xOffset: ctx.xOffset,
yOffset: ctx.yOffset,
width: itemWidth,
height: itemHeight,
};
ctx.rowMaxHeight = Math.max(ctx.rowMaxHeight, itemHeight);
ctx.xOffset += itemWidth;
return placement;
}),
);
for (const integrationId of linkedIntegrationIds) {
await db.insert(integrationItems).values({ itemId, integrationId });
}
};
interface App {
id: string;
name: string;
}
export const placeAllWidgetsAsync = async (
db: Database,
target: BoardTarget | BoardTarget[],
allIntegrations: Integration[],
allApps: App[],
) => {
const targets = Array.isArray(target) ? target : [target];
const contexts: PlacementContext[] = targets.map((currentTarget) => ({
...currentTarget,
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);
let matchingIds = allIntegrations
.filter((row) => getWidgetKindsForIntegration(row.kind).includes(widgetKind))
.map((row) => row.id);
let options = config?.options ? superjson.stringify(config.options) : undefined;
const mockIntegration = allIntegrations.find((row) => row.kind === "mock");
if (widgetKind === "stats" && mockIntegration) {
matchingIds = [mockIntegration.id];
options = superjson.stringify({
...config?.options,
entries: ["documents", "songs", "storage"].map((metric) => ({
id: `mock-${metric}`,
integrationId: mockIntegration.id,
metric,
label: "",
hidden: false,
compact: false,
})),
});
}
await placeWidgetAsync(
db,
contexts,
widgetKind,
matchingIds,
options,
config && { width: config.width, height: config.height },
);
}
}
for (const app of allApps) {
await placeWidgetAsync(
db,
contexts,
"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, contexts, config.kind, [], options ? superjson.stringify(options) : undefined, {
width: config.width,
height: config.height,
});
}
return placedWidgets.size + allApps.length;
};