feat(canvas): 画布工作台 - 新增生成副本并修复大型画布性能、素材同步与删除校验
统一图片与视频副本的生成落点并保留参考连接。\n优化大型画布节点虚拟化、媒体懒加载、拖拽与详情进入路径。\n修复素材主版本 ID 同步及删除时的画布独立引用校验。
This commit is contained in:
1 parent
b3b360481c
commit
3a21e0ebfc
45 files changed
+1348
-286
No files matched your search
@@ -50,10 +50,15 @@ export function normalizeCanvasNodeTimestamps(nodes: CanvasNodeData[], fallback:
|
||||
|
||||
export function stampCanvasNodeChanges(previousNodes: CanvasNodeData[], nextNodes: CanvasNodeData[], now = new Date().toISOString()) {
|
||||
if (previousNodes === nextNodes) return previousNodes;
|
||||
const previousById = new Map(previousNodes.map((node) => [node.id, node]));
|
||||
// Most canvas edits preserve order and replace one node. Avoid allocating a
|
||||
// 50k-entry id map and comparing metadata for every untouched node in that
|
||||
// hot path. The map fallback still handles inserts, deletes and reorders.
|
||||
const sameOrder = previousNodes.length === nextNodes.length && previousNodes.every((node, index) => node.id === nextNodes[index]?.id);
|
||||
const previousById = sameOrder ? null : new Map(previousNodes.map((node) => [node.id, node]));
|
||||
let changed = false;
|
||||
const stamped = nextNodes.map((node) => {
|
||||
const previous = previousById.get(node.id);
|
||||
const stamped = nextNodes.map((node, index) => {
|
||||
const previous = sameOrder ? previousNodes[index] : previousById?.get(node.id);
|
||||
if (previous === node) return node;
|
||||
const createdAt = canvasNodeCreatedAt(node, canvasNodeCreatedAt(previous || node, now)) || now;
|
||||
const meaningfulChange = previous ? canvasNodeMeaningfullyChanged(previous, node) : false;
|
||||
const updatedAt = previous
|
||||
@@ -68,6 +73,33 @@ export function stampCanvasNodeChanges(previousNodes: CanvasNodeData[], nextNode
|
||||
return changed ? stamped : nextNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update one node without routing through the generic full-array timestamp
|
||||
* reconciliation. This is used by media metadata callbacks, which can fire
|
||||
* in bursts while a large canvas is first becoming visible.
|
||||
*/
|
||||
export function updateCanvasNode(nodes: CanvasNodeData[], nodeId: string, update: (node: CanvasNodeData) => CanvasNodeData, now = new Date().toISOString()) {
|
||||
return updateCanvasNodes(nodes, new Map([[nodeId, update]]), now);
|
||||
}
|
||||
|
||||
export function updateCanvasNodes(nodes: CanvasNodeData[], updates: ReadonlyMap<string, (node: CanvasNodeData) => CanvasNodeData>, now = new Date().toISOString()) {
|
||||
if (!updates.size) return nodes;
|
||||
let changed = false;
|
||||
const next = nodes.map((current) => {
|
||||
const update = updates.get(current.id);
|
||||
if (!update) return current;
|
||||
const updated = update(current);
|
||||
if (updated === current) return current;
|
||||
changed = true;
|
||||
const createdAt = canvasNodeCreatedAt(updated, canvasNodeCreatedAt(current, now)) || now;
|
||||
const updatedAt = canvasNodeMeaningfullyChanged(current, updated)
|
||||
? now
|
||||
: canvasNodeUpdatedAt(updated, canvasNodeUpdatedAt(current, createdAt)) || createdAt;
|
||||
return updated.createdAt === createdAt && updated.updatedAt === updatedAt ? updated : { ...updated, createdAt, updatedAt };
|
||||
});
|
||||
return changed ? next : nodes;
|
||||
}
|
||||
|
||||
function canvasNodeMeaningfullyChanged(previous: CanvasNodeData, next: CanvasNodeData) {
|
||||
if (
|
||||
previous.type !== next.type
|
||||
|
||||
Reference in new issue
Block a user