mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
Add support for dynamic widgets (#6661)
Adds support for "dynamic combo" widgets where selecting a value on a combo widget can cause other widgets or inputs to be created.  Includes a fairly large refactoring in litegraphService to remove `#private` methods and cleanup some duplication in constructors for subgraphNodes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6661-Add-support-for-dynamic-widgets-2a96d73d3650817aa570c7babbaca2f3) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org>
This commit is contained in:
90
tests-ui/tests/widgets/dynamicCombo.test.ts
Normal file
90
tests-ui/tests/widgets/dynamicCombo.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration'
|
||||
import type { InputSpec } from '@/schemas/nodeDefSchema'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import type { HasInitialMinSize } from '@/services/litegraphService'
|
||||
|
||||
setActivePinia(createTestingPinia())
|
||||
type DynamicInputs = ('INT' | 'STRING' | 'IMAGE' | DynamicInputs)[][]
|
||||
|
||||
const { addNodeInput } = useLitegraphService()
|
||||
|
||||
function addDynamicCombo(node: LGraphNode, inputs: DynamicInputs) {
|
||||
const namePrefix = `${node.widgets?.length ?? 0}`
|
||||
function getSpec(
|
||||
inputs: DynamicInputs,
|
||||
depth: number = 0
|
||||
): { key: string; inputs: object }[] {
|
||||
return inputs.map((group, groupIndex) => {
|
||||
const inputs = group.map((input, inputIndex) => [
|
||||
`${namePrefix}.${depth}.${inputIndex}`,
|
||||
Array.isArray(input)
|
||||
? ['COMFY_DYNAMICCOMBO_V3', { options: getSpec(input, depth + 1) }]
|
||||
: [input, {}]
|
||||
])
|
||||
return {
|
||||
key: `${groupIndex}`,
|
||||
inputs: { required: Object.fromEntries(inputs) }
|
||||
}
|
||||
})
|
||||
}
|
||||
const inputSpec: Required<InputSpec> = [
|
||||
'COMFY_DYNAMICCOMBO_V3',
|
||||
{ options: getSpec(inputs) }
|
||||
]
|
||||
addNodeInput(
|
||||
node,
|
||||
transformInputSpecV1ToV2(inputSpec, { name: namePrefix, isOptional: false })
|
||||
)
|
||||
}
|
||||
function testNode() {
|
||||
const node: LGraphNode & Partial<HasInitialMinSize> = new LGraphNode('test')
|
||||
node.widgets = []
|
||||
node._initialMinSize = { width: 1, height: 1 }
|
||||
node.constructor.nodeData = {
|
||||
name: 'testnode'
|
||||
} as typeof node.constructor.nodeData
|
||||
return node as LGraphNode & Required<Pick<LGraphNode, 'widgets'>>
|
||||
}
|
||||
|
||||
describe('Dynamic Combos', () => {
|
||||
test('Can add widget on selection', () => {
|
||||
const node = testNode()
|
||||
addDynamicCombo(node, [['INT'], ['INT', 'STRING']])
|
||||
expect(node.widgets.length).toBe(2)
|
||||
node.widgets[0].value = '1'
|
||||
expect(node.widgets.length).toBe(3)
|
||||
})
|
||||
test('Can add nested widgets', () => {
|
||||
const node = testNode()
|
||||
addDynamicCombo(node, [['INT'], [[[], ['STRING']]]])
|
||||
expect(node.widgets.length).toBe(2)
|
||||
node.widgets[0].value = '1'
|
||||
expect(node.widgets.length).toBe(2)
|
||||
node.widgets[1].value = '1'
|
||||
expect(node.widgets.length).toBe(3)
|
||||
})
|
||||
test('Can add input', () => {
|
||||
const node = testNode()
|
||||
addDynamicCombo(node, [['INT'], ['IMAGE']])
|
||||
expect(node.widgets.length).toBe(2)
|
||||
node.widgets[0].value = '1'
|
||||
expect(node.widgets.length).toBe(1)
|
||||
expect(node.inputs.length).toBe(2)
|
||||
expect(node.inputs[1].type).toBe('IMAGE')
|
||||
})
|
||||
test('Dynamically added inputs are well ordered', () => {
|
||||
const node = testNode()
|
||||
addDynamicCombo(node, [['INT'], ['IMAGE']])
|
||||
addDynamicCombo(node, [['INT'], ['IMAGE']])
|
||||
node.widgets[2].value = '1'
|
||||
node.widgets[0].value = '1'
|
||||
expect(node.widgets.length).toBe(2)
|
||||
expect(node.inputs.length).toBe(4)
|
||||
expect(node.inputs[1].name).toBe('0.0.0')
|
||||
expect(node.inputs[3].name).toBe('2.0.0')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user