diff --git a/src/core/graph/subgraph/proxyWidget.ts b/src/core/graph/subgraph/proxyWidget.ts index 26d3d8870c..b97ae74cf5 100644 --- a/src/core/graph/subgraph/proxyWidget.ts +++ b/src/core/graph/subgraph/proxyWidget.ts @@ -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) { diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index bae17e74b7..fac938707f 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -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() - - const fApplyMultiNode = function ( - node: LGraphNode, - newNodes: Set - ): 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] - if (newSelected.size) { - canvas.selectNodes([...newSelected]) + // 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] } - 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 - - if (node instanceof SubgraphNode) { - const cloned = node.subgraph.clone().asSerialisable() - - 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 + const items = this._deserializeItems(this._serializeItems([node]), { + position: node.pos + }) + const cloned = items?.created[0] as LGraphNode | undefined + if (!cloned) return - 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() - } + cloned.pos[0] += 5 + cloned.pos[1] += 5 - 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 = {} + // 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) diff --git a/src/lib/litegraph/src/subgraph/SubgraphNode.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.ts index 81653c1b4b..08349dee0f 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphNode.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.ts @@ -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 + } }