mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
feat: expand widgetValueStore with full widget state management
Add WidgetState interface and widgetStates Map alongside existing values Map to support full widget lifecycle management: - WidgetState: nodeId, name, type, value, label, hidden, disabled, advanced, promoted, options, serialize - Registration: registerWidget(), unregisterWidget(), unregisterNode() - Getters: getWidget(), getNodeWidgets(), getVisibleWidgets(), getAdvancedWidgets(), getPromotedWidgets() - Setters: setHidden(), setDisabled(), setAdvanced(), setPromoted(), setLabel() Existing get/set/remove/removeNode API preserved for backward compat. set() now syncs value to WidgetState when widget is registered. Amp-Thread-ID: https://ampcode.com/threads/T-019c2626-df19-75cf-b9c9-11fe9735083d Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -9,54 +9,260 @@ describe('useWidgetValueStore', () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('stores and retrieves values', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.set('node-1', 'seed', 12345)
|
||||
expect(store.get('node-1', 'seed')).toBe(12345)
|
||||
describe('value management (legacy API)', () => {
|
||||
it('stores and retrieves values', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.set('node-1', 'seed', 12345)
|
||||
expect(store.get('node-1', 'seed')).toBe(12345)
|
||||
})
|
||||
|
||||
it('returns undefined for missing values', () => {
|
||||
const store = useWidgetValueStore()
|
||||
expect(store.get('missing', 'widget')).toBeUndefined()
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
})
|
||||
})
|
||||
|
||||
it('returns undefined for missing values', () => {
|
||||
const store = useWidgetValueStore()
|
||||
expect(store.get('missing', 'widget')).toBeUndefined()
|
||||
describe('widget registration', () => {
|
||||
it('registers a widget with default options', () => {
|
||||
const store = useWidgetValueStore()
|
||||
const state = store.registerWidget('node-1', 'seed', 'number', 12345)
|
||||
|
||||
expect(state.nodeId).toBe('node-1')
|
||||
expect(state.name).toBe('seed')
|
||||
expect(state.type).toBe('number')
|
||||
expect(state.value).toBe(12345)
|
||||
expect(state.hidden).toBe(false)
|
||||
expect(state.disabled).toBe(false)
|
||||
expect(state.advanced).toBe(false)
|
||||
expect(state.promoted).toBe(false)
|
||||
expect(state.serialize).toBe(true)
|
||||
expect(state.options).toEqual({})
|
||||
})
|
||||
|
||||
it('registers a widget with custom options', () => {
|
||||
const store = useWidgetValueStore()
|
||||
const state = store.registerWidget('node-1', 'prompt', 'string', 'test', {
|
||||
label: 'Prompt Text',
|
||||
hidden: true,
|
||||
disabled: true,
|
||||
advanced: true,
|
||||
promoted: true,
|
||||
serialize: false,
|
||||
widgetOptions: { multiline: true }
|
||||
})
|
||||
|
||||
expect(state.label).toBe('Prompt Text')
|
||||
expect(state.hidden).toBe(true)
|
||||
expect(state.disabled).toBe(true)
|
||||
expect(state.advanced).toBe(true)
|
||||
expect(state.promoted).toBe(true)
|
||||
expect(state.serialize).toBe(false)
|
||||
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)
|
||||
|
||||
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', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 1)
|
||||
store.registerWidget('node-1', 'steps', 'number', 20)
|
||||
store.registerWidget('node-2', 'seed', 'number', 2)
|
||||
|
||||
store.unregisterNode('node-1')
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
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()
|
||||
describe('widget getters', () => {
|
||||
it('getWidget returns widget state', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 100)
|
||||
|
||||
const widget = store.getWidget('node-1', 'seed')
|
||||
expect(widget).toBeDefined()
|
||||
expect(widget?.name).toBe('seed')
|
||||
expect(widget?.value).toBe(100)
|
||||
})
|
||||
|
||||
it('getWidget returns undefined for missing widget', () => {
|
||||
const store = useWidgetValueStore()
|
||||
expect(store.getWidget('missing', 'widget')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('getNodeWidgets returns all widgets for a node', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 1)
|
||||
store.registerWidget('node-1', 'steps', 'number', 20)
|
||||
store.registerWidget('node-2', 'cfg', 'number', 7)
|
||||
|
||||
const widgets = store.getNodeWidgets('node-1')
|
||||
expect(widgets).toHaveLength(2)
|
||||
expect(widgets.map((w) => w.name).sort()).toEqual(['seed', 'steps'])
|
||||
})
|
||||
|
||||
it('getVisibleWidgets filters out hidden widgets', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'visible', 'number', 1)
|
||||
store.registerWidget('node-1', 'hidden', 'number', 2, { hidden: true })
|
||||
|
||||
const visible = store.getVisibleWidgets('node-1')
|
||||
expect(visible).toHaveLength(1)
|
||||
expect(visible[0].name).toBe('visible')
|
||||
})
|
||||
|
||||
it('getAdvancedWidgets returns only advanced widgets', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'basic', 'number', 1)
|
||||
store.registerWidget('node-1', 'adv1', 'number', 2, { advanced: true })
|
||||
store.registerWidget('node-1', 'adv2', 'string', 'x', { advanced: true })
|
||||
|
||||
const advanced = store.getAdvancedWidgets('node-1')
|
||||
expect(advanced).toHaveLength(2)
|
||||
expect(advanced.map((w) => w.name).sort()).toEqual(['adv1', 'adv2'])
|
||||
})
|
||||
|
||||
it('getPromotedWidgets returns only promoted widgets', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'normal', 'number', 1)
|
||||
store.registerWidget('node-1', 'promo', 'string', 'x', { promoted: true })
|
||||
|
||||
const promoted = store.getPromotedWidgets('node-1')
|
||||
expect(promoted).toHaveLength(1)
|
||||
expect(promoted[0].name).toBe('promo')
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
describe('metadata setters', () => {
|
||||
it('setHidden updates widget hidden state', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 100)
|
||||
|
||||
store.removeNode('node-1')
|
||||
store.setHidden('node-1', 'seed', true)
|
||||
expect(store.getWidget('node-1', 'seed')?.hidden).toBe(true)
|
||||
|
||||
expect(store.get('node-1', 'seed')).toBeUndefined()
|
||||
expect(store.get('node-1', 'steps')).toBeUndefined()
|
||||
expect(store.get('node-2', 'seed')).toBe(2)
|
||||
store.setHidden('node-1', 'seed', false)
|
||||
expect(store.getWidget('node-1', 'seed')?.hidden).toBe(false)
|
||||
})
|
||||
|
||||
it('setDisabled updates widget disabled state', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 100)
|
||||
|
||||
store.setDisabled('node-1', 'seed', true)
|
||||
expect(store.getWidget('node-1', 'seed')?.disabled).toBe(true)
|
||||
})
|
||||
|
||||
it('setAdvanced updates widget advanced state', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 100)
|
||||
|
||||
store.setAdvanced('node-1', 'seed', true)
|
||||
expect(store.getWidget('node-1', 'seed')?.advanced).toBe(true)
|
||||
})
|
||||
|
||||
it('setPromoted updates widget promoted state', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 100)
|
||||
|
||||
store.setPromoted('node-1', 'seed', true)
|
||||
expect(store.getWidget('node-1', 'seed')?.promoted).toBe(true)
|
||||
})
|
||||
|
||||
it('setLabel updates widget label', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 100)
|
||||
|
||||
store.setLabel('node-1', 'seed', 'Random Seed')
|
||||
expect(store.getWidget('node-1', 'seed')?.label).toBe('Random Seed')
|
||||
|
||||
store.setLabel('node-1', 'seed', undefined)
|
||||
expect(store.getWidget('node-1', 'seed')?.label).toBeUndefined()
|
||||
})
|
||||
|
||||
it('setters silently do nothing for missing widgets', () => {
|
||||
const store = useWidgetValueStore()
|
||||
expect(() => store.setHidden('missing', 'widget', true)).not.toThrow()
|
||||
expect(() => store.setDisabled('missing', 'widget', true)).not.toThrow()
|
||||
expect(() => store.setAdvanced('missing', 'widget', true)).not.toThrow()
|
||||
expect(() => store.setPromoted('missing', 'widget', true)).not.toThrow()
|
||||
expect(() => store.setLabel('missing', 'widget', 'test')).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
describe('value sync between APIs', () => {
|
||||
it('set() updates registered widget state value', () => {
|
||||
const store = useWidgetValueStore()
|
||||
store.registerWidget('node-1', 'seed', 'number', 100)
|
||||
|
||||
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.set('node-1', 'seed', 200)
|
||||
|
||||
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.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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,22 +2,50 @@ import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type {
|
||||
IWidgetOptions,
|
||||
TWidgetType
|
||||
} from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
type WidgetKey = `${NodeId}:${string}`
|
||||
export type WidgetKey = `${NodeId}:${string}`
|
||||
|
||||
export interface WidgetState {
|
||||
nodeId: NodeId
|
||||
name: string
|
||||
type: TWidgetType
|
||||
value: unknown
|
||||
label?: string
|
||||
hidden: boolean
|
||||
disabled: boolean
|
||||
advanced: boolean
|
||||
promoted: boolean
|
||||
options: IWidgetOptions
|
||||
serialize: boolean
|
||||
}
|
||||
|
||||
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(`${nodeId}:${widgetName}`)
|
||||
return values.value.get(makeKey(nodeId, widgetName))
|
||||
}
|
||||
|
||||
function set(nodeId: NodeId, widgetName: string, value: unknown): void {
|
||||
values.value.set(`${nodeId}:${widgetName}`, value)
|
||||
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(`${nodeId}:${widgetName}`)
|
||||
values.value.delete(makeKey(nodeId, widgetName))
|
||||
}
|
||||
|
||||
function removeNode(nodeId: NodeId): void {
|
||||
@@ -29,5 +57,157 @@ export const useWidgetValueStore = defineStore('widgetValue', () => {
|
||||
}
|
||||
}
|
||||
|
||||
return { values, get, set, remove, removeNode }
|
||||
function registerWidget(
|
||||
nodeId: NodeId,
|
||||
name: string,
|
||||
type: TWidgetType,
|
||||
value: unknown,
|
||||
options: Partial<
|
||||
Pick<
|
||||
WidgetState,
|
||||
'label' | 'hidden' | 'disabled' | 'advanced' | 'promoted' | 'serialize'
|
||||
>
|
||||
> & { widgetOptions?: IWidgetOptions } = {}
|
||||
): WidgetState {
|
||||
const key = makeKey(nodeId, name)
|
||||
const state: WidgetState = {
|
||||
nodeId,
|
||||
name,
|
||||
type,
|
||||
value,
|
||||
label: options.label,
|
||||
hidden: options.hidden ?? false,
|
||||
disabled: options.disabled ?? false,
|
||||
advanced: options.advanced ?? false,
|
||||
promoted: options.promoted ?? false,
|
||||
options: options.widgetOptions ?? {},
|
||||
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)
|
||||
}
|
||||
|
||||
function unregisterNode(nodeId: NodeId): void {
|
||||
const prefix = `${nodeId}:`
|
||||
for (const key of widgetStates.value.keys()) {
|
||||
if (key.startsWith(prefix)) {
|
||||
widgetStates.value.delete(key)
|
||||
}
|
||||
}
|
||||
removeNode(nodeId)
|
||||
}
|
||||
|
||||
function getWidget(
|
||||
nodeId: NodeId,
|
||||
widgetName: string
|
||||
): WidgetState | undefined {
|
||||
return widgetStates.value.get(makeKey(nodeId, widgetName))
|
||||
}
|
||||
|
||||
function getNodeWidgets(nodeId: NodeId): WidgetState[] {
|
||||
const prefix = `${nodeId}:`
|
||||
const result: WidgetState[] = []
|
||||
for (const [key, state] of widgetStates.value) {
|
||||
if (key.startsWith(prefix)) {
|
||||
result.push(state)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getVisibleWidgets(nodeId: NodeId): WidgetState[] {
|
||||
return getNodeWidgets(nodeId).filter((w) => !w.hidden)
|
||||
}
|
||||
|
||||
function getAdvancedWidgets(nodeId: NodeId): WidgetState[] {
|
||||
return getNodeWidgets(nodeId).filter((w) => w.advanced)
|
||||
}
|
||||
|
||||
function getPromotedWidgets(nodeId: NodeId): WidgetState[] {
|
||||
return getNodeWidgets(nodeId).filter((w) => w.promoted)
|
||||
}
|
||||
|
||||
function setHidden(
|
||||
nodeId: NodeId,
|
||||
widgetName: string,
|
||||
hidden: boolean
|
||||
): void {
|
||||
const state = getWidget(nodeId, widgetName)
|
||||
if (state) {
|
||||
state.hidden = hidden
|
||||
}
|
||||
}
|
||||
|
||||
function setDisabled(
|
||||
nodeId: NodeId,
|
||||
widgetName: string,
|
||||
disabled: boolean
|
||||
): void {
|
||||
const state = getWidget(nodeId, widgetName)
|
||||
if (state) {
|
||||
state.disabled = disabled
|
||||
}
|
||||
}
|
||||
|
||||
function setAdvanced(
|
||||
nodeId: NodeId,
|
||||
widgetName: string,
|
||||
advanced: boolean
|
||||
): void {
|
||||
const state = getWidget(nodeId, widgetName)
|
||||
if (state) {
|
||||
state.advanced = advanced
|
||||
}
|
||||
}
|
||||
|
||||
function setPromoted(
|
||||
nodeId: NodeId,
|
||||
widgetName: string,
|
||||
promoted: boolean
|
||||
): void {
|
||||
const state = getWidget(nodeId, widgetName)
|
||||
if (state) {
|
||||
state.promoted = promoted
|
||||
}
|
||||
}
|
||||
|
||||
function setLabel(
|
||||
nodeId: NodeId,
|
||||
widgetName: string,
|
||||
label: string | undefined
|
||||
): void {
|
||||
const state = getWidget(nodeId, widgetName)
|
||||
if (state) {
|
||||
state.label = label
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
values,
|
||||
widgetStates,
|
||||
get,
|
||||
set,
|
||||
remove,
|
||||
removeNode,
|
||||
registerWidget,
|
||||
unregisterWidget,
|
||||
unregisterNode,
|
||||
getWidget,
|
||||
getNodeWidgets,
|
||||
getVisibleWidgets,
|
||||
getAdvancedWidgets,
|
||||
getPromotedWidgets,
|
||||
setHidden,
|
||||
setDisabled,
|
||||
setAdvanced,
|
||||
setPromoted,
|
||||
setLabel
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user