mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-24 00:09:32 +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 { ComfyLink, ComfyNode, ComfyWorkflowJSON } from '@/types/comfyWorkflow'
|
||||||
import { useToastStore } from '@/stores/toastStore'
|
import { useToastStore } from '@/stores/toastStore'
|
||||||
import { ComfyExtension } from '@/types/comfy'
|
import { ComfyExtension } from '@/types/comfy'
|
||||||
|
import {
|
||||||
|
deserialiseAndCreate,
|
||||||
|
serialise
|
||||||
|
} from '@/extensions/core/vintageClipboard'
|
||||||
|
|
||||||
type GroupNodeWorkflowData = {
|
type GroupNodeWorkflowData = {
|
||||||
external: ComfyLink[]
|
external: ComfyLink[]
|
||||||
@@ -1480,120 +1484,6 @@ function manageGroupNodes() {
|
|||||||
new ManageGroupDialog(app).show()
|
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'
|
const id = 'Comfy.GroupNode'
|
||||||
let globalDefs
|
let globalDefs
|
||||||
const ext: ComfyExtension = {
|
const ext: ComfyExtension = {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ComfyDialog, $el } from '../../scripts/ui'
|
|||||||
import { GroupNodeConfig, GroupNodeHandler } from './groupNode'
|
import { GroupNodeConfig, GroupNodeHandler } from './groupNode'
|
||||||
import { LGraphCanvas } from '@comfyorg/litegraph'
|
import { LGraphCanvas } from '@comfyorg/litegraph'
|
||||||
import { useToastStore } from '@/stores/toastStore'
|
import { useToastStore } from '@/stores/toastStore'
|
||||||
|
import { deserialiseAndCreate } from '@/extensions/core/vintageClipboard'
|
||||||
|
|
||||||
// Adds the ability to save and add multiple nodes as a template
|
// Adds the ability to save and add multiple nodes as a template
|
||||||
// To save:
|
// To save:
|
||||||
@@ -414,8 +415,14 @@ app.registerExtension({
|
|||||||
clipboardAction(async () => {
|
clipboardAction(async () => {
|
||||||
const data = JSON.parse(t.data)
|
const data = JSON.parse(t.data)
|
||||||
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {})
|
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 { type IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||||
import { workflowService } from '@/services/workflowService'
|
import { workflowService } from '@/services/workflowService'
|
||||||
import { useWidgetStore } from '@/stores/widgetStore'
|
import { useWidgetStore } from '@/stores/widgetStore'
|
||||||
|
import { deserialiseAndCreate } from '@/extensions/core/vintageClipboard'
|
||||||
|
|
||||||
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
||||||
|
|
||||||
@@ -2123,8 +2124,14 @@ export class ComfyApp {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem('litegrapheditor_clipboard', template.data)
|
// Check for old clipboard format
|
||||||
app.canvas.pasteFromClipboard()
|
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
|
// Move mouse position down to paste the next template below
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user