diff --git a/src/extensions/core/index.ts b/src/extensions/core/index.ts index 5354ef4e9..00c925935 100644 --- a/src/extensions/core/index.ts +++ b/src/extensions/core/index.ts @@ -11,6 +11,7 @@ import './maskeditor' import './nodeTemplates' import './noteNode' import './previewAny' +import './proxyWidget' import './rerouteNode' import './saveImageExtraOutput' import './saveMesh' diff --git a/src/extensions/core/proxyWidget.ts b/src/extensions/core/proxyWidget.ts new file mode 100644 index 000000000..c34bfacdc --- /dev/null +++ b/src/extensions/core/proxyWidget.ts @@ -0,0 +1,118 @@ +// @ts-nocheck +// FIXME: typechecking for proxy system + + +import type { LGraphNode } from '@/lib/litegraph/src/litegraph' +import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' +import { useExtensionService } from '@/services/extensionService' +import { useDomWidgetStore } from '@/stores/domWidgetStore' + +useExtensionService().registerExtension({ + name: "Comfy.SubgraphProxyWidgets", + nodeCreated(node: LGraphNode) { + if (node instanceof SubgraphNode) { + setTimeout(() => injectProperty(node),0) + node.addProxyWidget = (nodeId, widgetName) => + addProxyWidget(node, nodeId, widgetName) + } + } +}) +function injectProperty(subgraphNode: SubgraphNode) { + subgraphNode.properties.proxyWidgets ??= [] + const proxyWidgets = subgraphNode.properties.proxyWidgets + Object.defineProperty(subgraphNode.properties, 'proxyWidgets', { + get: () => { + return subgraphNode.widgets + .filter((w) => !!w._overlay) + .map((w) => [w._overlay.nodeId, w._overlay.widgetName]) + }, + set: (property) => { + const { widgetStates } = useDomWidgetStore() + subgraphNode.widgets.forEach((w) => { + if (w.id && widgetStates.has(w.id)) + widgetStates.get(w.id).active = false + }) + //NOTE: This does not apply to pushed entries, only initial load + subgraphNode.widgets = subgraphNode.widgets.filter((w) => !w._overlay) + for (const [nodeId, widgetName] of property) { + const w = addProxyWidget(subgraphNode, `${nodeId}`, widgetName) + if (w.id && widgetStates.has(w.id)) { + const widgetState = widgetStates.get(w.id) + widgetState.active = true + widgetState.widget = w + } + } + //TODO: set dirty canvas + } + }) + subgraphNode.properties.proxyWidgets = proxyWidgets +} + +function addProxyWidget(subgraphNode: SubgraphNode, nodeId: string, widgetName: string) { + const overlay = { nodeId, widgetName } + return addProxyFromOverlay(subgraphNode, { __proto__: overlay }) +} +function addProxyFromOverlay(subgraphNode: SubgraphNode, overlay: object) { + overlay.label = `${overlay.nodeId}: ${overlay.widgetName}` + overlay.graph = subgraphNode.subgraph + overlay.isProxyWidget = true + //TODO: Add minimal caching for linkedWidget? + //use a weakref and only trigger recalc on calls when undefined? + //TODO: call toConcrete when resolved and hold reference? + function linkedWidget(graph, nodeId = '', widgetName) { + const g = graph + let n = undefined + for (const id of nodeId.split(':')) { + n = g?._nodes_by_id?.[id] + graph = n?.subgraph + } + if (!n) return + return n.widgets.find((w) => w.name === widgetName) + } + let lw = undefined + const handler = Object.fromEntries( + ['get', 'set', 'getPrototypeOf', 'ownKeys', 'has'].map((s) => { + const func = function (t, p, ...rest) { + if (s == 'get' && p == '_overlay') return overlay + if (!lw) { + lw = linkedWidget(overlay.graph, overlay.nodeId, overlay.widgetName) + } + if (s == 'get' && p == 'node') { + return subgraphNode + } + if (s == 'set' && p == 'computedDisabled') { + //ignore setting, calc actual + lw.computedDisabled = + lw.disabled || lw.node.getSlotFromWidget(lw)?.link != null + return true + } + //NOTE: p may be undefined + let r = rest.at(-1) + if ( + [ + 'y', + 'last_y', + 'width', + 'computedHeight', + 'afterQueued', + 'beforeQueued', + 'onRemove', + 'isProxyWidget', + 'label' + ].includes(p) + ) + t = overlay + else { + t = lw + if (!t) t = { __proto__: overlay, draw: drawDisconnected } + if (p == 'value') r = t + } + return Reflect[s](t, p, ...rest.slice(0, -1), r) + } + return [s, func] + }) + ) + const w = new Proxy(overlay, handler) + subgraphNode.widgets.push(w) + return w +} diff --git a/src/lib/litegraph/src/subgraph/SubgraphNode.ts b/src/lib/litegraph/src/subgraph/SubgraphNode.ts index 11c2bb134..d87e49658 100644 --- a/src/lib/litegraph/src/subgraph/SubgraphNode.ts +++ b/src/lib/litegraph/src/subgraph/SubgraphNode.ts @@ -23,7 +23,6 @@ import type { import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import type { UUID } from '@/lib/litegraph/src/utils/uuid' import { toConcreteWidget } from '@/lib/litegraph/src/widgets/widgetMap' -import { useDomWidgetStore } from '@/stores/domWidgetStore' import { type ExecutableLGraphNode, @@ -149,8 +148,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { ) this.type = subgraph.id - //FIXME: This breaks subgraph conversion - //this.configure(instanceData) + this.configure(instanceData) this.addTitleButton({ name: 'enter_subgraph', @@ -307,35 +305,6 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { break } } - //ensure proxyWidgets is enumerated during serialization - this.properties.proxyWidgets ??= [] - const proxyWidgets = this.properties.proxyWidgets - Object.defineProperty(this.properties, 'proxyWidgets', { - get: () => { - return this.widgets - .filter((w) => !!w._overlay) - .map((w) => [w._overlay.nodeId, w._overlay.widgetName]) - }, - set: (property) => { - const { widgetStates } = useDomWidgetStore() - this.widgets.forEach((w) => { - if (w.id && widgetStates.has(w.id)) - widgetStates.get(w.id).active = false - }) - //NOTE: This does not apply to pushed entries, only initial load - this.widgets = this.widgets.filter((w) => !w._overlay) - for (const [nodeId, widgetName] of property) { - const w = this.addProxyWidget(`${nodeId}`, widgetName) - if (w.id && widgetStates.has(w.id)) { - const widgetState = widgetStates.get(w.id) - widgetState.active = true - widgetState.widget = w - } - } - //TODO: set dirty canvas - } - }) - this.properties.proxyWidgets = proxyWidgets } #setWidget( @@ -607,73 +576,4 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph { // Call parent serialize method return super.serialize() } - addProxyWidget(nodeId: string, widgetName: string) { - const overlay = { nodeId, widgetName } - return this.addProxyFromOverlay({ __proto__: overlay }) - } - addProxyFromOverlay(overlay: object) { - overlay.label = `${overlay.nodeId}: ${overlay.widgetName}` - overlay.graph = this.subgraph - overlay.isProxyWidget = true - //TODO: Add minimal caching for linkedWidget? - //use a weakref and only trigger recalc on calls when undefined? - //TODO: call toConcrete when resolved and hold reference? - const subgraphNode = this - function linkedWidget(graph, nodeId = '', widgetName) { - const g = graph - let n = undefined - for (const id of nodeId.split(':')) { - n = g?._nodes_by_id?.[id] - graph = n?.subgraph - } - if (!n) return - return n.widgets.find((w) => w.name === widgetName) - } - let lw = undefined - const handler = Object.fromEntries( - ['get', 'set', 'getPrototypeOf', 'ownKeys', 'has'].map((s) => { - const func = function (t, p, ...rest) { - if (s == 'get' && p == '_overlay') return overlay - if (!lw) { - lw = linkedWidget(overlay.graph, overlay.nodeId, overlay.widgetName) - } - if (s == 'get' && p == 'node') { - return subgraphNode - } - if (s == 'set' && p == 'computedDisabled') { - //ignore setting, calc actual - lw.computedDisabled = - lw.disabled || lw.node.getSlotFromWidget(lw)?.link != null - return true - } - //NOTE: p may be undefined - let r = rest.at(-1) - if ( - [ - 'y', - 'last_y', - 'width', - 'computedHeight', - 'afterQueued', - 'beforeQueued', - 'onRemove', - 'isProxyWidget', - 'label' - ].includes(p) - ) - t = overlay - else { - t = lw - if (!t) t = { __proto__: overlay, draw: drawDisconnected } - if (p == 'value') r = t - } - return Reflect[s](t, p, ...rest.slice(0, -1), r) - } - return [s, func] - }) - ) - const w = new Proxy(overlay, handler) - this.widgets.push(w) - return w - } } diff --git a/src/scripts/app.ts b/src/scripts/app.ts index ade027255..dd897cfaf 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -31,6 +31,7 @@ import { isComboInputSpecV1, isComboInputSpecV2 } from '@/schemas/nodeDefSchema' +import { type BaseDOMWidget, DOMWidgetImpl } from '@/scripts/domWidget' import { getFromWebmFile } from '@/scripts/metadata/ebml' import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf' import { getFromIsobmffFile } from '@/scripts/metadata/isobmff' @@ -839,19 +840,21 @@ export class ComfyApp { (e) => { // Assertion: Not yet defined in litegraph. const { newGraph } = e.detail - - const widgetIds = {} + + const widgetIds: Record> = {} const widgetStore = useDomWidgetStore() - + for (const node of newGraph.nodes) for (const w of node.widgets ?? []) - if (w.id) + if (w instanceof DOMWidgetImpl && w.id) widgetIds[w.id] = w - + // Assertions: UnwrapRef for (const widgetId of widgetStore.widgetStates.keys()) { const widgetState = widgetStore - .widgetStates.get(widgetId) + .widgetStates.get(widgetId) + //Unreachable, but required for type safety + if (!widgetState) continue if (widgetId in widgetIds) { widgetState.active = true widgetState.widget = widgetIds[widgetId]