Add logic to compute widget.prompted

NOTE: Currently fails to track state with nested subgraphs
The event for "Leaving a graph" does not indicate the direction
This commit is contained in:
Austin Mroz
2025-09-25 10:35:59 -05:00
parent a32fc7a667
commit 3efb76b765
8 changed files with 83 additions and 51 deletions

View File

@@ -28,4 +28,5 @@ export function promoteWidget(widget: IBaseWidget, node: LGraphNode) {
pushWidgets(onode, [`${node.id}`, widget.name])
}
}
widget.promoted = true
}

View File

@@ -1865,13 +1865,13 @@ export class LGraphCanvas
this.#dirty()
}
openSubgraph(subgraph: Subgraph): void {
openSubgraph(subgraph: Subgraph, fromNode: SubgraphNode): void {
const { graph } = this
if (!graph) throw new NullGraphError()
const options = {
bubbles: true,
detail: { subgraph, closingGraph: graph },
detail: { subgraph, closingGraph: graph, fromNode },
cancelable: true
}
const mayContinue = this.canvas.dispatchEvent(
@@ -2792,7 +2792,7 @@ export class LGraphCanvas
if (pos[1] < 0 && !inCollapse) {
node.onNodeTitleDblClick?.(e, pos, this)
} else if (node instanceof SubgraphNode) {
this.openSubgraph(node.subgraph)
this.openSubgraph(node.subgraph, node)
}
node.onDblClick?.(e, pos, this)

View File

@@ -4,6 +4,7 @@ import type { LGraphGroup } from '@/lib/litegraph/src/LGraphGroup'
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
import type { Subgraph } from '@/lib/litegraph/src/subgraph/Subgraph'
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
export interface LGraphCanvasEventMap {
@@ -14,6 +15,11 @@ export interface LGraphCanvasEventMap {
/** The old active graph, or `null` if there was no active graph. */
oldGraph: LGraph | Subgraph | null | undefined
}
'subgraph-opened': {
subgraph: Subgraph
closingGraph: LGraph
fromNode: SubgraphNode
}
'litegraph:canvas':
| { subType: 'before-change' | 'after-change' }

View File

@@ -164,7 +164,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
canvas: LGraphCanvas
): void {
if (button.name === 'enter_subgraph') {
canvas.openSubgraph(this.subgraph)
canvas.openSubgraph(this.subgraph, this)
} else {
super.onTitleButtonClick(button, canvas)
}

View File

@@ -297,6 +297,12 @@ export interface IBaseWidget<
hidden?: boolean
advanced?: boolean
/**
* Set if the node is displayed on the parent subgraphNode
* Promoted widgets have a green border
* @readonly [Computed] This property is computed on graph change
*/
promoted?: boolean
tooltip?: string

View File

@@ -38,6 +38,7 @@ import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
import { getMp3Metadata } from '@/scripts/metadata/mp3'
import { getOggMetadata } from '@/scripts/metadata/ogg'
import { getSvgMetadata } from '@/scripts/metadata/svg'
import { registerProxyWidgets } from '@/scripts/proxyWidget'
import { useDialogService } from '@/services/dialogService'
import { useExtensionService } from '@/services/extensionService'
import { useLitegraphService } from '@/services/litegraphService'
@@ -862,6 +863,7 @@ export class ComfyApp {
}
}
)
registerProxyWidgets(this.canvas)
this.graph.start()

View File

@@ -1,5 +1,9 @@
import { useNodeImage } from '@/composables/node/useNodeImage'
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
import type {
LGraph,
LGraphCanvas,
LGraphNode
} from '@/lib/litegraph/src/litegraph'
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts'
import { disconnectedWidget } from '@/lib/litegraph/src/widgets/DisconnectedWidget'
@@ -10,54 +14,67 @@ import { useCanvasStore } from '@/stores/graphStore'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import { getNodeByExecutionId } from '@/utils/graphTraversalUtil'
const originalOnConfigure = SubgraphNode.prototype.onConfigure
SubgraphNode.prototype.onConfigure = function (serialisedNode) {
if (!this.isSubgraphNode())
throw new Error("Can't add proxyWidgets to non-subgraphNode")
const canvasStore = useCanvasStore()
const subgraphNode = this
//Must give value to proxyWidgets prior to defining or it won't serialize
subgraphNode.properties.proxyWidgets ??= '[]'
let proxyWidgets = subgraphNode.properties.proxyWidgets
originalOnConfigure?.bind(this)?.(serialisedNode)
Object.defineProperty(subgraphNode.properties, 'proxyWidgets', {
get: () => {
return proxyWidgets
},
set: (property: string) => {
const parsed = parseProxyWidgets(property)
const { widgetStates } = useDomWidgetStore()
for (const w of subgraphNode.widgets.filter((w) => isProxyWidget(w))) {
if (w instanceof DOMWidgetImpl && widgetStates.has(w.id)) {
const widgetState = widgetStates.get(w.id)
if (!widgetState) continue
widgetState.active = false
}
export function registerProxyWidgets(canvas: LGraphCanvas) {
//NOTE: canvasStore hasn't been initialized yet
canvas.canvas.addEventListener<'subgraph-opened'>('subgraph-opened', (e) => {
const { subgraph, fromNode } = e.detail
const pw = parseProxyWidgets(fromNode.properties.proxyWidgets)
for (const node of subgraph.nodes) {
for (const widget of node.widgets ?? []) {
widget.promoted = pw.some(([n, w]) => node.id == n && widget.name == w)
}
//NOTE: This does not apply to pushed entries, only initial load
subgraphNode.widgets = subgraphNode.widgets.filter(
(w) => !isProxyWidget(w)
)
for (const [nodeId, widgetName] of parsed) {
const w = addProxyWidget(subgraphNode, `${nodeId}`, widgetName)
if (w instanceof DOMWidgetImpl) {
const widgetState = widgetStates.get(w.id)
if (!widgetState) continue
widgetState.active = true
widgetState.widget = w
}
}
proxyWidgets = property
canvasStore.canvas?.setDirty(true, true)
subgraphNode._setConcreteSlots()
subgraphNode.arrange()
}
})
subgraphNode.properties.proxyWidgets = proxyWidgets
const originalOnConfigure = SubgraphNode.prototype.onConfigure
SubgraphNode.prototype.onConfigure = function onConfigure(serialisedNode) {
if (!this.isSubgraphNode())
throw new Error("Can't add proxyWidgets to non-subgraphNode")
const canvasStore = useCanvasStore()
const subgraphNode = this
//Must give value to proxyWidgets prior to defining or it won't serialize
subgraphNode.properties.proxyWidgets ??= '[]'
let proxyWidgets = subgraphNode.properties.proxyWidgets
originalOnConfigure?.bind(this)?.(serialisedNode)
Object.defineProperty(subgraphNode.properties, 'proxyWidgets', {
get: () => {
return proxyWidgets
},
set: (property: string) => {
const parsed = parseProxyWidgets(property)
const { widgetStates } = useDomWidgetStore()
for (const w of subgraphNode.widgets.filter((w) => isProxyWidget(w))) {
if (w instanceof DOMWidgetImpl && widgetStates.has(w.id)) {
const widgetState = widgetStates.get(w.id)
if (!widgetState) continue
widgetState.active = false
}
}
//NOTE: This does not apply to pushed entries, only initial load
subgraphNode.widgets = subgraphNode.widgets.filter(
(w) => !isProxyWidget(w)
)
for (const [nodeId, widgetName] of parsed) {
const w = addProxyWidget(subgraphNode, `${nodeId}`, widgetName)
if (w instanceof DOMWidgetImpl) {
const widgetState = widgetStates.get(w.id)
if (!widgetState) continue
widgetState.active = true
widgetState.widget = w
}
}
proxyWidgets = property
canvasStore.canvas?.setDirty(true, true)
subgraphNode._setConcreteSlots()
subgraphNode.arrange()
}
})
subgraphNode.properties.proxyWidgets = proxyWidgets
}
}
type Overlay = Partial<IBaseWidget> & {
graph: LGraph
nodeId: string
@@ -89,7 +106,8 @@ function addProxyWidget(
computedHeight: undefined,
afterQueued: undefined,
onRemove: undefined,
node: subgraphNode
node: subgraphNode,
promoted: undefined
}
return addProxyFromOverlay(subgraphNode, overlay)
}

View File

@@ -29,7 +29,6 @@ import { useSettingStore } from '@/stores/settingStore'
import type { ComfyApp } from './app'
import './domWidget'
import './errorNodeWidgets'
import './proxyWidget'
export type ComfyWidgetConstructorV2 = (
node: LGraphNode,