refactor(canvas): 节点注册表 - 收敛节点展示与几何知识到单一数据源

每加一种画布节点,叫什么、长什么样、能不能自由拉伸要在 4-5 处各写一遍。
实际后果是节点标签出现三份且互相矛盾(NODE_DEFAULT_SIZE.title / 创建菜单 label /
搜索弹窗 nodeTypeLabel),canvas-node-size.ts 里还有第四份硬编码兜底。

新增 lib/canvas/node-registry/,镜像现有 tool-registry/ 的结构:
静态定义带 satisfies Record<CanvasNodeType, ...> 做编译期穷尽性检查
(往枚举加成员不补定义即编译失败),再注册进模块级 Map 并按 ownerId 归属,
为后续扩展留出隔离位。

尺寸与初始 metadata 的数据源仍是 constant/canvas.ts 的 NODE_SPECS,定义文件读它
而非复制,现有约 40 处布局计算未动;渲染层 nodeContentRenderers 已是查表结构,
本次不改。

标签刻意分三个字段各司其职,界面文案零变化:
- defaultTitle 新建节点默认名(Note/Audio/未命名背板 原样保留)
- label UI 短标签
- listLabel 列表标签,缺省派生为 label + 节点;背板显式钉住为背板

接入 3 处调用点:创建菜单标签与图标、搜索弹窗类型标签、canvas-node.tsx 的
minWidth/minHeight 与 keepRatio。导演台落在节点分区但开的是工作台、不是节点类型,
故不走注册表。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dlh
2026-08-20 22:22:28 +08:00
parent 7a817d6847
commit af84cf5772
8 changed files with 232 additions and 29 deletions

View File

@@ -0,0 +1,74 @@
import type { CanvasNodeData, CanvasNodeType } from "@/types/canvas";
import type { CanvasNodeDefinition } from "./node-definition";
/** 模块级注册表 */
const definitions = new Map<CanvasNodeType, CanvasNodeDefinition>();
/** 节点类型 → 归属方。内置节点归属 "builtin",为后续扩展留出隔离位。 */
const ownerByType = new Map<CanvasNodeType, string>();
/** 未注册类型的兜底最小尺寸——与非媒体节点的历史下限一致 */
const FALLBACK_MIN_SIZE = { width: 220, height: 160 } as const;
/** 批量注册节点定义 */
export function registerNodeDefinitions(defs: CanvasNodeDefinition[], ownerId = "builtin") {
for (const def of defs) {
definitions.set(def.type, def);
ownerByType.set(def.type, ownerId);
}
}
/**
* 注销某归属方的全部节点定义。
* 只删归属该 ownerId 的条目——内置节点不会被其他归属方的增删波及。
*/
export function unregisterNodeDefinitions(ownerId: string) {
for (const [type, owner] of ownerByType) {
if (owner !== ownerId) continue;
definitions.delete(type);
ownerByType.delete(type);
}
}
export function getNodeDefinition(type: CanvasNodeType) {
return definitions.get(type);
}
export function getNodeOwnerId(type: CanvasNodeType) {
return ownerByType.get(type) || "builtin";
}
export function listNodeDefinitions() {
return [...definitions.values()];
}
/** 添加节点菜单可见的节点定义 */
export function listCreatableNodeDefinitions() {
return listNodeDefinitions().filter((def) => def.showInCreateMenu);
}
/** UI 短标签 */
export function getNodeLabel(type: CanvasNodeType) {
return definitions.get(type)?.label || "未知节点";
}
/** 列表/搜索标签,缺省派生自 label */
export function getNodeListLabel(type: CanvasNodeType) {
const def = definitions.get(type);
if (!def) return "未知节点";
return def.listLabel || `${def.label}节点`;
}
export function getNodeIcon(type: CanvasNodeType) {
return definitions.get(type)?.icon ?? null;
}
/** 手动拉伸的最小尺寸 */
export function getNodeMinSize(type: CanvasNodeType) {
return definitions.get(type)?.minSize ?? FALLBACK_MIN_SIZE;
}
/** 拉伸时是否锁定宽高比 */
export function shouldKeepAspectRatio(node: CanvasNodeData) {
return definitions.get(node.type)?.keepAspectRatio?.(node) ?? false;
}