diff --git a/package.json b/package.json index e4f840a..dbc94f0 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@litegraph-ts/nodes-math": "workspace:*", "@litegraph-ts/nodes-strings": "workspace:*", "@litegraph-ts/tsconfig": "workspace:*", + "@sveltejs/svelte-virtual-list": "^3.0.1", "@sveltejs/vite-plugin-svelte": "^2.1.1", "@tsconfig/svelte": "^4.0.1", "events": "^3.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f628138..7f3975b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,6 +64,9 @@ importers: '@litegraph-ts/tsconfig': specifier: workspace:* version: link:litegraph/packages/tsconfig + '@sveltejs/svelte-virtual-list': + specifier: ^3.0.1 + version: 3.0.1 '@sveltejs/vite-plugin-svelte': specifier: ^2.1.1 version: 2.1.1(svelte@3.58.0)(vite@4.3.1) @@ -2225,6 +2228,10 @@ packages: - supports-color dev: true + /@sveltejs/svelte-virtual-list@3.0.1: + resolution: {integrity: sha512-aF9TptS7NKKS7/TqpsxQBSDJ9Q0XBYzBehCeIC5DzdMEgrJZpIYao9LRLnyyo6SVodpapm2B7FE/Lj+FSA5/SQ==} + dev: false + /@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0): resolution: {integrity: sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==} engines: {node: ^14.18.0 || >= 16} diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 1f208e0..d0f21d0 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -764,6 +764,16 @@ export default class ComfyApp { async refreshComboInNodes(flashUI: boolean = false) { const defs = await this.api.getNodeDefs(); + for (let nodeNum in this.lGraph._nodes) { + const node = this.lGraph._nodes[nodeNum]; + if (node.type === "ui/combo") { + (node as nodes.ComfyComboNode).valuesForCombo = null; + (node as nodes.ComfyComboNode).comboRefreshed.set(true); + } + } + + let seen = new Set() + for (let nodeNum in this.lGraph._nodes) { const node = this.lGraph._nodes[nodeNum]; @@ -775,13 +785,18 @@ export default class ComfyApp { const comfyInput = input as IComfyInputSlot; if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) { - comfyInput.config.values = def["input"]["required"][comfyInput.name][0]; + const rawValues = def["input"]["required"][comfyInput.name][0]; + + comfyInput.config.values = rawValues; const inputNode = node.getInputNode(index) - if (inputNode && "doAutoConfig" in inputNode && comfyInput.widgetNodeType === inputNode.type) { - console.debug("[ComfyApp] Reconfiguring combo widget", inputNode.type, comfyInput.config.values) + if (inputNode && "doAutoConfig" in inputNode && comfyInput.widgetNodeType === inputNode.type && !seen.has(inputNode.id)) { + seen.add(inputNode.id) + console.warn("[ComfyApp] Reconfiguring combo widget", inputNode.type, comfyInput.config.values.length) const comfyComboNode = inputNode as nodes.ComfyComboNode; comfyComboNode.doAutoConfig(comfyInput, { includeProperties: new Set(["values"]), setWidgetTitle: false }) + + comfyComboNode.formatValues(rawValues) if (!comfyInput.config.values.includes(get(comfyComboNode.value))) { comfyComboNode.setValue(comfyInput.config.defaultValue || comfyInput.config.values[0]) } diff --git a/src/lib/components/ComfyProperties.svelte b/src/lib/components/ComfyProperties.svelte index b85a922..0dcf102 100644 --- a/src/lib/components/ComfyProperties.svelte +++ b/src/lib/components/ComfyProperties.svelte @@ -268,7 +268,7 @@ on:input={(e) => updateAttribute(spec, target, e.detail)} disabled={!$uiState.uiUnlocked || !spec.editable} label={spec.name} - max_lines={1} + max_lines={spec.multiline ? 5 : 1} /> {:else if spec.type === "boolean"} updateProperty(spec, e.detail)} label={spec.name} disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={1} + max_lines={spec.multiline ? 5 : 1} /> {:else if spec.type === "boolean"} updateVar(spec, e.detail)} label={spec.name} disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={1} + max_lines={spec.multiline ? 5 : 1} /> {:else if spec.type === "boolean"} updateWorkflowAttribute(spec, e.detail)} label={spec.name} disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={1} + max_lines={spec.multiline ? 5 : 1} /> {:else if spec.type === "boolean"} extends ComfyGraphNode { } private onValueUpdated(value: any) { - console.debug("[Widget] valueUpdated", this, value) + // console.debug("[Widget] valueUpdated", this, value) this.displayWidget.value = this.formatValue(value) if (this.outputIndex !== null && this.outputs.length >= this.outputIndex) { @@ -151,7 +151,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { if (!this.delayChangedEvent) this.triggerChangeEvent(get(this.value)) else { - console.debug("[Widget] queueChangeEvent", this, value) + // console.debug("[Widget] queueChangeEvent", this, value) this._aboutToChange = 2; // wait 1.5-2 frames, in case we're already in the middle of executing the graph this._aboutToChangeValue = get(this.value); } @@ -402,13 +402,17 @@ LiteGraph.registerNodeType({ export interface ComfyComboProperties extends ComfyWidgetProperties { values: string[] + + /* JS Function body that takes a parameter named "value" as a parameter and returns the label for each combo entry */ + convertValueToLabelCode: string } export class ComfyComboNode extends ComfyWidgetNode { override properties: ComfyComboProperties = { tags: [], defaultValue: "A", - values: ["A", "B", "C", "D"] + values: ["A", "B", "C", "D"], + convertValueToLabelCode: "" } static slotLayout: SlotLayout = { @@ -428,11 +432,52 @@ export class ComfyComboNode extends ComfyWidgetNode { comboRefreshed: Writable; + valuesForCombo: any[] | null = null; + constructor(name?: string) { super(name, "A") this.comboRefreshed = writable(false) } + override onPropertyChanged(property: any, value: any) { + if (property === "values" || property === "convertValueToLabelCode") { + this.formatValues(this.properties.values) + } + } + + formatValues(values: string[]) { + if (values == null) + return; + + this.properties.values = values; + + let formatter: any; + if (this.properties.convertValueToLabelCode) + formatter = new Function("value", this.properties.convertValueToLabelCode) as (v: string) => string; + else + formatter = (value) => `${value}`; + + try { + this.valuesForCombo = this.properties.values.map(value => { + return { + value, + label: formatter(value) + } + }) + } + catch (err) { + console.error("Failed formatting!", err) + this.valuesForCombo = this.properties.values.map(value => { + return { + value, + label: `${value}` + } + }) + } + + this.comboRefreshed.set(true); + } + onConnectOutput( outputIndex: number, inputType: INodeInputSlot["type"], @@ -476,6 +521,12 @@ export class ComfyComboNode extends ComfyWidgetNode { } } + override onSerialize(o: SerializedLGraphNode) { + super.onSerialize(o); + // TODO fix saving combo nodes with huge values lists + o.properties.values = [] + } + override stripUserState(o: SerializedLGraphNode) { super.stripUserState(o); o.properties.values = [] diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index eebef67..96a499d 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -219,6 +219,11 @@ export type AttributesSpec = { */ max?: number, + /* + * If `type` is "string", display as a textarea. + */ + multiline?: boolean, + /* * Valid `LGraphNode.type`s this property applies to if it's located in a node. * These are like "ui/button", "ui/slider". @@ -386,6 +391,17 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ defaultValue: "large" }, + // Combo + { + name: "convertValueToLabelCode", + type: "string", + location: "nodeProps", + editable: true, + multiline: true, + validNodeTypes: ["ui/combo"], + defaultValue: "" + }, + // Gallery { name: "variant", diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index 366fd42..96d9364 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -1,9 +1,11 @@
- {#key $propsChanged} - {#key $comboRefreshed} - {#if node !== null && nodeValue !== null} + {#key $comboRefreshed} + {#if node !== null && nodeValue !== null} + {#if node.valuesForCombo == null} + Loading... + {:else} + Count {node.valuesForCombo.length} {/if} - {/key} + {/if} {/key}
@@ -116,6 +151,12 @@ .wrapper { padding: 2px; width: 100%; + + :global(.selected-item) { + // no idea how to get the select box to shrink in the flexbox otherwise... + position: absolute !important; + width: -webkit-fill-available !important; + } } @keyframes -global-light-up { @@ -142,5 +183,43 @@ :global(.svelte-select-list) { z-index: var(--layer-top) !important; + overflow-y: initial !important; + width: auto !important; // seems floating-ui overrides listAutoWidth + } + + .container { + border-top: 1px solid #333; + border-bottom: 1px solid #333; + + height: 100% + } + + .list { + height: 30rem; + width: 30rem; + background-color: white; + + .item { + font-size: 16px; + &.selected { + color: white; + background: var(--color-yellow-500); + } + } + + .details { + background: white; + border: 1px solid grey; + } + + :global(svelte-virtual-list-row) { + white-space: nowrap; + } + + :global(svelte-virtual-list-row:hover) { + color: white; + background: var(--color-blue-500); + cursor: pointer; + } }