fix(canvas): 节点复制 - 修复内部粘贴和批量状态残留 (#60)

Co-authored-by: Danborad <Danborad@users.noreply.github.com>
This commit is contained in:
DanboradandDanborad authored and GitHub committed 2026-08-01 15:38:15 +08:00
1 parent 14cfdbed4a
commit a3b35290de
10 files changed
+129 -31

No files matched your search

@@ -15,6 +15,10 @@ type CanvasClipboard = {
connections: CanvasConnection[];
};
const CANVAS_NODES_CLIPBOARD_PREFIX = "open-ai-canvas-nodes:";
const CANVAS_NODES_JSON_CLIPBOARD_PREFIX = "open-ai-canvas-nodes-json:";
const CANVAS_NODES_CLIPBOARD_STORAGE_KEY = "open-ai-canvas:nodes-clipboard";
type UseCanvasNodeOperationsOptions = {
projectId: string;
nodesRef: { current: CanvasNodeData[] };
@@ -49,9 +53,11 @@ export function useCanvasNodeOperations({
const preferCopiedNodesRef = useRef(false);
const markerWritePendingRef = useRef(false);
const markerWriteSequenceRef = useRef(0);
const internalMarkerWriteRef = useRef(false);
const [hasCopiedNodes, setHasCopiedNodes] = useState(false);
const releaseCopiedNodesPastePriority = useCallback(() => {
if (internalMarkerWriteRef.current) return;
markerWriteSequenceRef.current += 1;
markerWritePendingRef.current = false;
preferCopiedNodesRef.current = false;
@@ -330,26 +336,33 @@ export function useCanvasNodeOperations({
.filter((node) => copyIds.has(node.id))
.map((node) => ({ ...node, position: { ...node.position }, metadata: node.metadata ? { ...node.metadata, frame: node.metadata.frame ? { ...node.metadata.frame } : undefined } : undefined }));
if (!copiedNodes.length) return;
clipboardRef.current = {
nodes: copiedNodes,
connections: connectionsRef.current.filter((connection) => copyIds.has(connection.fromNodeId) && copyIds.has(connection.toNodeId)).map((connection) => ({ ...connection })),
};
const copiedConnections = connectionsRef.current.filter((connection) => copyIds.has(connection.fromNodeId) && copyIds.has(connection.toNodeId)).map((connection) => ({ ...connection }));
clipboardRef.current = { nodes: copiedNodes, connections: copiedConnections };
try {
sessionStorage.setItem(CANVAS_NODES_CLIPBOARD_STORAGE_KEY, JSON.stringify(clipboardRef.current));
} catch {
// 大型媒体节点可能超过浏览器存储配额,当前页面内仍可正常粘贴。
}
setHasCopiedNodes(true);
// 写入完成前或写入失败时优先内部节点,避免快速粘贴读到系统残留图片。
const marker = `open-ai-canvas-nodes:${Date.now()}:${copiedNodes.length}`;
const marker = `${CANVAS_NODES_CLIPBOARD_PREFIX}${Date.now()}:${copiedNodes.length}`;
const sequence = markerWriteSequenceRef.current + 1;
markerWriteSequenceRef.current = sequence;
markerWritePendingRef.current = true;
preferCopiedNodesRef.current = true;
void copyToClipboard(marker, { format: "text/plain" }).then((written) => {
if (markerWriteSequenceRef.current !== sequence) return;
markerWritePendingRef.current = false;
preferCopiedNodesRef.current = !written;
}, () => {
try {
internalMarkerWriteRef.current = true;
copyToClipboard(marker, { format: "text/plain" });
internalMarkerWriteRef.current = false;
if (markerWriteSequenceRef.current !== sequence) return;
markerWritePendingRef.current = false;
preferCopiedNodesRef.current = true;
});
} catch {
internalMarkerWriteRef.current = false;
if (markerWriteSequenceRef.current !== sequence) return;
markerWritePendingRef.current = false;
preferCopiedNodesRef.current = true;
}
}, [connectionsRef, nodesRef]);
const copySelectedNodes = useCallback(() => {
@@ -417,6 +430,25 @@ export function useCanvasNodeOperations({
return true;
}, [cloneDrawingForNode, commitConnections, commitNodes, connectionsRef, getCanvasCenter, nodesRef, selectNodes, setContextMenu, setDialogNodeId]);
const restoreCopiedNodesFromText = useCallback((value: string) => {
const isMarker = value.startsWith(CANVAS_NODES_CLIPBOARD_PREFIX);
const isLegacyJSON = value.startsWith(CANVAS_NODES_JSON_CLIPBOARD_PREFIX);
if (!isMarker && !isLegacyJSON) return false;
try {
const serialized = isLegacyJSON ? value.slice(CANVAS_NODES_JSON_CLIPBOARD_PREFIX.length) : sessionStorage.getItem(CANVAS_NODES_CLIPBOARD_STORAGE_KEY);
if (!serialized) return false;
const parsed = JSON.parse(serialized) as Partial<CanvasClipboard>;
if (!parsed.nodes?.length) return false;
clipboardRef.current = { nodes: parsed.nodes, connections: parsed.connections || [] };
setHasCopiedNodes(true);
preferCopiedNodesRef.current = true;
markerWritePendingRef.current = false;
return true;
} catch {
return false;
}
}, []);
return {
alignSelectedNodes,
arrangeSelectedNodes,
@@ -430,6 +462,7 @@ export function useCanvasNodeOperations({
duplicateNode,
hasCopiedNodes,
pasteCopiedNodes,
restoreCopiedNodesFromText,
releaseCopiedNodesPastePriority,
setPrimaryVersion,
shouldPreferCopiedNodes,