mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
fix: unify slot-promoted widgets with PromotedWidgetSlot system
Replace -1 nodeId entries in proxyWidgets with real interior node IDs. Slot-promoted widget copies from _setWidget() now carry sourceNodeId and sourceWidgetName properties, enabling syncPromotedWidgets to convert all entries uniformly to PromotedWidgetSlot instances. - Add sourceNodeId/sourceWidgetName to _setWidget() copies - Pass interiorNode through all _setWidget() call sites - syncPromotedWidgets: convert legacy -1 entries and uncovered copies to PromotedWidgetSlots - proxyWidgets getter: emit real IDs from copies - Remove -1 special cases from TabSubgraphInputs and SubgraphEditor - Remove redundant widgets_values restoration for -1 entries Amp-Thread-ID: https://ampcode.com/threads/T-019c54e5-7ece-752b-8d00-c993fab9ee8e Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -84,13 +84,6 @@ const widgetsList = computed((): NodeWidgetsList => {
|
||||
|
||||
const result: NodeWidgetsList = []
|
||||
for (const [nodeId, widgetName] of proxyWidgetsOrder) {
|
||||
if (nodeId === '-1') {
|
||||
// Native widget on the SubgraphNode itself
|
||||
const widget = node.widgets?.find((w) => w.name === widgetName)
|
||||
if (widget) result.push({ node, widget })
|
||||
continue
|
||||
}
|
||||
// Resolve the interior node and widget from the subgraph
|
||||
const interiorNode = node.subgraph.getNodeById(nodeId)
|
||||
if (!interiorNode) continue
|
||||
const widget = interiorNode.widgets?.find((w) => w.name === widgetName)
|
||||
|
||||
@@ -70,11 +70,6 @@ const activeWidgets = computed<WidgetItem[]>({
|
||||
if (!activeNode.value) return []
|
||||
const node = activeNode.value
|
||||
function mapWidgets([id, name]: [string, string]): WidgetItem[] {
|
||||
if (id === '-1') {
|
||||
const widget = node.widgets.find((w) => w.name === name)
|
||||
if (!widget) return []
|
||||
return [[{ id: -1, title: '(Linked)', type: '' }, widget]]
|
||||
}
|
||||
const wNode = node.subgraph._nodes_by_id[id]
|
||||
if (!wNode?.widgets) return []
|
||||
const widget = wNode.widgets.find((w) => w.name === name)
|
||||
@@ -172,10 +167,21 @@ function showAll() {
|
||||
function hideAll() {
|
||||
const node = activeNode.value
|
||||
if (!node) return
|
||||
// Slot-promoted widgets (exposed via SubgraphInput links) can't be hidden
|
||||
const slotPromoted = new Set<string>()
|
||||
for (const slot of node.subgraph.inputNode.slots) {
|
||||
for (const linkId of slot.linkIds) {
|
||||
const link = node.subgraph.getLink(linkId)
|
||||
if (!link) continue
|
||||
const { inputNode, input } = link.resolve(node.subgraph)
|
||||
if (!inputNode || !input?.widget?.name) continue
|
||||
slotPromoted.add(`${inputNode.id}:${input.widget.name}`)
|
||||
}
|
||||
}
|
||||
proxyWidgets.value = proxyWidgets.value.filter(
|
||||
(propertyItem) =>
|
||||
!filteredActive.value.some(matchesWidgetItem(propertyItem)) ||
|
||||
propertyItem[0] === '-1'
|
||||
slotPromoted.has(`${propertyItem[0]}:${propertyItem[1]}`)
|
||||
)
|
||||
}
|
||||
function showRecommended() {
|
||||
|
||||
@@ -56,32 +56,71 @@ function syncPromotedWidgets(
|
||||
const canvasStore = useCanvasStore()
|
||||
const parsed = parseProxyWidgets(property)
|
||||
|
||||
// Snapshot native widgets before filtering so we can restore them
|
||||
const widgets = node.widgets ?? []
|
||||
|
||||
const nativeWidgets = widgets.filter(
|
||||
(w) => !(w instanceof PromotedWidgetSlot)
|
||||
)
|
||||
|
||||
// Remove existing PromotedWidgetSlot instances and native widgets
|
||||
// that will be re-ordered by the parsed list.
|
||||
// Dispose DOM adapters on slots being removed.
|
||||
// Dispose DOM adapters on existing PromotedWidgetSlots being removed.
|
||||
for (const w of widgets) {
|
||||
if (w instanceof PromotedWidgetSlot) w.disposeDomAdapter()
|
||||
}
|
||||
node.widgets = widgets.filter((w) => {
|
||||
if (w instanceof PromotedWidgetSlot) return false
|
||||
return !parsed.some(([, name]) => w.name === name)
|
||||
|
||||
// Collect slot-promoted copies created by _setWidget() during configure.
|
||||
// These have sourceNodeId/sourceWidgetName set via Object.defineProperties.
|
||||
const copies = widgets.filter(
|
||||
(
|
||||
w
|
||||
): w is IBaseWidget & { sourceNodeId: string; sourceWidgetName: string } =>
|
||||
!(w instanceof PromotedWidgetSlot) &&
|
||||
'sourceNodeId' in w &&
|
||||
'sourceWidgetName' in w
|
||||
)
|
||||
|
||||
// Remove all promoted widgets (both PromotedWidgetSlots and copies)
|
||||
node.widgets = widgets.filter(
|
||||
(w) =>
|
||||
!(w instanceof PromotedWidgetSlot) &&
|
||||
!(copies as IBaseWidget[]).includes(w)
|
||||
)
|
||||
|
||||
// Track which source widgets are covered by the parsed list
|
||||
const covered = new Set<string>()
|
||||
|
||||
// Create PromotedWidgetSlots for all parsed entries.
|
||||
// Legacy `-1` entries are resolved to real IDs via the copies.
|
||||
const newSlots: IBaseWidget[] = parsed.flatMap(([nodeId, widgetName]) => {
|
||||
let resolvedNodeId = nodeId
|
||||
let resolvedWidgetName = widgetName
|
||||
|
||||
if (nodeId === '-1') {
|
||||
const copy = copies.find((w) => w.name === widgetName)
|
||||
if (!copy) return []
|
||||
resolvedNodeId = copy.sourceNodeId
|
||||
resolvedWidgetName = copy.sourceWidgetName
|
||||
}
|
||||
|
||||
covered.add(`${resolvedNodeId}:${resolvedWidgetName}`)
|
||||
return [
|
||||
new PromotedWidgetSlot(
|
||||
node as SubgraphNode,
|
||||
resolvedNodeId,
|
||||
resolvedWidgetName
|
||||
)
|
||||
]
|
||||
})
|
||||
|
||||
// Create new PromotedWidgetSlot for each promoted entry
|
||||
const newSlots: IBaseWidget[] = parsed.flatMap(([nodeId, widgetName]) => {
|
||||
if (nodeId === '-1') {
|
||||
const widget = nativeWidgets.find((w) => w.name === widgetName)
|
||||
return widget ? [widget] : []
|
||||
}
|
||||
return [new PromotedWidgetSlot(node as SubgraphNode, nodeId, widgetName)]
|
||||
})
|
||||
// Add PromotedWidgetSlots for any copies not in the parsed list
|
||||
// (e.g. old workflows that didn't serialize slot-promoted entries)
|
||||
for (const copy of copies) {
|
||||
const key = `${copy.sourceNodeId}:${copy.sourceWidgetName}`
|
||||
if (covered.has(key)) continue
|
||||
newSlots.unshift(
|
||||
new PromotedWidgetSlot(
|
||||
node as SubgraphNode,
|
||||
copy.sourceNodeId,
|
||||
copy.sourceWidgetName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
node.widgets.push(...newSlots)
|
||||
|
||||
canvasStore.canvas?.setDirty(true, true)
|
||||
@@ -103,21 +142,17 @@ const onConfigure = function (
|
||||
|
||||
Object.defineProperty(this.properties, 'proxyWidgets', {
|
||||
get: () =>
|
||||
this.widgets.map((w) =>
|
||||
w instanceof PromotedWidgetSlot
|
||||
? [w.sourceNodeId, w.sourceWidgetName]
|
||||
: ['-1', w.name]
|
||||
),
|
||||
this.widgets.map((w) => {
|
||||
if (w instanceof PromotedWidgetSlot)
|
||||
return [w.sourceNodeId, w.sourceWidgetName]
|
||||
if ('sourceNodeId' in w && 'sourceWidgetName' in w)
|
||||
return [String(w.sourceNodeId), String(w.sourceWidgetName)]
|
||||
return ['-1', w.name]
|
||||
}),
|
||||
set: (value: NodeProperty) => syncPromotedWidgets(this, value)
|
||||
})
|
||||
|
||||
if (serialisedNode.properties?.proxyWidgets) {
|
||||
syncPromotedWidgets(this, serialisedNode.properties.proxyWidgets)
|
||||
const parsed = parseProxyWidgets(serialisedNode.properties.proxyWidgets)
|
||||
serialisedNode.widgets_values?.forEach((v, index) => {
|
||||
if (parsed[index]?.[0] !== '-1') return
|
||||
const widget = this.widgets.find((w) => w.name == parsed[index][1])
|
||||
if (v !== null && widget) widget.value = v
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +93,14 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
const linkId = subgraphInput.linkIds[0]
|
||||
const { inputNode, input } = subgraph.links[linkId].resolve(subgraph)
|
||||
const widget = inputNode?.widgets?.find?.((w) => w.name == name)
|
||||
if (widget)
|
||||
this._setWidget(subgraphInput, existingInput, widget, input?.widget)
|
||||
if (widget && inputNode)
|
||||
this._setWidget(
|
||||
subgraphInput,
|
||||
existingInput,
|
||||
widget,
|
||||
input?.widget,
|
||||
inputNode
|
||||
)
|
||||
return
|
||||
}
|
||||
const input = this.addInput(name, type)
|
||||
@@ -206,7 +212,19 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
if (!widget) return
|
||||
|
||||
const widgetLocator = e.detail.input.widget
|
||||
this._setWidget(subgraphInput, input, widget, widgetLocator)
|
||||
// Resolve the interior node from the subgraph input's link
|
||||
const linkId = subgraphInput.linkIds[0]
|
||||
const link = linkId != null ? this.subgraph.getLink(linkId) : undefined
|
||||
const interiorNode = link?.resolve(this.subgraph).inputNode
|
||||
if (!interiorNode) return
|
||||
|
||||
this._setWidget(
|
||||
subgraphInput,
|
||||
input,
|
||||
widget,
|
||||
widgetLocator,
|
||||
interiorNode
|
||||
)
|
||||
},
|
||||
{ signal }
|
||||
)
|
||||
@@ -323,7 +341,13 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
const widget = inputNode.getWidgetFromSlot(targetInput)
|
||||
if (!widget) continue
|
||||
|
||||
this._setWidget(subgraphInput, input, widget, targetInput.widget)
|
||||
this._setWidget(
|
||||
subgraphInput,
|
||||
input,
|
||||
widget,
|
||||
targetInput.widget,
|
||||
inputNode
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -333,7 +357,8 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
subgraphInput: Readonly<SubgraphInput>,
|
||||
input: INodeInputSlot,
|
||||
widget: Readonly<IBaseWidget>,
|
||||
inputWidget: IWidgetLocator | undefined
|
||||
inputWidget: IWidgetLocator | undefined,
|
||||
interiorNode: LGraphNode
|
||||
) {
|
||||
// Use the first matching widget
|
||||
const promotedWidget =
|
||||
@@ -349,6 +374,16 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
// and copies the result as a data property.
|
||||
const sourceWidget = widget as IBaseWidget
|
||||
Object.defineProperties(promotedWidget, {
|
||||
sourceNodeId: {
|
||||
value: String(interiorNode.id),
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
},
|
||||
sourceWidgetName: {
|
||||
value: sourceWidget.name,
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
},
|
||||
name: {
|
||||
get: () => subgraphInput.name,
|
||||
set() {},
|
||||
|
||||
Reference in New Issue
Block a user