Remove redundant state from Widget store.

This commit is contained in:
Alexander Brown
2026-02-04 10:54:35 -08:00
parent 616b2d68e0
commit 6e3d6f6c97
10 changed files with 40 additions and 129 deletions

View File

@@ -38,10 +38,12 @@ describe('Node Reactivity', () => {
expect((widget as any)._nodeId).toBe(node.id)
// Initial value should be in store after setNodeId was called
expect(store.get(node.id, 'testnum')).toBe(2)
expect(store.getWidget(node.id, 'testnum')?.value).toBe(2)
const onValueChange = vi.fn()
const widgetValue = computed(() => store.get(node.id, 'testnum'))
const widgetValue = computed(
() => store.getWidget(node.id, 'testnum')?.value
)
watch(widgetValue, onValueChange)
widget.value = 42
@@ -62,7 +64,9 @@ describe('Node Reactivity', () => {
})
await nextTick()
const widgetValue = computed(() => store.get(node.id, 'testnum'))
const widgetValue = computed(
() => store.getWidget(node.id, 'testnum')?.value
)
watch(widgetValue, onValueChange)
node.widgets![0].value = 99

View File

@@ -240,15 +240,6 @@ export function extractVueNodeData(node: LGraphNode): VueNodeData {
// Extract safe widget data
const slotMetadata = new Map<string, WidgetSlotMetadata>()
const reactiveWidgets = shallowReactive<IBaseWidget[]>(node.widgets ?? [])
Object.defineProperty(node, 'widgets', {
get() {
return reactiveWidgets
},
set(v) {
reactiveWidgets.splice(0, reactiveWidgets.length, ...v)
}
})
const reactiveInputs = shallowReactive<INodeInputSlot[]>(node.inputs ?? [])
Object.defineProperty(node, 'inputs', {
get() {

View File

@@ -54,7 +54,6 @@ describe('useWidget', () => {
expect(value.value).toBe(42)
value.value = 100
expect(store.get(1, 'test_widget')).toBe(100)
expect(store.getWidget(1, 'test_widget')?.value).toBe(100)
})
@@ -95,7 +94,7 @@ describe('useWidget', () => {
expect(value.value).toBe(false)
expect(isHidden.value).toBe(false)
store.set(1, 'test_widget', true)
store.getWidget(1, 'test_widget')!.value = true
expect(value.value).toBe(true)
store.setHidden(1, 'test_widget', true)

View File

@@ -27,7 +27,9 @@ export function useWidget(
const value = computed({
get: () => widget.value?.value,
set: (v: unknown) => store.set(toValue(nodeId), toValue(widgetName), v)
set: (v: unknown) => {
if (widget.value) widget.value.value = v
}
})
const isHidden = computed(() => widget.value?.hidden ?? false)

View File

@@ -111,9 +111,10 @@ describe('BaseWidget store integration', () => {
widget.setNodeId(1)
widget.value = 99
expect(store.get(1, 'valueWidget')).toBe(99)
expect(store.getWidget(1, 'valueWidget')?.value).toBe(99)
store.set(1, 'valueWidget', 55)
const state = store.getWidget(1, 'valueWidget')!
state.value = 55
expect(widget.value).toBe(55)
})
})
@@ -158,11 +159,11 @@ describe('BaseWidget store integration', () => {
expect(state?.label).toBeUndefined()
})
it('registers widget value in values map', () => {
it('registers widget value accessible via getWidget', () => {
const widget = createTestWidget(node, { name: 'valuesWidget', value: 77 })
widget.setNodeId(1)
expect(store.get(1, 'valuesWidget')).toBe(77)
expect(store.getWidget(1, 'valuesWidget')?.value).toBe(77)
})
})

View File

@@ -184,7 +184,7 @@ export abstract class BaseWidget<
get value(): TWidget['value'] {
if (this._nodeId !== undefined) {
const store = useWidgetValueStore()
const storeValue = store.get(this._nodeId, this.name)
const storeValue = store.getWidget(this._nodeId, this.name)?.value
if (storeValue !== undefined) {
return storeValue as TWidget['value']
}
@@ -196,7 +196,8 @@ export abstract class BaseWidget<
this._internalValue = value
if (this._nodeId !== undefined) {
const store = useWidgetValueStore()
store.set(this._nodeId, this.name, value)
const state = store.getWidget(this._nodeId, this.name)
if (state) state.value = value
}
}

View File

@@ -221,7 +221,7 @@ const nodeBadges = computed<NodeBadgeProps[]>(() => {
if (relevantNames.length > 0 && nodeData?.id != null) {
for (const name of relevantNames) {
// Access value from store to create reactive dependency
void widgetStore.get(nodeData.id, name)
void widgetStore.getWidget(nodeData.id, name)?.value
}
}
// Access input connections for regular inputs

View File

@@ -178,7 +178,7 @@ const processedWidgets = computed((): ProcessedWidget[] => {
const widgetState = widgetValueStore.getWidget(nodeId, widget.name)
// Get value from store (falls back to undefined if not registered)
const value = widgetValueStore.get(nodeId, widget.name) as WidgetValue
const value = widgetState?.value as WidgetValue
// Build options from store state, with slot-linked override for disabled
const storeOptions = widgetState?.options ?? {}
@@ -208,7 +208,7 @@ const processedWidgets = computed((): ProcessedWidget[] => {
function updateHandler(newValue: WidgetValue) {
// Update value in store
widgetValueStore.set(nodeId, widget.name, newValue)
if (widgetState) widgetState.value = newValue
// Invoke LiteGraph callback wrapper (handles triggerDraw, etc.)
widget.callback?.(newValue)
}

View File

@@ -9,56 +9,32 @@ describe('useWidgetValueStore', () => {
setActivePinia(createTestingPinia({ stubActions: false }))
})
describe('value management (legacy API)', () => {
it('stores and retrieves values', () => {
describe('widgetState.value access', () => {
it('getWidget returns undefined for unregistered widget', () => {
const store = useWidgetValueStore()
store.set('node-1', 'seed', 12345)
expect(store.get('node-1', 'seed')).toBe(12345)
expect(store.getWidget('missing', 'widget')).toBeUndefined()
})
it('returns undefined for missing values', () => {
it('widgetState.value can be read and written directly', () => {
const store = useWidgetValueStore()
expect(store.get('missing', 'widget')).toBeUndefined()
})
const state = store.registerWidget('node-1', 'seed', 'number', 100)
expect(state.value).toBe(100)
it('removes single widget value', () => {
const store = useWidgetValueStore()
store.set('node-1', 'seed', 100)
store.remove('node-1', 'seed')
expect(store.get('node-1', 'seed')).toBeUndefined()
})
it('removes all widgets for a node', () => {
const store = useWidgetValueStore()
store.set('node-1', 'seed', 1)
store.set('node-1', 'steps', 20)
store.set('node-2', 'seed', 2)
store.removeNode('node-1')
expect(store.get('node-1', 'seed')).toBeUndefined()
expect(store.get('node-1', 'steps')).toBeUndefined()
expect(store.get('node-2', 'seed')).toBe(2)
})
it('overwrites existing values', () => {
const store = useWidgetValueStore()
store.set('node-1', 'seed', 100)
store.set('node-1', 'seed', 200)
expect(store.get('node-1', 'seed')).toBe(200)
state.value = 200
expect(store.getWidget('node-1', 'seed')?.value).toBe(200)
})
it('stores different value types', () => {
const store = useWidgetValueStore()
store.set('node-1', 'text', 'hello')
store.set('node-1', 'number', 42)
store.set('node-1', 'boolean', true)
store.set('node-1', 'array', [1, 2, 3])
store.registerWidget('node-1', 'text', 'string', 'hello')
store.registerWidget('node-1', 'number', 'number', 42)
store.registerWidget('node-1', 'boolean', 'toggle', true)
store.registerWidget('node-1', 'array', 'combo', [1, 2, 3])
expect(store.get('node-1', 'text')).toBe('hello')
expect(store.get('node-1', 'number')).toBe(42)
expect(store.get('node-1', 'boolean')).toBe(true)
expect(store.get('node-1', 'array')).toEqual([1, 2, 3])
expect(store.getWidget('node-1', 'text')?.value).toBe('hello')
expect(store.getWidget('node-1', 'number')?.value).toBe(42)
expect(store.getWidget('node-1', 'boolean')?.value).toBe(true)
expect(store.getWidget('node-1', 'array')?.value).toEqual([1, 2, 3])
})
})
@@ -100,12 +76,6 @@ describe('useWidgetValueStore', () => {
expect(state.options).toEqual({ multiline: true })
})
it('also sets the value in the values map', () => {
const store = useWidgetValueStore()
store.registerWidget('node-1', 'seed', 'number', 42)
expect(store.get('node-1', 'seed')).toBe(42)
})
it('unregisters a widget', () => {
const store = useWidgetValueStore()
store.registerWidget('node-1', 'seed', 'number', 100)
@@ -113,7 +83,6 @@ describe('useWidgetValueStore', () => {
store.unregisterWidget('node-1', 'seed')
expect(store.getWidget('node-1', 'seed')).toBeUndefined()
expect(store.get('node-1', 'seed')).toBeUndefined()
})
it('unregisters all widgets for a node', () => {
@@ -126,9 +95,7 @@ describe('useWidgetValueStore', () => {
expect(store.getWidget('node-1', 'seed')).toBeUndefined()
expect(store.getWidget('node-1', 'steps')).toBeUndefined()
expect(store.getWidget('node-2', 'seed')).toBeDefined()
expect(store.get('node-1', 'seed')).toBeUndefined()
expect(store.get('node-2', 'seed')).toBe(2)
expect(store.getWidget('node-2', 'seed')?.value).toBe(2)
})
})
@@ -247,22 +214,4 @@ describe('useWidgetValueStore', () => {
expect(() => store.setLabel('missing', 'widget', 'test')).not.toThrow()
})
})
describe('value sync between APIs', () => {
it('set() updates registered widget state value', () => {
const store = useWidgetValueStore()
store.registerWidget('node-1', 'seed', 'number', 100)
store.set('node-1', 'seed', 200)
expect(store.get('node-1', 'seed')).toBe(200)
expect(store.getWidget('node-1', 'seed')?.value).toBe(200)
})
it('set() works for unregistered widgets', () => {
const store = useWidgetValueStore()
store.set('node-1', 'seed', 100)
expect(store.get('node-1', 'seed')).toBe(100)
})
})
})

View File

@@ -24,39 +24,12 @@ export interface WidgetState {
}
export const useWidgetValueStore = defineStore('widgetValue', () => {
const values = ref(new Map<WidgetKey, unknown>())
const widgetStates = ref(new Map<WidgetKey, WidgetState>())
function makeKey(nodeId: NodeId, widgetName: string): WidgetKey {
return `${nodeId}:${widgetName}`
}
function get(nodeId: NodeId, widgetName: string): unknown {
return values.value.get(makeKey(nodeId, widgetName))
}
function set(nodeId: NodeId, widgetName: string, value: unknown): void {
const key = makeKey(nodeId, widgetName)
values.value.set(key, value)
const state = widgetStates.value.get(key)
if (state) {
state.value = value
}
}
function remove(nodeId: NodeId, widgetName: string): void {
values.value.delete(makeKey(nodeId, widgetName))
}
function removeNode(nodeId: NodeId): void {
const prefix = `${nodeId}:`
for (const key of values.value.keys()) {
if (key.startsWith(prefix)) {
values.value.delete(key)
}
}
}
function registerWidget(
nodeId: NodeId,
name: string,
@@ -84,14 +57,11 @@ export const useWidgetValueStore = defineStore('widgetValue', () => {
serialize: options.serialize ?? true
}
widgetStates.value.set(key, state)
values.value.set(key, value)
return state
}
function unregisterWidget(nodeId: NodeId, widgetName: string): void {
const key = makeKey(nodeId, widgetName)
widgetStates.value.delete(key)
values.value.delete(key)
widgetStates.value.delete(makeKey(nodeId, widgetName))
}
function unregisterNode(nodeId: NodeId): void {
@@ -101,7 +71,6 @@ export const useWidgetValueStore = defineStore('widgetValue', () => {
widgetStates.value.delete(key)
}
}
removeNode(nodeId)
}
function getWidget(
@@ -190,12 +159,7 @@ export const useWidgetValueStore = defineStore('widgetValue', () => {
}
return {
values,
widgetStates,
get,
set,
remove,
removeNode,
registerWidget,
unregisterWidget,
unregisterNode,