diff --git a/litegraph b/litegraph index 23242ca..ac3621c 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit 23242ca3d77e4463158cc7e340cac29409260440 +Subproject commit ac3621c4bdb9b5dd7a9f0a4b0b07599613dee42a diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index fcc0836..acbd23a 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -91,7 +91,6 @@ export default class ComfyApp { LiteGraph.release_link_on_empty_shows_menu = true; LiteGraph.alt_drag_do_clone_nodes = true; - LiteGraph.ignore_all_widget_events = true; this.lGraph.start(); diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index 6bbe53d..a1f9938 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -1,4 +1,4 @@ -import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget } from "@litegraph-ts/core"; +import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot } from "@litegraph-ts/core"; import ComfyGraphNode from "./ComfyGraphNode"; import ComboWidget from "$lib/widgets/ComboWidget.svelte"; import RangeWidget from "$lib/widgets/RangeWidget.svelte"; @@ -7,9 +7,12 @@ import type { SvelteComponentDev } from "svelte/internal"; import { ComfyWidgets } from "$lib/widgets"; import { Watch } from "@litegraph-ts/nodes-basic"; import type IComfyInputSlot from "$lib/IComfyInputSlot"; -import { writable, type Unsubscriber, type Writable } from "svelte/store"; +import { writable, type Unsubscriber, type Writable, get } from "svelte/store"; +import { clamp } from "$lib/utils" +import layoutState from "$lib/stores/layoutState"; export interface ComfyWidgetProperties extends Record { + defaultValue: any } /* @@ -21,6 +24,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { abstract properties: ComfyWidgetProperties; value: Writable + propsChanged: Writable = writable(true) // dummy to indicate if props changed unsubscribe: Unsubscriber; /** Svelte class for the frontend logic */ @@ -38,7 +42,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { override size: Vector2 = [60, 40]; - constructor(name?: string, value: T) { + constructor(name: string, value: T) { const color = LGraphCanvas.node_colors["blue"] super(name) this.value = writable(value) @@ -49,10 +53,12 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { "Value", "" ); + this.displayWidget.disabled = true; // prevent editing this.unsubscribe = this.value.subscribe(this.onValueUpdated.bind(this)) } private onValueUpdated(value: any) { + console.debug("[Widget] valueUpdated", this, value) this.displayWidget.value = Watch.toString(value) } @@ -82,21 +88,49 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { for (const key in comfyInput.config) this.setProperty(key, comfyInput.config[key]) } + if ("defaultValue" in this.properties) + this.setValue(this.properties.defaultValue) + + const widget = layoutState.findLayoutForNode(this.id) + if (widget) { + widget.attrs.title = input.name; + } + console.debug("Property copy", input, this.properties) } return true; } + onConnectionsChange( + type: LConnectionKind, + slotIndex: number, + isConnected: boolean, + link: LLink, + ioSlot: (INodeOutputSlot | INodeInputSlot) + ): void { + this.clampConfig(); + } + clampConfig() { + let changed = false; for (const link of this.getOutputLinks(0)) { - const node = this.graph._nodes_by_id[link.target_id] - if (node) { - const input = node.inputs[link.target_slot] - if (input && "config" in input) - this.clampOneConfig(input as IComfyInputSlot) + if (link) { // can be undefined if the link is removed + const node = this.graph._nodes_by_id[link.target_id] + if (node) { + const input = node.inputs[link.target_slot] + if (input && "config" in input) { + this.clampOneConfig(input as IComfyInputSlot) + changed = true; + } + } } } + + if (changed) { + // Force trigger reactivity to update component based on new props + this.propsChanged.set(true) + } } clampOneConfig(input: IComfyInputSlot) {} @@ -111,6 +145,7 @@ export interface ComfySliderProperties extends ComfyWidgetProperties { export class ComfySliderNode extends ComfyWidgetNode { override properties: ComfySliderProperties = { + defaultValue: 0, min: 0, max: 10, step: 1, @@ -131,10 +166,10 @@ export class ComfySliderNode extends ComfyWidgetNode { } override clampOneConfig(input: IComfyInputSlot) { - this.setProperty("min", Math.max(this.properties.min, input.config.min)) - this.setProperty("max", Math.max(this.properties.max, input.config.max)) - this.setProperty("step", Math.max(this.properties.step, input.config.step)) - this.setValue(Math.max(Math.min(this.properties.value, this.properties.max), this.properties.min)) + // this.setProperty("min", clamp(this.properties.min, input.config.min, input.config.max)) + // this.setProperty("max", clamp(this.properties.max, input.config.max, input.config.min)) + // this.setProperty("step", Math.min(this.properties.step, input.config.step)) + this.setValue(clamp(this.properties.defaultValue, this.properties.min, this.properties.max)) } } @@ -146,12 +181,13 @@ LiteGraph.registerNodeType({ }) export interface ComfyComboProperties extends ComfyWidgetProperties { - options: string[] + values: string[] } export class ComfyComboNode extends ComfyWidgetNode { override properties: ComfyComboProperties = { - options: ["A", "B", "C", "D"] + defaultValue: "A", + values: ["A", "B", "C", "D"] } static slotLayout: SlotLayout = { @@ -178,21 +214,23 @@ export class ComfyComboNode extends ComfyWidgetNode { return false; const thisProps = this.properties; - const otherProps = inputNode.properties; + if (!("config" in input)) + return true; + + const comfyInput = input as IComfyInputSlot; + const otherProps = comfyInput.config; // Ensure combo options match - if (!(otherProps.options instanceof Array)) + if (!(otherProps.values instanceof Array)) return false; - if (otherProps.options.length !== thisProps.options.length) - return false; - if (otherProps.find((v, i) => thisProps[i] !== v)) + if (thisProps.values.find((v, i) => otherProps.values.indexOf(v) === -1)) return false; return true; } override clampOneConfig(input: IComfyInputSlot) { - if (!(this.properties.value in input.config.values)) { + if (input.config.values.indexOf(this.properties.value) === -1) { if (input.config.values.length === 0) this.setValue("") else @@ -214,6 +252,7 @@ export interface ComfyTextProperties extends ComfyWidgetProperties { export class ComfyTextNode extends ComfyWidgetNode { override properties: ComfyTextProperties = { + defaultValue: "", multiline: false } diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index 7c5a0e3..f788c69 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -98,6 +98,7 @@ type LayoutStateOps = { groupItems: (dragItems: IDragItem[]) => ContainerLayout, ungroup: (container: ContainerLayout) => void, getCurrentSelection: () => IDragItem[], + findLayoutForNode: (nodeId: number) => IDragItem | null; clear: (state?: Partial) => void, resetLayout: () => void, } @@ -362,6 +363,16 @@ function ungroup(container: ContainerLayout) { store.set(state) } +function findLayoutForNode(nodeId: number): WidgetLayout | null { + const state = get(store) + const found = Object.entries(state.allItems).find(pair => + pair[1].dragItem.type === "widget" + && (pair[1].dragItem as WidgetLayout).node.id === nodeId) + if (found) + return found[1].dragItem as WidgetLayout + return null; +} + function clear(state: Partial = {}) { store.set({ root: null, @@ -390,6 +401,7 @@ const layoutStateStore: WritableLayoutStateStore = configureFinished, getCurrentSelection, groupItems, + findLayoutForNode, ungroup, clear, resetLayout diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6e15ea6..f5c47a7 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -6,6 +6,10 @@ import { get } from "svelte/store" import layoutState from "$lib/stores/layoutState" import type { SvelteComponentDev } from "svelte/internal"; +export function clamp(n: number, min: number, max: number): number { + return Math.min(Math.max(n, min), max) +} + export function download(filename: string, text: string, type: string = "text/plain") { const blob = new Blob([text], { type: type }); const url = URL.createObjectURL(blob); diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index 991c098..ee2e6b6 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -7,14 +7,22 @@ export let widget: WidgetLayout | null = null; let node: ComfyComboNode | null = null; let nodeValue: Writable | null = null; + let propsChanged: Writable | null = null; let option: any $: widget && setNodeValue(widget); + $: if ($propsChanged && nodeValue !== null) { + setOption($nodeValue) + setNodeValue(widget) + node.properties = node.properties + } + function setNodeValue(widget: WidgetLayout) { - if(widget && !node) { + if(widget) { node = widget.node as ComfyComboNode nodeValue = node.value; + propsChanged = node.propsChanged; setOption($nodeValue) // don't react on option } } @@ -23,7 +31,7 @@ option = value; } - $: if (nodeValue && option) { + $: if (nodeValue && option && option.value) { $nodeValue = option.value; } @@ -34,8 +42,8 @@ {widget.attrs.title}