diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index 4007ef508..fd39687e5 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -9,6 +9,10 @@ import { useNodeDefStore } from '@/stores/nodeDefStore' import { ComfyLink, ComfyNode, ComfyWorkflowJSON } from '@/types/comfyWorkflow' import { useToastStore } from '@/stores/toastStore' import { ComfyExtension } from '@/types/comfy' +import { + deserialiseAndCreate, + serialise +} from '@/extensions/core/vintageClipboard' type GroupNodeWorkflowData = { external: ComfyLink[] @@ -1480,120 +1484,6 @@ function manageGroupNodes() { new ManageGroupDialog(app).show() } -/** - * Serialises an array of nodes using a modified version of the old Litegraph copy (& paste) function - * @param nodes All nodes to be serialised - * @param graph The graph we are working in - * @returns A serialised string of all nodes, and their connections - * @deprecated Format not in use anywhere else. - */ -function serialise(nodes: LGraphNode[], graph: LGraph): string { - const serialisable = { - nodes: [], - links: [] - } - let index = 0 - const cloneable: LGraphNode[] = [] - - for (const node of nodes) { - if (node.clonable === false) continue - - node._relative_id = index++ - cloneable.push(node) - } - - // Clone the node - for (const node of cloneable) { - const cloned = node.clone() - if (!cloned) { - console.warn('node type not found: ' + node.type) - continue - } - - serialisable.nodes.push(cloned.serialize()) - if (!node.inputs?.length) continue - - // For inputs only, gather link details of every connection - for (const input of node.inputs) { - if (!input || input.link == null) continue - - const link = graph.links.get(input.link) - if (!link) continue - - const outNode = graph.getNodeById(link.origin_id) - if (!outNode) continue - - // Special format for old Litegraph copy & paste only - serialisable.links.push([ - outNode._relative_id, - link.origin_slot, - node._relative_id, - link.target_slot, - outNode.id - ]) - } - } - - return JSON.stringify(serialisable) -} - -/** - * Deserialises nodes and links using a modified version of the old Litegraph (copy &) paste function - * @param data The serialised nodes and links to create - * @param canvas The canvas to create the serialised items in - */ -function deserialiseAndCreate(data: string, canvas: LGraphCanvas): void { - if (!data) return - - const { graph, graph_mouse } = canvas - graph.beforeChange() - - const deserialised = JSON.parse(data) - - // Find the top left point of the boundary of all pasted nodes - const topLeft = [Infinity, Infinity] - for (const { pos } of deserialised.nodes) { - if (topLeft[0] > pos[0]) topLeft[0] = pos[0] - if (topLeft[1] > pos[1]) topLeft[1] = pos[1] - } - - // Silent default instead of throw - if (!Number.isFinite(topLeft[0]) || !Number.isFinite(topLeft[1])) { - topLeft[0] = graph_mouse[0] - topLeft[1] = graph_mouse[1] - } - - // Create nodes - const nodes: LGraphNode[] = [] - for (const info of deserialised.nodes) { - const node = LiteGraph.createNode(info.type) - if (!node) continue - - node.configure(info) - - // Paste to the bottom right of pointer - node.pos[0] += graph_mouse[0] - topLeft[0] - node.pos[1] += graph_mouse[1] - topLeft[1] - - graph.add(node, true) - nodes.push(node) - } - - // Create links - for (const info of deserialised.links) { - const relativeId = info[0] - const outNode = relativeId != null ? nodes[relativeId] : undefined - - const inNode = nodes[info[2]] - if (outNode && inNode) outNode.connect(info[1], inNode, info[3]) - else console.warn('Warning, nodes missing on pasting') - } - - canvas.selectNodes(nodes) - - graph.afterChange() -} - const id = 'Comfy.GroupNode' let globalDefs const ext: ComfyExtension = { diff --git a/src/extensions/core/nodeTemplates.ts b/src/extensions/core/nodeTemplates.ts index 46fffdf2b..db42c190e 100644 --- a/src/extensions/core/nodeTemplates.ts +++ b/src/extensions/core/nodeTemplates.ts @@ -5,6 +5,7 @@ import { ComfyDialog, $el } from '../../scripts/ui' import { GroupNodeConfig, GroupNodeHandler } from './groupNode' import { LGraphCanvas } from '@comfyorg/litegraph' import { useToastStore } from '@/stores/toastStore' +import { deserialiseAndCreate } from '@/extensions/core/vintageClipboard' // Adds the ability to save and add multiple nodes as a template // To save: @@ -414,8 +415,14 @@ app.registerExtension({ clipboardAction(async () => { const data = JSON.parse(t.data) await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {}) - localStorage.setItem('litegrapheditor_clipboard', t.data) - app.canvas.pasteFromClipboard() + + // Check for old clipboard format + if (!data.reroutes) { + deserialiseAndCreate(t.data, app.canvas) + } else { + localStorage.setItem('litegrapheditor_clipboard', t.data) + app.canvas.pasteFromClipboard() + } }) } } diff --git a/src/extensions/core/vintageClipboard.ts b/src/extensions/core/vintageClipboard.ts new file mode 100644 index 000000000..cfe066349 --- /dev/null +++ b/src/extensions/core/vintageClipboard.ts @@ -0,0 +1,119 @@ +// @ts-strict-ignore +import type { LGraph, LGraphCanvas, LGraphNode } from '@comfyorg/litegraph' +import { LiteGraph } from '@comfyorg/litegraph' + +/** + * Serialises an array of nodes using a modified version of the old Litegraph copy (& paste) function + * @param nodes All nodes to be serialised + * @param graph The graph we are working in + * @returns A serialised string of all nodes, and their connections + * @deprecated Format not in use anywhere else. + */ +export function serialise(nodes: LGraphNode[], graph: LGraph): string { + const serialisable = { + nodes: [], + links: [] + } + let index = 0 + const cloneable: LGraphNode[] = [] + + for (const node of nodes) { + if (node.clonable === false) continue + + node._relative_id = index++ + cloneable.push(node) + } + + // Clone the node + for (const node of cloneable) { + const cloned = node.clone() + if (!cloned) { + console.warn('node type not found: ' + node.type) + continue + } + + serialisable.nodes.push(cloned.serialize()) + if (!node.inputs?.length) continue + + // For inputs only, gather link details of every connection + for (const input of node.inputs) { + if (!input || input.link == null) continue + + const link = graph.links.get(input.link) + if (!link) continue + + const outNode = graph.getNodeById(link.origin_id) + if (!outNode) continue + + // Special format for old Litegraph copy & paste only + serialisable.links.push([ + outNode._relative_id, + link.origin_slot, + node._relative_id, + link.target_slot, + outNode.id + ]) + } + } + + return JSON.stringify(serialisable) +} + +/** + * Deserialises nodes and links using a modified version of the old Litegraph (copy &) paste function + * @param data The serialised nodes and links to create + * @param canvas The canvas to create the serialised items in + */ +export function deserialiseAndCreate(data: string, canvas: LGraphCanvas): void { + if (!data) return + + const { graph, graph_mouse } = canvas + canvas.emitBeforeChange() + graph.beforeChange() + + const deserialised = JSON.parse(data) + + // Find the top left point of the boundary of all pasted nodes + const topLeft = [Infinity, Infinity] + for (const { pos } of deserialised.nodes) { + if (topLeft[0] > pos[0]) topLeft[0] = pos[0] + if (topLeft[1] > pos[1]) topLeft[1] = pos[1] + } + + // Silent default instead of throw + if (!Number.isFinite(topLeft[0]) || !Number.isFinite(topLeft[1])) { + topLeft[0] = graph_mouse[0] + topLeft[1] = graph_mouse[1] + } + + // Create nodes + const nodes: LGraphNode[] = [] + for (const info of deserialised.nodes) { + const node = LiteGraph.createNode(info.type) + if (!node) continue + + node.configure(info) + + // Paste to the bottom right of pointer + node.pos[0] += graph_mouse[0] - topLeft[0] + node.pos[1] += graph_mouse[1] - topLeft[1] + + graph.add(node, true) + nodes.push(node) + } + + // Create links + for (const info of deserialised.links) { + const relativeId = info[0] + const outNode = relativeId != null ? nodes[relativeId] : undefined + + const inNode = nodes[info[2]] + if (outNode && inNode) outNode.connect(info[1], inNode, info[3]) + else console.warn('Warning, nodes missing on pasting') + } + + canvas.selectNodes(nodes) + + graph.afterChange() + canvas.emitAfterChange() +} diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 26531eb9d..c4a431926 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -64,6 +64,7 @@ import { shallowReactive } from 'vue' import { type IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets' import { workflowService } from '@/services/workflowService' import { useWidgetStore } from '@/stores/widgetStore' +import { deserialiseAndCreate } from '@/extensions/core/vintageClipboard' export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview' @@ -2123,8 +2124,14 @@ export class ComfyApp { continue } - localStorage.setItem('litegrapheditor_clipboard', template.data) - app.canvas.pasteFromClipboard() + // Check for old clipboard format + const data = JSON.parse(template.data) + if (!data.reroutes) { + deserialiseAndCreate(template.data, app.canvas) + } else { + localStorage.setItem('litegrapheditor_clipboard', template.data) + app.canvas.pasteFromClipboard() + } // Move mouse position down to paste the next template below