mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
fix: delegate slot-promoted widget value to interior widget
Replace Object.assign (which silently copies getter return values as data properties) with Object.defineProperties to properly install accessor pairs. The value accessor delegates to the source interior widget, sharing the same store entry for both canvas and RSP rendering. Reverts the PromotedWidgetSlot conversion approach in favor of this simpler fix. Amp-Thread-ID: https://ampcode.com/threads/T-019c54cb-77f0-736a-a619-a530cdb8da86 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { PromotedWidgetSlot } from '@/core/graph/subgraph/PromotedWidgetSlot'
|
||||
import { promoteRecommendedWidgets } from '@/core/graph/subgraph/proxyWidgetUtils'
|
||||
import { parseProxyWidgets } from '@/core/schemas/proxyWidget'
|
||||
import type { NodeId, NodeProperty } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { ISerialisedNode } from '@/lib/litegraph/src/types/serialisation'
|
||||
@@ -74,69 +74,21 @@ function syncPromotedWidgets(
|
||||
return !parsed.some(([, name]) => w.name === name)
|
||||
})
|
||||
|
||||
// Create PromotedWidgetSlot for each promoted entry.
|
||||
// For '-1' (slot-promoted) entries, resolve the interior source via
|
||||
// subgraph input links so they use the same delegation as context-menu
|
||||
// promoted widgets.
|
||||
const subgraphNode = node as SubgraphNode
|
||||
// Map from slot name → PromotedWidgetSlot for syncing input._widget refs
|
||||
const slotsByName = new Map<string, PromotedWidgetSlot>()
|
||||
// Create new PromotedWidgetSlot for each promoted entry
|
||||
const newSlots: IBaseWidget[] = parsed.flatMap(([nodeId, widgetName]) => {
|
||||
if (nodeId === '-1') {
|
||||
const source = resolveSlotPromotedSource(subgraphNode, widgetName)
|
||||
if (source) {
|
||||
const slot = new PromotedWidgetSlot(subgraphNode, source[0], source[1])
|
||||
slotsByName.set(widgetName, slot)
|
||||
return [slot]
|
||||
}
|
||||
const widget = nativeWidgets.find((w) => w.name === widgetName)
|
||||
return widget ? [widget] : []
|
||||
}
|
||||
return [new PromotedWidgetSlot(subgraphNode, nodeId, widgetName)]
|
||||
return [new PromotedWidgetSlot(node as SubgraphNode, nodeId, widgetName)]
|
||||
})
|
||||
node.widgets.push(...newSlots)
|
||||
|
||||
// Sync input._widget references so litegraph lifecycle events
|
||||
// (removal, renaming) target the new PromotedWidgetSlot instances.
|
||||
for (const input of subgraphNode.inputs) {
|
||||
const slot = slotsByName.get(input.name)
|
||||
if (slot) input._widget = slot
|
||||
}
|
||||
|
||||
canvasStore.canvas?.setDirty(true, true)
|
||||
node._setConcreteSlots()
|
||||
node.arrange()
|
||||
}
|
||||
|
||||
/**
|
||||
* Follows the subgraph input slot's link chain to find the interior
|
||||
* node ID and widget name for a slot-promoted widget.
|
||||
*/
|
||||
function resolveSlotPromotedSource(
|
||||
subgraphNode: SubgraphNode,
|
||||
slotName: string
|
||||
): [NodeId, string] | null {
|
||||
const subgraphInput = subgraphNode.subgraph.inputNode.slots.find(
|
||||
(slot) => slot.name === slotName
|
||||
)
|
||||
if (!subgraphInput) return null
|
||||
|
||||
for (const linkId of subgraphInput.linkIds) {
|
||||
const link = subgraphNode.subgraph.getLink(linkId)
|
||||
if (!link) continue
|
||||
|
||||
const { inputNode } = link.resolve(subgraphNode.subgraph)
|
||||
if (!inputNode) continue
|
||||
|
||||
const targetInput = inputNode.inputs.find((inp) => inp.link === linkId)
|
||||
if (!targetInput) continue
|
||||
|
||||
const widget = inputNode.getWidgetFromSlot(targetInput)
|
||||
if (widget) return [String(inputNode.id) as NodeId, widget.name]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const originalOnConfigure = SubgraphNode.prototype.onConfigure
|
||||
const onConfigure = function (
|
||||
this: LGraphNode,
|
||||
@@ -162,14 +114,10 @@ const onConfigure = function (
|
||||
if (serialisedNode.properties?.proxyWidgets) {
|
||||
syncPromotedWidgets(this, serialisedNode.properties.proxyWidgets)
|
||||
const parsed = parseProxyWidgets(serialisedNode.properties.proxyWidgets)
|
||||
const subNode = this as SubgraphNode
|
||||
serialisedNode.widgets_values?.forEach((v, index) => {
|
||||
if (parsed[index]?.[0] !== '-1' || v === null) return
|
||||
const slotName = parsed[index][1]
|
||||
const widget =
|
||||
this.widgets.find((w) => w.name === slotName) ??
|
||||
subNode.inputs.find((inp) => inp.name === slotName)?._widget
|
||||
if (widget) widget.value = v
|
||||
if (parsed[index]?.[0] !== '-1') return
|
||||
const widget = this.widgets.find((w) => w.name == parsed[index][1])
|
||||
if (v !== null && widget) widget.value = v
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,47 +343,43 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
if (widget instanceof AssetWidget)
|
||||
promotedWidget.options.nodeType ??= widget.node.type
|
||||
|
||||
Object.assign(promotedWidget, {
|
||||
get name() {
|
||||
return subgraphInput.name
|
||||
// Delegate value to the interior widget so both the SubgraphNode copy
|
||||
// and the interior widget share the same store entry.
|
||||
// Object.defineProperty is required — Object.assign invokes getters
|
||||
// and copies the result as a data property.
|
||||
const sourceWidget = widget as IBaseWidget
|
||||
Object.defineProperties(promotedWidget, {
|
||||
name: {
|
||||
get: () => subgraphInput.name,
|
||||
set() {},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
},
|
||||
set name(value) {
|
||||
console.warn(
|
||||
'Promoted widget: setting name is not allowed',
|
||||
this,
|
||||
value
|
||||
)
|
||||
localized_name: {
|
||||
get: () => subgraphInput.localized_name,
|
||||
set() {},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
},
|
||||
get localized_name() {
|
||||
return subgraphInput.localized_name
|
||||
value: {
|
||||
get: () => sourceWidget.value,
|
||||
set: (v) => {
|
||||
sourceWidget.value = v
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
},
|
||||
set localized_name(value) {
|
||||
console.warn(
|
||||
'Promoted widget: setting localized_name is not allowed',
|
||||
this,
|
||||
value
|
||||
)
|
||||
label: {
|
||||
get: () => subgraphInput.label,
|
||||
set() {},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
},
|
||||
get label() {
|
||||
return subgraphInput.label
|
||||
},
|
||||
set label(value) {
|
||||
console.warn(
|
||||
'Promoted widget: setting label is not allowed',
|
||||
this,
|
||||
value
|
||||
)
|
||||
},
|
||||
get tooltip() {
|
||||
// Preserve the original widget's tooltip for promoted widgets
|
||||
return widget.tooltip
|
||||
},
|
||||
set tooltip(value) {
|
||||
console.warn(
|
||||
'Promoted widget: setting tooltip is not allowed',
|
||||
this,
|
||||
value
|
||||
)
|
||||
tooltip: {
|
||||
get: () => widget.tooltip,
|
||||
set() {},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user