From 0c4c258a521cb10c775a8d1d8164432fae3d1504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=AC=91=E7=9A=84=E6=AF=9B=E6=AF=9B=E8=99=AB?= <315515767@qq.com> Date: Mon, 3 Aug 2026 23:22:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(canvas):=20=E5=B7=A5=E5=85=B7=E6=A0=8F?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=8C=96=20=E2=80=94=20=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E8=A1=A8=E9=A9=B1=E5=8A=A8+=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E6=84=9F=E7=9F=A5+=E8=87=AA=E5=AE=9A=E4=B9=89=E6=8E=92?= =?UTF-8?q?=E5=BA=8F+=E5=8D=B1=E9=99=A9=E9=9A=94=E7=A6=BB=20#95=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将三个硬编码工具栏(主工具栏/多选工具栏/节点悬停工具栏)统一改造为注册表驱动体系: - 新增 tool-registry 核心基础设施(ToolDefinition/ToolContext/ToolbarHandlers 类型、 注册表解析器 resolveToolbarEntries/resolveToolbarTools、偏好持久化 readToolbarPrefs/persistToolbarPrefs) - 提取四个工具定义文件(main/selection/node-hover/add-node-menu),通过 applicable 谓词实现上下文感知 - FloatingDock 新增 expands 字段(aria-expanded 语义修正)和 danger-group 分组渲染(危险操作视觉隔离) - 主工具栏新增工具栏设置面板(ToolbarSettingsModal),支持拖拽排序+显隐控制(motion/react Reorder,零新增依赖) - 偏好持久化到 scopedLocalStorage,按工具栏独立存储 Co-authored-by: sikongyue <3155157671@qq.com> --- .../canvas/canvas-node-hover-toolbar.tsx | 74 ++++-- web/src/components/canvas/canvas-toolbar.tsx | 148 +++++++----- .../toolbars/toolbar-settings-modal.tsx | 159 +++++++++++++ .../ui/aceternity/floating-dock.tsx | 59 ++++- .../definitions/add-node-menu-tools.tsx | 22 ++ .../canvas/tool-registry/definitions/index.ts | 8 + .../definitions/main-toolbar-tools.tsx | 125 ++++++++++ .../definitions/node-hover-tools.tsx | 223 ++++++++++++++++++ .../definitions/selection-toolbar-tools.tsx | 37 +++ web/src/lib/canvas/tool-registry/index.ts | 4 + .../canvas/tool-registry/tool-definition.ts | 159 +++++++++++++ .../canvas/tool-registry/tool-persistence.ts | 41 ++++ .../lib/canvas/tool-registry/tool-registry.ts | 114 +++++++++ .../canvas-project-selection-toolbar.tsx | 47 ++-- web/src/styles/globals.css | 4 + 15 files changed, 1112 insertions(+), 112 deletions(-) create mode 100644 web/src/components/canvas/toolbars/toolbar-settings-modal.tsx create mode 100644 web/src/lib/canvas/tool-registry/definitions/add-node-menu-tools.tsx create mode 100644 web/src/lib/canvas/tool-registry/definitions/index.ts create mode 100644 web/src/lib/canvas/tool-registry/definitions/main-toolbar-tools.tsx create mode 100644 web/src/lib/canvas/tool-registry/definitions/node-hover-tools.tsx create mode 100644 web/src/lib/canvas/tool-registry/definitions/selection-toolbar-tools.tsx create mode 100644 web/src/lib/canvas/tool-registry/index.ts create mode 100644 web/src/lib/canvas/tool-registry/tool-definition.ts create mode 100644 web/src/lib/canvas/tool-registry/tool-persistence.ts create mode 100644 web/src/lib/canvas/tool-registry/tool-registry.ts diff --git a/web/src/components/canvas/canvas-node-hover-toolbar.tsx b/web/src/components/canvas/canvas-node-hover-toolbar.tsx index 269143c70..a2dc2ab3e 100644 --- a/web/src/components/canvas/canvas-node-hover-toolbar.tsx +++ b/web/src/components/canvas/canvas-node-hover-toolbar.tsx @@ -1,9 +1,10 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode, type RefObject } from "react"; import { App, Button, Dropdown, Input, Modal, Segmented, Tag } from "antd"; -import { Download, Ellipsis, FolderPlus, GalleryHorizontalEnd, Image as ImageIcon, Info, LoaderCircle, Lock, Maximize2, MessageSquare, Minus, Music2, Plus, RefreshCw, Settings2, Trash2, Unlock, Upload, UserRound, Video } from "lucide-react"; +import { Ellipsis, Lock, Plus, Settings2, Unlock } from "lucide-react"; import { canvasThemes } from "@/lib/canvas-theme"; import { canvasDockStyle } from "@/lib/canvas/canvas-aceternity-style"; +import { defaultToolbarPrefs, readToolbarPrefs, resolveToolbarTools, type ToolContext, type ToolbarHandlers } from "@/lib/canvas/tool-registry"; import { subscribeCanvasViewportPreview } from "@/lib/canvas/canvas-live-viewport"; import { canvasNodeAssetCategory } from "@/lib/canvas/canvas-node-asset"; import { formatBytes, getDataUrlByteSize } from "@/lib/image-utils"; @@ -232,28 +233,55 @@ export function CanvasNodeHoverToolbar({ setImageToolSettingsOpen(true); } - const baseToolbarTools: ToolbarTool[] = [ - { id: "info", title: isCharacterReference ? "查看角色详情" : "查看节点信息", label: isCharacterReference ? "角色详情" : "信息", icon: isCharacterReference ? : , onClick: () => onInfo(node) }, - { id: "delete", title: "移除节点", label: "删除", icon: , onClick: () => onDelete(node), danger: true }, - ]; - const nodeToolbarTools: ToolbarTool[] = [ - ...(canRetry ? [{ id: "retry", title: "重新生成", label: "重试", icon: , onClick: () => onRetry(node) }] : []), - ...(hasVideo && !simpleMode ? [{ id: "extractLastFrame", title: extractingVideoFrame ? "正在截取尾帧" : "截取尾帧", label: extractingVideoFrame ? "截取中" : "尾帧", icon: extractingVideoFrame ? : , onClick: () => onExtractVideoLastFrame(node), disabled: extractingVideoFrame }] : []), - ...(hasImage || hasVideo || isEditableText ? [{ id: "saveAsset", title: "加入我的素材", label: "存素材", icon: , onClick: () => onSaveAsset(node) }] : []), - ...(hasImage || hasVideo || hasAudio ? [{ id: "download", title: hasAudio ? "下载音频" : hasVideo ? "下载视频" : "下载图片", label: "下载", icon: , onClick: () => onDownload(node) }] : []), - ...(canOpenDialog ? [{ id: "edit", title: isEditableText ? "调用文本模型生成内容" : "编辑", label: isEditableText ? "文本生成" : "编辑", icon: , onClick: () => onToggleDialog(node) }] : []), - ...(isEditableText ? [{ id: "editText", title: "放大编辑文本", label: "放大编辑", icon: , onClick: () => onEditText(node) }] : []), - ...(isEditableText ? [{ id: "generateImage", title: "用文本生图", label: "生图", icon: , onClick: () => onGenerateImage(node) }] : []), - ...(isConfig && !simpleMode ? [{ id: "config", title: "生成配置", label: "生成配置", icon: , onClick: () => onToggleDialog(node) }] : []), - ...(isEditableText && !simpleMode ? [{ id: "decreaseFont", title: "减小字号", label: "缩小", icon: , onClick: () => onDecreaseFont(node) }] : []), - ...(isEditableText && !simpleMode ? [{ id: "increaseFont", title: "增大字号", label: "放大", icon: , onClick: () => onIncreaseFont(node) }] : []), - ...(isImage && !hasImage ? [{ id: "uploadImage", title: "上传图片", label: "上传图片", icon: , onClick: () => onUpload(node) }] : []), - ...(isVideo ? [{ id: "uploadVideo", title: hasVideo ? "替换视频" : "上传视频", label: hasVideo ? "替换视频" : "上传视频", icon: