174 lines
5.0 KiB
TypeScript
174 lines
5.0 KiB
TypeScript
import type {
|
|
FlowBoardDraft,
|
|
FlowBoardSheetSnapshotData,
|
|
FlowBoardSnapshotData,
|
|
} from "@/lib/db/schema";
|
|
|
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function normalizeRecord(value: unknown): Record<string, unknown> | null {
|
|
return isRecord(value) ? value : null;
|
|
}
|
|
|
|
const DEFAULT_FLOW_BOARD_CANVAS = {
|
|
backgroundColor: "#f7f8fb",
|
|
gridColor: "#d8dde8",
|
|
gridEnabled: false,
|
|
gridSize: 24,
|
|
};
|
|
|
|
function normalizeCanvasColor(value: unknown, fallback: string) {
|
|
return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value)
|
|
? value.toLowerCase()
|
|
: fallback;
|
|
}
|
|
|
|
function normalizeFlowBoardCanvas(value: unknown): Record<string, unknown> {
|
|
const canvas = isRecord(value) ? value : {};
|
|
const requestedGridSize =
|
|
typeof canvas.gridSize === "number" && Number.isFinite(canvas.gridSize)
|
|
? Math.round(canvas.gridSize)
|
|
: DEFAULT_FLOW_BOARD_CANVAS.gridSize;
|
|
|
|
return {
|
|
backgroundColor: normalizeCanvasColor(
|
|
canvas.backgroundColor,
|
|
DEFAULT_FLOW_BOARD_CANVAS.backgroundColor
|
|
),
|
|
gridColor: normalizeCanvasColor(
|
|
canvas.gridColor,
|
|
DEFAULT_FLOW_BOARD_CANVAS.gridColor
|
|
),
|
|
gridEnabled:
|
|
typeof canvas.gridEnabled === "boolean"
|
|
? canvas.gridEnabled
|
|
: DEFAULT_FLOW_BOARD_CANVAS.gridEnabled,
|
|
gridSize: Math.min(64, Math.max(8, requestedGridSize)),
|
|
};
|
|
}
|
|
|
|
function normalizeSheetId(value: unknown, index: number) {
|
|
const id = typeof value === "string" ? value.trim().slice(0, 128) : "";
|
|
return id || `sheet-${index + 1}`;
|
|
}
|
|
|
|
function normalizeSheetTitle(value: unknown, index: number) {
|
|
const title = typeof value === "string" ? value.trim().slice(0, 128) : "";
|
|
return title || `画布 ${index + 1}`;
|
|
}
|
|
|
|
function normalizeFlowBoardSheet(
|
|
value: unknown,
|
|
index: number
|
|
): FlowBoardSheetSnapshotData | null {
|
|
if (!isRecord(value)) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: normalizeSheetId(value.id, index),
|
|
title: normalizeSheetTitle(value.title, index),
|
|
children: Array.isArray(value.children)
|
|
? value.children.filter(isRecord)
|
|
: [],
|
|
viewport: normalizeRecord(value.viewport),
|
|
theme: normalizeRecord(value.theme),
|
|
canvas: normalizeFlowBoardCanvas(value.canvas),
|
|
};
|
|
}
|
|
|
|
export function normalizeFlowBoardTitle(value: unknown) {
|
|
const title = String(value || "").trim();
|
|
return title.slice(0, 256) || "未命名流程白板";
|
|
}
|
|
|
|
export function normalizeFlowBoardSnapshot(
|
|
value: unknown
|
|
): FlowBoardSnapshotData {
|
|
const raw = isRecord(value) ? value : {};
|
|
const legacyChildren = Array.isArray(raw.children)
|
|
? raw.children.filter(isRecord)
|
|
: [];
|
|
const normalizedSheets = Array.isArray(raw.sheets)
|
|
? raw.sheets
|
|
.map(normalizeFlowBoardSheet)
|
|
.filter(
|
|
(sheet): sheet is FlowBoardSheetSnapshotData => Boolean(sheet)
|
|
)
|
|
: [];
|
|
const sheets =
|
|
normalizedSheets.length > 0
|
|
? normalizedSheets
|
|
: [
|
|
{
|
|
id: "sheet-1",
|
|
title: "画布 1",
|
|
children: legacyChildren,
|
|
viewport: normalizeRecord(raw.viewport),
|
|
theme: normalizeRecord(raw.theme),
|
|
canvas: normalizeFlowBoardCanvas(raw.canvas),
|
|
},
|
|
];
|
|
const requestedActiveSheetId =
|
|
typeof raw.activeSheetId === "string" ? raw.activeSheetId.trim() : "";
|
|
const activeSheet =
|
|
sheets.find((sheet) => sheet.id === requestedActiveSheetId) || sheets[0];
|
|
const activeChildren = Array.isArray(raw.children)
|
|
? legacyChildren
|
|
: activeSheet.children;
|
|
const activeViewport = isRecord(raw.viewport)
|
|
? raw.viewport
|
|
: activeSheet.viewport;
|
|
const activeTheme = isRecord(raw.theme) ? raw.theme : activeSheet.theme;
|
|
const activeCanvas = isRecord(raw.canvas)
|
|
? normalizeFlowBoardCanvas(raw.canvas)
|
|
: normalizeFlowBoardCanvas(activeSheet.canvas);
|
|
const nextSheets = sheets.map((sheet) =>
|
|
sheet.id === activeSheet.id
|
|
? {
|
|
...sheet,
|
|
children: activeChildren,
|
|
viewport: activeViewport,
|
|
theme: activeTheme,
|
|
canvas: activeCanvas,
|
|
}
|
|
: sheet
|
|
);
|
|
const updatedAt =
|
|
typeof raw.updatedAt === "string" && raw.updatedAt.trim()
|
|
? raw.updatedAt
|
|
: new Date().toISOString();
|
|
|
|
return {
|
|
version: 2,
|
|
activeSheetId: activeSheet.id,
|
|
sheets: nextSheets,
|
|
children: activeChildren,
|
|
viewport: activeViewport,
|
|
theme: activeTheme,
|
|
canvas: activeCanvas,
|
|
updatedAt,
|
|
};
|
|
}
|
|
|
|
export function mapFlowBoardDraft(draft: FlowBoardDraft) {
|
|
return {
|
|
id: draft.id,
|
|
title: draft.title || "未命名流程白板",
|
|
snapshot: draft.snapshot ?? { children: [] },
|
|
createdAt: draft.createdAt?.toISOString() ?? null,
|
|
updatedAt: draft.updatedAt?.toISOString() ?? null,
|
|
};
|
|
}
|
|
|
|
export function mapFlowBoardDraftSummary(draft: FlowBoardDraft) {
|
|
return {
|
|
id: draft.id,
|
|
title: draft.title || "未命名流程白板",
|
|
createdAt: draft.createdAt?.toISOString() ?? null,
|
|
updatedAt: draft.updatedAt?.toISOString() ?? null,
|
|
};
|
|
}
|