perf(*): 创作工作台 - 优化首页按需加载与画布交互并修复历史迁移冲突
This commit is contained in:
1 parent
165b4443ab
commit
58f01d1eb6
107 files changed
+3025
-1089
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { Eraser, FolderOpen, Hand, Palette, Plus, Redo2, Settings2, SquareDashedMousePointer, Trash2, Undo2, X } from "lucide-react";
|
||||
import { Eraser, FolderOpen, Hand, Palette, Plus, Redo2, Settings2, SquareDashedMousePointer, Trash2, Undo2 } from "lucide-react";
|
||||
|
||||
import { registerToolbarTools, type ToolDefinition } from "@/lib/canvas/tool-registry";
|
||||
|
||||
@@ -7,26 +7,23 @@ export const mainToolbarTools: ToolDefinition[] = [
|
||||
id: "tool-move",
|
||||
toolbar: "main",
|
||||
category: "navigation",
|
||||
label: (ctx) => ctx.canvasTool === "box-select" ? "移动与选择" : ctx.selectedCount ? `取消选择${ctx.selectedCount > 1 ? ` ${ctx.selectedCount} 个节点` : ""}` : "移动与选择",
|
||||
icon: (ctx) => ctx.canvasTool === "box-select" ? <Hand /> : ctx.selectedCount ? <X /> : <Hand />,
|
||||
label: "抓手工具",
|
||||
icon: <Hand />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 10,
|
||||
active: (ctx) => ctx.canvasTool === "move",
|
||||
run: (ctx) => {
|
||||
if (ctx.canvasTool !== "move") ctx.handlers.onToolChange("move");
|
||||
else ctx.handlers.onDeselect();
|
||||
},
|
||||
run: (ctx) => ctx.handlers.onToolChange("move"),
|
||||
},
|
||||
{
|
||||
id: "tool-box-select",
|
||||
toolbar: "main",
|
||||
category: "navigation",
|
||||
label: "框选",
|
||||
label: "区域选择",
|
||||
icon: <SquareDashedMousePointer />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 20,
|
||||
active: (ctx) => ctx.canvasTool === "box-select",
|
||||
run: (ctx) => ctx.handlers.onToolChange(ctx.canvasTool === "box-select" ? "move" : "box-select"),
|
||||
run: (ctx) => ctx.handlers.onToolChange("box-select"),
|
||||
},
|
||||
{
|
||||
id: "tool-undo",
|
||||
|
||||
@@ -31,10 +31,15 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
toolbar: "node-hover",
|
||||
category: "node-state",
|
||||
label: (ctx) => isCharacterReference(ctx) ? "查看角色详情" : "查看节点信息",
|
||||
displayLabel: (ctx) => isCharacterReference(ctx) ? "角色详情" : "信息",
|
||||
displayLabel: (ctx) => isCharacterReference(ctx) ? "角色详情" : "节点信息",
|
||||
icon: (ctx) => isCharacterReference(ctx) ? <UserRound className="size-3.5" /> : <Info className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 10,
|
||||
nodeToolbar: {
|
||||
group: (ctx) => isCharacterReference(ctx) || (!isImage(ctx) && !isVideo(ctx) && !isAudio(ctx) && !isEditableText(ctx) && !isConfig(ctx)) ? "primary" : "more",
|
||||
order: 10,
|
||||
section: "节点管理",
|
||||
},
|
||||
run: (ctx) => ctx.handlers.onNodeInfo(ctx.node!),
|
||||
},
|
||||
{
|
||||
@@ -42,10 +47,11 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
toolbar: "node-hover",
|
||||
category: "node-state",
|
||||
label: "移除节点",
|
||||
displayLabel: "删除",
|
||||
displayLabel: "移除节点",
|
||||
icon: <Trash2 className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 20,
|
||||
nodeToolbar: { group: "more", order: 1000, section: "危险操作" },
|
||||
danger: true,
|
||||
run: (ctx) => ctx.handlers.onNodeDelete(ctx.node!),
|
||||
},
|
||||
@@ -59,6 +65,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <RefreshCw className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 30,
|
||||
nodeToolbar: { group: "primary", order: 5 },
|
||||
applicable: canRetry,
|
||||
run: (ctx) => ctx.handlers.onNodeRetry(ctx.node!),
|
||||
},
|
||||
@@ -66,11 +73,12 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
id: "extractFrames",
|
||||
toolbar: "node-hover",
|
||||
category: "node-state",
|
||||
label: (ctx) => ctx.extractingVideoFrames ? "正在提取视频画面" : "提取一个或多个视频画面",
|
||||
label: (ctx) => ctx.extractingVideoFrames ? "正在截取视频画面" : "截取一个或多个视频画面",
|
||||
displayLabel: (ctx) => ctx.extractingVideoFrames ? "提取中" : "提取画面",
|
||||
icon: (ctx) => ctx.extractingVideoFrames ? <LoaderCircle className="size-3.5 animate-spin" /> : <Images className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 40,
|
||||
nodeToolbar: { group: "process", order: 10, section: "提取素材", description: "选取画面,保存为图片节点" },
|
||||
applicable: (ctx) => hasVideo(ctx) && !simpleMode(ctx),
|
||||
disabled: (ctx) => ctx.extractingVideoFrames,
|
||||
run: (ctx) => ctx.handlers.onNodeExtractVideoFrames(ctx.node!),
|
||||
@@ -79,11 +87,12 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
id: "extractAudio",
|
||||
toolbar: "node-hover",
|
||||
category: "node-state",
|
||||
label: (ctx) => ctx.extractingAudio ? "正在提取声音为 MP3" : "提取声音为 MP3",
|
||||
label: (ctx) => ctx.extractingAudio ? "正在提取音频" : "提取视频声音为 MP3,保留原视频声音",
|
||||
displayLabel: (ctx) => ctx.extractingAudio ? "提取中" : "提取音频",
|
||||
icon: (ctx) => ctx.extractingAudio ? <LoaderCircle className="size-3.5 animate-spin" /> : <AudioLines className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 42,
|
||||
nodeToolbar: { group: "process", order: 20, section: "提取素材", description: "生成音频节点,保留原视频声音" },
|
||||
applicable: (ctx) => hasVideo(ctx) && !simpleMode(ctx),
|
||||
disabled: (ctx) => ctx.extractingAudio || ctx.trimmingVideo,
|
||||
run: (ctx) => ctx.handlers.onNodeExtractAudioFromVideo(ctx.node!),
|
||||
@@ -97,6 +106,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: (ctx) => ctx.trimmingVideo ? <LoaderCircle className="size-3.5 animate-spin" /> : <Scissors className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 44,
|
||||
nodeToolbar: { group: "primary", order: 10, description: "截取一段或多段,生成新视频节点" },
|
||||
applicable: (ctx) => hasVideo(ctx) && !simpleMode(ctx),
|
||||
disabled: (ctx) => ctx.extractingAudio || ctx.trimmingVideo,
|
||||
run: (ctx) => ctx.handlers.onNodeTrimVideoSegments(ctx.node!),
|
||||
@@ -106,10 +116,11 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
toolbar: "node-hover",
|
||||
category: "node-state",
|
||||
label: "加入我的素材",
|
||||
displayLabel: "存素材",
|
||||
displayLabel: "保存到素材库",
|
||||
icon: <FolderPlus className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 50,
|
||||
nodeToolbar: { group: "more", order: 40, section: "素材" },
|
||||
applicable: (ctx) => hasImage(ctx) || hasVideo(ctx) || isEditableText(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeSaveAsset(ctx.node!),
|
||||
},
|
||||
@@ -122,6 +133,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Download className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 60,
|
||||
nodeToolbar: { group: "utility", order: 20 },
|
||||
applicable: (ctx) => hasImage(ctx) || hasVideo(ctx) || hasAudio(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeDownload(ctx.node!),
|
||||
},
|
||||
@@ -130,10 +142,11 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
toolbar: "node-hover",
|
||||
category: "node-state",
|
||||
label: (ctx) => isEditableText(ctx) ? "调用文本模型生成内容" : "编辑",
|
||||
displayLabel: (ctx) => isEditableText(ctx) ? "文本生成" : "编辑",
|
||||
displayLabel: (ctx) => isEditableText(ctx) ? "文本生成" : "生成设置",
|
||||
icon: <MessageSquare className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 70,
|
||||
nodeToolbar: { group: (ctx) => isEditableText(ctx) ? "primary" : "more", order: 20, section: "生成信息" },
|
||||
applicable: canOpenDialog,
|
||||
run: (ctx) => ctx.handlers.onNodeToggleDialog(ctx.node!),
|
||||
},
|
||||
@@ -146,6 +159,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Maximize2 className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 80,
|
||||
nodeToolbar: { group: "primary", order: 30 },
|
||||
applicable: isEditableText,
|
||||
run: (ctx) => ctx.handlers.onNodeEditText(ctx.node!),
|
||||
},
|
||||
@@ -158,6 +172,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <ImageIcon className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 90,
|
||||
nodeToolbar: { group: "primary", order: 40 },
|
||||
applicable: isEditableText,
|
||||
run: (ctx) => ctx.handlers.onNodeGenerateImage(ctx.node!),
|
||||
},
|
||||
@@ -170,6 +185,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Settings2 className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 100,
|
||||
nodeToolbar: { group: "primary", order: 10 },
|
||||
applicable: (ctx) => isConfig(ctx) && !simpleMode(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeToggleDialog(ctx.node!),
|
||||
},
|
||||
@@ -182,6 +198,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Minus className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 110,
|
||||
nodeToolbar: { group: "process", order: 10 },
|
||||
applicable: (ctx) => isEditableText(ctx) && !simpleMode(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeDecreaseFont(ctx.node!),
|
||||
},
|
||||
@@ -194,6 +211,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Plus className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 120,
|
||||
nodeToolbar: { group: "process", order: 20 },
|
||||
applicable: (ctx) => isEditableText(ctx) && !simpleMode(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeIncreaseFont(ctx.node!),
|
||||
},
|
||||
@@ -206,6 +224,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Upload className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 130,
|
||||
nodeToolbar: { group: "primary", order: 10 },
|
||||
applicable: (ctx) => isImage(ctx) && !hasImage(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeUpload(ctx.node!),
|
||||
},
|
||||
@@ -218,6 +237,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Video className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 140,
|
||||
nodeToolbar: { group: (ctx) => hasVideo(ctx) ? "more" : "primary", order: 45, section: "素材" },
|
||||
applicable: isVideo,
|
||||
run: (ctx) => ctx.handlers.onNodeUpload(ctx.node!),
|
||||
},
|
||||
@@ -230,6 +250,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Captions className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 145,
|
||||
nodeToolbar: { group: "primary", order: 20 },
|
||||
applicable: (ctx) => hasVideo(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeSubtitles(ctx.node!),
|
||||
},
|
||||
@@ -238,10 +259,11 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
toolbar: "node-hover",
|
||||
category: "node-state",
|
||||
label: "时间线编辑",
|
||||
displayLabel: "时间线",
|
||||
displayLabel: "进入剪辑",
|
||||
icon: <Clapperboard className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 148,
|
||||
nodeToolbar: { group: "workspace", order: 30, section: "项目剪辑", description: "打开项目时间线,定位到当前素材" },
|
||||
applicable: (ctx) => hasVideo(ctx) || hasAudio(ctx),
|
||||
run: (ctx) => ctx.handlers.onNodeTimeline(ctx.node!),
|
||||
},
|
||||
@@ -254,6 +276,7 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
icon: <Music2 className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 150,
|
||||
nodeToolbar: { group: (ctx) => hasAudio(ctx) ? "more" : "primary", order: 45, section: "素材" },
|
||||
applicable: isAudio,
|
||||
run: (ctx) => ctx.handlers.onNodeUpload(ctx.node!),
|
||||
},
|
||||
@@ -263,10 +286,11 @@ export const nodeHoverToolbarTools: ToolDefinition[] = [
|
||||
toolbar: "node-hover",
|
||||
category: "navigation",
|
||||
label: (ctx) => ctx.nodeMetadata?.locked ? "解锁节点" : "锁定位置和尺寸",
|
||||
displayLabel: (ctx) => ctx.nodeMetadata?.locked ? "解锁" : "锁定",
|
||||
displayLabel: "锁定位置与尺寸",
|
||||
icon: (ctx) => ctx.nodeMetadata?.locked ? <Unlock className="size-3.5" /> : <Lock className="size-3.5" />,
|
||||
defaultVisible: true,
|
||||
defaultOrder: 160,
|
||||
nodeToolbar: { group: "more", order: 900, section: "节点管理" },
|
||||
active: (ctx) => Boolean(ctx.nodeMetadata?.locked),
|
||||
run: (ctx) => ctx.handlers.onNodeToggleLocked(ctx.node!),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type { AddNodeMenuCommand, AddNodeMenuContext, ToolbarHandlers, ToolbarId, ToolbarPrefs, ToolCategory, ToolContext, ToolDefinition } from "./tool-definition";
|
||||
export type { AddNodeMenuCommand, AddNodeMenuContext, NodeToolbarGroup, ToolbarHandlers, ToolbarId, ToolbarPrefs, ToolCategory, ToolContext, ToolDefinition } from "./tool-definition";
|
||||
export { clearToolbarPrefs, persistToolbarPrefs, readToolbarPrefs } from "./tool-persistence";
|
||||
export { defaultToolbarPrefs, getAddNodeMenuCommands, getToolbarTools, registerAddNodeMenuCommands, registerToolbarTools, resolveAddNodeMenuCommands, resolveToolbarEntries, resolveToolbarTools } from "./tool-registry";
|
||||
export { defaultToolbarPrefs, getAddNodeMenuCommands, getToolbarTools, registerAddNodeMenuCommands, registerToolbarTools, resolveAddNodeMenuCommands, resolveNodeToolbarPlacement, resolveToolbarEntries, resolveToolbarTools } from "./tool-registry";
|
||||
import "./definitions";
|
||||
@@ -7,6 +7,7 @@ import type { CanvasNodeData, CanvasNodeMetadata, CanvasNodeTypeId, CanvasToolMo
|
||||
|
||||
/** 工具栏标识——每个工具栏有独立的注册表与偏好 */
|
||||
export type ToolbarId = "main" | "selection" | "node-hover" | "add-node-menu";
|
||||
export type NodeToolbarGroup = "primary" | "portrait" | "viewpoint" | "process" | "workspace" | "utility" | "more";
|
||||
|
||||
/** 工具分类——用于分组渲染、危险隔离与 separator 自动插入 */
|
||||
export type ToolCategory =
|
||||
@@ -166,6 +167,13 @@ export type ToolDefinition = {
|
||||
defaultVisible: boolean;
|
||||
/** 默认排序权重,升序 */
|
||||
defaultOrder: number;
|
||||
/** 节点工具条的展示层级;由注册表统一决定,避免组件按工具 ID 二次编排。 */
|
||||
nodeToolbar?: {
|
||||
group: NodeToolbarGroup | ((ctx: ToolContext) => NodeToolbarGroup);
|
||||
order?: number | ((ctx: ToolContext) => number);
|
||||
section?: string;
|
||||
description?: string;
|
||||
};
|
||||
active?: (ctx: ToolContext) => boolean;
|
||||
disabled?: (ctx: ToolContext) => boolean;
|
||||
/** 危险操作——渲染时隔离到独立分组 */
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { FloatingDockEntry } from "@/components/ui/aceternity/floating-dock
|
||||
import { ART_CRITIQUE_NODE_TYPE } from "@/lib/art-critique/contracts";
|
||||
import { listCreatableNodeDefinitions } from "@/lib/canvas/node-registry";
|
||||
|
||||
import type { AddNodeMenuCommand, AddNodeMenuContext, ToolCategory, ToolContext, ToolDefinition, ToolbarId, ToolbarPrefs } from "./tool-definition";
|
||||
import type { AddNodeMenuCommand, AddNodeMenuContext, NodeToolbarGroup, ToolCategory, ToolContext, ToolDefinition, ToolbarId, ToolbarPrefs } from "./tool-definition";
|
||||
|
||||
/** 模块级注册表 */
|
||||
const registry = new Map<ToolbarId, ToolDefinition[]>();
|
||||
@@ -93,6 +93,15 @@ export function resolveToolbarTools(toolbar: ToolbarId, ctx: ToolContext, prefs:
|
||||
});
|
||||
}
|
||||
|
||||
/** 将节点工具定义解析为唯一的 Dock 展示层级和排序。 */
|
||||
export function resolveNodeToolbarPlacement(tool: ToolDefinition, ctx: ToolContext): { group: NodeToolbarGroup; order: number } {
|
||||
const placement = tool.nodeToolbar;
|
||||
return {
|
||||
group: typeof placement?.group === "function" ? placement.group(ctx) : placement?.group || "more",
|
||||
order: typeof placement?.order === "function" ? placement.order(ctx) : placement?.order ?? tool.defaultOrder,
|
||||
};
|
||||
}
|
||||
|
||||
/** 解析添加节点菜单命令——合并插件节点后按 applicable 过滤并排序。 */
|
||||
export function resolveAddNodeMenuCommands(ctx: AddNodeMenuContext): AddNodeMenuCommand[] {
|
||||
return [...getAddNodeMenuCommands(), ...getPluginNodeMenuCommands()].filter((command) => !command.applicable || command.applicable(ctx)).sort((a, b) => a.defaultOrder - b.defaultOrder);
|
||||
|
||||
Reference in new issue
Block a user