mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
Multiple fixes related to copying subgraphs (#6383)
- DOMWidgets have ownership reset after a `subgraphNode.clone()`. - Fixes Ctrl+C on a subgraphNode with a prompted prompt making the prompt disappear. - alt + drag uses the copy/paste pathway that deeply clones subgraphs. - Fixed dangling references on nodes in subgraphs by updating subgraph ids before configuration. - Attempt to recursively resolve disconnected proxyWidgets (Can matter when subgraphs load out of order). - Fix Right click -> clone creating linked copies of subgraphs. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6383-Multiple-fixes-related-to-copying-subgraphs-29b6d73d365081819671ced440dde327) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -158,7 +158,11 @@ function resolveLinkedWidget(
|
||||
const { graph, nodeId, widgetName } = overlay
|
||||
const n = getNodeByExecutionId(graph, nodeId)
|
||||
if (!n) return [undefined, undefined]
|
||||
return [n, n.widgets?.find((w: IBaseWidget) => w.name === widgetName)]
|
||||
const widget = n.widgets?.find((w: IBaseWidget) => w.name === widgetName)
|
||||
//Slightly hacky. Force recursive resolution of nested widgets
|
||||
if (widget instanceof disconnectedWidget.constructor && isProxyWidget(widget))
|
||||
widget.computedHeight = 20
|
||||
return [n, widget]
|
||||
}
|
||||
|
||||
function newProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) {
|
||||
|
||||
@@ -86,7 +86,11 @@ import {
|
||||
RenderShape,
|
||||
TitleMode
|
||||
} from './types/globalEnums'
|
||||
import type { ClipboardItems, SubgraphIO } from './types/serialisation'
|
||||
import type {
|
||||
ClipboardItems,
|
||||
ISerialisedNode,
|
||||
SubgraphIO
|
||||
} from './types/serialisation'
|
||||
import type { NeverNever, PickNevers } from './types/utility'
|
||||
import type { IBaseWidget } from './types/widgets'
|
||||
import { alignNodes, distributeNodes, getBoundaryNodes } from './utils/arrange'
|
||||
@@ -1772,47 +1776,24 @@ export class LGraphCanvas
|
||||
menu: ContextMenu,
|
||||
node: LGraphNode
|
||||
): void {
|
||||
const { graph } = node
|
||||
if (!graph) throw new NullGraphError()
|
||||
graph.beforeChange()
|
||||
|
||||
const newSelected = new Set<LGraphNode>()
|
||||
|
||||
const fApplyMultiNode = function (
|
||||
node: LGraphNode,
|
||||
newNodes: Set<LGraphNode>
|
||||
): void {
|
||||
if (node.clonable === false) return
|
||||
|
||||
const newnode = node.clone()
|
||||
if (!newnode) return
|
||||
|
||||
newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]
|
||||
if (!node.graph) throw new NullGraphError()
|
||||
|
||||
node.graph.add(newnode)
|
||||
newNodes.add(newnode)
|
||||
}
|
||||
|
||||
const canvas = LGraphCanvas.active_canvas
|
||||
if (
|
||||
!canvas.selected_nodes ||
|
||||
Object.keys(canvas.selected_nodes).length <= 1
|
||||
) {
|
||||
fApplyMultiNode(node, newSelected)
|
||||
} else {
|
||||
for (const i in canvas.selected_nodes) {
|
||||
fApplyMultiNode(canvas.selected_nodes[i], newSelected)
|
||||
}
|
||||
const nodes = canvas.selectedItems.size ? canvas.selectedItems : [node]
|
||||
|
||||
// Find top-left-most boundary
|
||||
let offsetX = Infinity
|
||||
let offsetY = Infinity
|
||||
for (const item of nodes) {
|
||||
if (item.pos == null)
|
||||
throw new TypeError(
|
||||
'Invalid node encountered on clone. `pos` was null.'
|
||||
)
|
||||
if (item.pos[0] < offsetX) offsetX = item.pos[0]
|
||||
if (item.pos[1] < offsetY) offsetY = item.pos[1]
|
||||
}
|
||||
|
||||
if (newSelected.size) {
|
||||
canvas.selectNodes([...newSelected])
|
||||
}
|
||||
|
||||
graph.afterChange()
|
||||
|
||||
canvas.setDirty(true, true)
|
||||
canvas._deserializeItems(canvas._serializeItems(nodes), {
|
||||
position: [offsetX + 5, offsetY + 5]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2384,42 +2365,22 @@ export class LGraphCanvas
|
||||
node &&
|
||||
this.allow_interaction
|
||||
) {
|
||||
let newType = node.type
|
||||
const items = this._deserializeItems(this._serializeItems([node]), {
|
||||
position: node.pos
|
||||
})
|
||||
const cloned = items?.created[0] as LGraphNode | undefined
|
||||
if (!cloned) return
|
||||
|
||||
if (node instanceof SubgraphNode) {
|
||||
const cloned = node.subgraph.clone().asSerialisable()
|
||||
cloned.pos[0] += 5
|
||||
cloned.pos[1] += 5
|
||||
|
||||
const subgraph = graph.createSubgraph(cloned)
|
||||
subgraph.configure(cloned)
|
||||
newType = subgraph.id
|
||||
}
|
||||
|
||||
const node_data = node.clone()?.serialize()
|
||||
if (node_data?.type != null) {
|
||||
// Ensure the cloned node is configured against the correct type (especially for SubgraphNodes)
|
||||
node_data.type = newType
|
||||
const cloned = LiteGraph.createNode(newType)
|
||||
if (cloned) {
|
||||
cloned.configure(node_data)
|
||||
cloned.pos[0] += 5
|
||||
cloned.pos[1] += 5
|
||||
|
||||
if (this.allow_dragnodes) {
|
||||
pointer.onDragStart = (pointer) => {
|
||||
graph.add(cloned, false)
|
||||
this.#startDraggingItems(cloned, pointer)
|
||||
}
|
||||
pointer.onDragEnd = (e) => this.#processDraggedItems(e)
|
||||
} else {
|
||||
// TODO: Check if before/after change are necessary here.
|
||||
graph.beforeChange()
|
||||
graph.add(cloned, false)
|
||||
graph.afterChange()
|
||||
}
|
||||
|
||||
return
|
||||
if (this.allow_dragnodes) {
|
||||
pointer.onDragStart = (pointer) => {
|
||||
this.#startDraggingItems(cloned, pointer)
|
||||
}
|
||||
pointer.onDragEnd = (e) => this.#processDraggedItems(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Node clicked
|
||||
@@ -3963,17 +3924,26 @@ export class LGraphCanvas
|
||||
const { created, nodes, links, reroutes } = results
|
||||
|
||||
// const failedNodes: ISerialisedNode[] = []
|
||||
const subgraphIdMap: Record<string, string> = {}
|
||||
// SubgraphV2: Remove always-clone behaviour
|
||||
//Update subgraph ids
|
||||
for (const subgraphInfo of parsed.subgraphs)
|
||||
subgraphInfo.id = subgraphIdMap[subgraphInfo.id] = createUuidv4()
|
||||
const allNodeInfo: ISerialisedNode[] = [
|
||||
parsed.nodes ? [parsed.nodes] : [],
|
||||
parsed.subgraphs ? parsed.subgraphs.map((s) => s.nodes ?? []) : []
|
||||
].flat(2)
|
||||
for (const nodeInfo of allNodeInfo)
|
||||
if (nodeInfo.type in subgraphIdMap)
|
||||
nodeInfo.type = subgraphIdMap[nodeInfo.type]
|
||||
|
||||
// Subgraphs
|
||||
for (const info of parsed.subgraphs) {
|
||||
// SubgraphV2: Remove always-clone behaviour
|
||||
const originalId = info.id
|
||||
info.id = createUuidv4()
|
||||
|
||||
const subgraph = graph.createSubgraph(info)
|
||||
subgraph.configure(info)
|
||||
results.subgraphs.set(originalId, subgraph)
|
||||
results.subgraphs.set(info.id, subgraph)
|
||||
}
|
||||
for (const info of parsed.subgraphs)
|
||||
results.subgraphs.get(info.id)?.configure(info)
|
||||
|
||||
// Groups
|
||||
for (const info of parsed.groups) {
|
||||
@@ -3985,17 +3955,6 @@ export class LGraphCanvas
|
||||
created.push(group)
|
||||
}
|
||||
|
||||
// Update subgraph ids with nesting
|
||||
function updateSubgraphIds(nodes: { type: string }[]) {
|
||||
for (const info of nodes) {
|
||||
const subgraph = results.subgraphs.get(info.type)
|
||||
if (!subgraph) continue
|
||||
info.type = subgraph.id
|
||||
updateSubgraphIds(subgraph.nodes)
|
||||
}
|
||||
}
|
||||
updateSubgraphIds(parsed.nodes)
|
||||
|
||||
// Nodes
|
||||
for (const info of parsed.nodes) {
|
||||
const node = info.type == null ? null : LiteGraph.createNode(info.type)
|
||||
|
||||
@@ -618,4 +618,17 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
// Call parent serialize method
|
||||
return super.serialize()
|
||||
}
|
||||
override clone() {
|
||||
const clone = super.clone()
|
||||
// force reasign so domWidgets reset ownership
|
||||
// eslint-disable-next-line no-self-assign
|
||||
this.properties.proxyWidgets = this.properties.proxyWidgets
|
||||
|
||||
//TODO: Consider deep cloning subgraphs here.
|
||||
//It's the safest place to prevent creation of linked subgraphs
|
||||
//But the frequency of clone().serialize() calls is likely to result in
|
||||
//pollution of rootGraph.subgraphs
|
||||
|
||||
return clone
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user