mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-30 21:09:53 +00:00
Update Template copy & paste (#1533)
* Split original clipboard functions out * Add version check for templates * Fix regression in use template undo steps
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
119
src/extensions/core/vintageClipboard.ts
Normal file
119
src/extensions/core/vintageClipboard.ts
Normal file
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user