每加一种画布节点,叫什么、长什么样、能不能自由拉伸要在 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>
32 lines
2.7 KiB
TypeScript
32 lines
2.7 KiB
TypeScript
import { FolderOpen, Layers3, Palette, UploadCloud, UserRound } from "lucide-react";
|
|
|
|
import { getNodeIcon, getNodeLabel } from "@/lib/canvas/node-registry";
|
|
import { registerAddNodeMenuCommands, type AddNodeMenuCommand } from "@/lib/canvas/tool-registry";
|
|
import { CanvasNodeType } from "@/types/canvas";
|
|
|
|
/** 真正创建节点的命令,文案与图标统一取自节点注册表。 */
|
|
function nodeCommand(type: CanvasNodeType, rest: Omit<AddNodeMenuCommand, "id" | "label" | "icon" | "section">): AddNodeMenuCommand {
|
|
return { id: type, label: getNodeLabel(type), icon: getNodeIcon(type), section: "node", ...rest };
|
|
}
|
|
|
|
export const addNodeMenuCommands: AddNodeMenuCommand[] = [
|
|
// 项目级动作不占用节点网格,保证 8 个节点稳定保持四列两行。
|
|
{ id: "style", label: "项目画风", icon: <Palette />, section: "project", defaultOrder: 10, applicable: (ctx) => !ctx.isProjectLinked, run: (ctx) => ctx.handlers.onChooseStyle() },
|
|
// 创作节点
|
|
nodeCommand(CanvasNodeType.Text, { defaultOrder: 10, run: (ctx) => ctx.handlers.onAddText() }),
|
|
nodeCommand(CanvasNodeType.Drawing, { defaultOrder: 20, run: (ctx) => ctx.handlers.onAddDrawing() }),
|
|
nodeCommand(CanvasNodeType.Script, { badge: "核心", defaultOrder: 30, run: (ctx) => ctx.handlers.onAddScript() }),
|
|
nodeCommand(CanvasNodeType.Frame, { defaultOrder: 40, applicable: (ctx) => ctx.workspaceMode !== "simple", run: (ctx) => ctx.handlers.onAddFrame() }),
|
|
nodeCommand(CanvasNodeType.Image, { defaultOrder: 50, run: (ctx) => ctx.handlers.onAddImage() }),
|
|
nodeCommand(CanvasNodeType.Video, { defaultOrder: 60, run: (ctx) => ctx.handlers.onAddVideo() }),
|
|
// 导演台落在节点分区,但它开的是导演工作台、不是某种画布节点,故不走注册表。
|
|
{ id: "director", label: "导演台", icon: <Layers3 />, badge: "3D", section: "node", defaultOrder: 70, applicable: (ctx) => ctx.workspaceMode !== "simple", run: (ctx) => ctx.handlers.onOpenDirector() },
|
|
nodeCommand(CanvasNodeType.Audio, { defaultOrder: 80, applicable: (ctx) => ctx.workspaceMode !== "simple", run: (ctx) => ctx.handlers.onAddAudio() }),
|
|
// 导入资源
|
|
{ id: "upload", label: "上传文件", icon: <UploadCloud />, section: "resource", defaultOrder: 10, run: (ctx) => ctx.handlers.onUpload() },
|
|
{ id: "project-character", label: "添加角色卡", icon: <UserRound />, section: "resource", defaultOrder: 20, applicable: (ctx) => ctx.isProjectLinked, run: (ctx) => ctx.handlers.onOpenProjectCharacters() },
|
|
{ id: "assets", label: "素材库", icon: <FolderOpen />, section: "resource", defaultOrder: 30, applicable: (ctx) => !ctx.isProjectLinked, run: (ctx) => ctx.handlers.onOpenMyAssets() },
|
|
];
|
|
|
|
registerAddNodeMenuCommands(addNodeMenuCommands);
|