Files
ComfyUI_frontend/src/extensions/core/proxyWidget.ts
2025-09-13 10:33:31 -05:00

127 lines
4.1 KiB
TypeScript

// @ts-nocheck
// FIXME: typechecking for proxy system
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import { disconnectedWidget } from '@/lib/litegraph/src/widgets/DisconnectedWidget'
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)
}
}
})
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 resolveLinkedWidget(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)
}
function getWidgetRef(overlay) {
const lw = resolveLinkedWidget(
overlay.graph,
overlay.nodeId,
overlay.widgetName
)
if (lw) return new WeakRef(lw)
return { deref: () => undefined }
}
function addProxyFromOverlay(subgraphNode: SubgraphNode, overlay: object) {
overlay.label = `${overlay.nodeId}: ${overlay.widgetName}`
overlay.graph = subgraphNode.subgraph
overlay.isProxyWidget = true
//TODO: call toConcrete when resolved and hold reference?
//NOTE: From testing, WeakRefs don't appear to actually drop
//Memory management doesn't matter here and this is worthless for notification
let linkedWidget = getWidgetRef(overlay)
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 (!linkedWidget.deref()) linkedWidget = getWidgetRef(overlay)
const bw = linkedWidget.deref() ?? disconnectedWidget
if (s == 'get' && p == 'node') {
return subgraphNode
}
if (s == 'set' && p == 'computedDisabled') {
//ignore setting, calc actual
bw.computedDisabled =
bw.disabled || bw.node.getSlotFromWidget(bw)?.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 = bw
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
}