fix: serialize subgraph instance widget values

This commit is contained in:
dante01yoon
2026-04-08 21:01:08 +09:00
parent f9df4f2be5
commit 79279682bf
3 changed files with 57 additions and 26 deletions

View File

@@ -253,7 +253,7 @@ describe('Subgraph proxyWidgets', () => {
expect(subgraphNode.widgets).toHaveLength(0)
})
test('serialize does not produce widgets_values for promoted views', () => {
test('serialize stores widgets_values for promoted views', () => {
const [subgraphNode, innerNodes, innerIds] = setupSubgraph(1)
innerNodes[0].addWidget('text', 'stringWidget', 'value', () => {})
usePromotionStore().setPromotions(
@@ -265,9 +265,7 @@ describe('Subgraph proxyWidgets', () => {
const serialized = subgraphNode.serialize()
// SubgraphNode doesn't set serialize_widgets, so widgets_values is absent.
// Even if it were set, views have serialize: false and would be skipped.
expect(serialized.widgets_values).toBeUndefined()
expect(serialized.widgets_values).toEqual(['value'])
})
test('serialize preserves proxyWidgets in properties', () => {

View File

@@ -91,5 +91,45 @@ describe('SubgraphNode multi-instance widget isolation', () => {
expect(widgets2[0].value).toBe(20)
expect(widgets1[0].serializeValue!(instance1, 0)).toBe(10)
expect(widgets2[0].serializeValue!(instance2, 0)).toBe(20)
expect(instance1.serialize().widgets_values).toEqual([10])
expect(instance2.serialize().widgets_values).toEqual([20])
})
it('round-trips per-instance widget values through serialize and configure', () => {
const subgraph = createTestSubgraph({
inputs: [{ name: 'value', type: 'number' }]
})
const { node } = createNodeWithWidget('TestNode', 0)
subgraph.add(node)
subgraph.inputNode.slots[0].connect(node.inputs[0], node)
const originalInstance = createTestSubgraphNode(subgraph, { id: 301 })
originalInstance.configure({
id: 301,
type: subgraph.id,
pos: [100, 100],
size: [200, 100],
inputs: [],
outputs: [],
mode: 0,
order: 0,
flags: {},
properties: { proxyWidgets: [['-1', 'widget']] },
widgets_values: [33]
})
const serialized = originalInstance.serialize()
const restoredInstance = createTestSubgraphNode(subgraph, { id: 302 })
restoredInstance.configure({
...serialized,
id: 302,
type: subgraph.id
})
const restoredWidget = restoredInstance.widgets?.[0]
expect(restoredWidget?.value).toBe(33)
expect(restoredWidget?.serializeValue?.(restoredInstance, 0)).toBe(33)
})
})

View File

@@ -1600,28 +1600,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
ctx.restore()
}
/**
* Synchronizes widget values from this SubgraphNode instance to the
* corresponding widgets in the subgraph definition before serialization.
* This ensures nested subgraph widget values are preserved when saving.
*/
override serialize(): ISerialisedNode {
// Sync widget values to subgraph definition before serialization.
// Only sync for inputs that are linked to a promoted widget via _widget.
for (const input of this.inputs) {
if (!input._widget) continue
const subgraphInput =
input._subgraphSlot ??
this.subgraph.inputNode.slots.find((slot) => slot.name === input.name)
if (!subgraphInput) continue
const connectedWidgets = subgraphInput.getConnectedWidgets()
for (const connectedWidget of connectedWidgets) {
connectedWidget.value = input._widget.value
}
}
// Write promotion store state back to properties for serialization
const entries = usePromotionStore().getPromotions(
this.rootGraph.id,
@@ -1629,7 +1608,21 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
)
this.properties.proxyWidgets = this._serializeEntries(entries)
return super.serialize()
const serialized = super.serialize()
const views = this._getPromotedViews()
if (views.length > 0) {
serialized.widgets_values = views.map((view) => {
const value = view.serializeValue
? view.serializeValue(this, -1)
: view.value
return value != null && typeof value === 'object'
? JSON.parse(JSON.stringify(value))
: (value ?? null)
})
}
return serialized
}
override clone() {
const clone = super.clone()