diff --git a/src/lib/ComfyGraph.ts b/src/lib/ComfyGraph.ts index 578a6fd..bab45cb 100644 --- a/src/lib/ComfyGraph.ts +++ b/src/lib/ComfyGraph.ts @@ -2,7 +2,6 @@ import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, Li import GraphSync from "./GraphSync"; import EventEmitter from "events"; import type TypedEmitter from "typed-emitter"; -import layoutState from "./stores/layoutState"; import uiState from "./stores/uiState"; import { get } from "svelte/store"; import type ComfyGraphNode from "./nodes/ComfyGraphNode"; @@ -10,6 +9,9 @@ import type IComfyInputSlot from "./IComfyInputSlot"; import type { ComfyBackendNode } from "./nodes/ComfyBackendNode"; import type { ComfyComboNode, ComfyWidgetNode } from "./nodes/widgets"; import selectionState from "./stores/selectionState"; +import type { WritableLayoutStateStore } from "./stores/layoutStates"; +import type { WorkflowInstID } from "./components/ComfyApp"; +import layoutStates from "./stores/layoutStates"; type ComfyGraphEvents = { configured: (graph: LGraph) => void @@ -25,6 +27,13 @@ type ComfyGraphEvents = { export default class ComfyGraph extends LGraph { eventBus: TypedEmitter = new EventEmitter() as TypedEmitter; + workflowID: WorkflowInstID | null = null; + + constructor(workflowID?: WorkflowInstID) { + super(); + this.workflowID = workflowID; + } + override onConfigure() { console.debug("Configured"); } @@ -50,19 +59,24 @@ export default class ComfyGraph extends LGraph { override onNodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) { // Don't add nodes in subgraphs until this callback reaches the root // graph - if (node.getRootGraph() == null || this._is_subgraph) - return; + // Only root graphs will have a workflow ID, so we don't mind subgraphs + // missing it + if (node.getRootGraph() != null && !this._is_subgraph && this.workflowID != null) { + const layoutState = get(layoutStates).all[this.workflowID] + if (layoutState === null) { + throw new Error(`LGraph with workflow missing layout! ${this.workflowID}`) + } - this.doAddNode(node, options); + this.doAddNode(node, layoutState, options); + } - // console.debug("Added", node); this.eventBus.emit("nodeAdded", node); } /* * Add widget UI/groups for newly added nodes. */ - private doAddNode(node: LGraphNode, options: LGraphAddNodeOptions) { + private doAddNode(node: LGraphNode, layoutState: WritableLayoutStateStore, options: LGraphAddNodeOptions) { layoutState.nodeAdded(node, options) // All nodes whether they come from base litegraph or ComfyBox should @@ -144,7 +158,7 @@ export default class ComfyGraph extends LGraph { // ************** RECURSION ALERT ! ************** if (node.is(Subgraph)) { for (const child of node.subgraph.iterateNodesInOrder()) { - this.doAddNode(child, options) + this.doAddNode(child, layoutState, options) } } // ************** RECURSION ALERT ! ************** @@ -152,16 +166,23 @@ export default class ComfyGraph extends LGraph { override onNodeRemoved(node: LGraphNode, options: LGraphRemoveNodeOptions) { selectionState.clear(); // safest option - layoutState.nodeRemoved(node, options); - // Handle subgraphs being removed - if (node.is(Subgraph)) { - for (const child of node.subgraph.iterateNodesInOrder()) { - this.onNodeRemoved(child, options) + if (node.getRootGraph() != null && !this._is_subgraph && this.workflowID != null) { + const layoutState = get(layoutStates).all[this.workflowID] + if (layoutState === null) { + throw new Error(`LGraph with workflow missing layout! ${this.workflowID}`) + } + + layoutState.nodeRemoved(node, options); + + // Handle subgraphs being removed + if (node.is(Subgraph)) { + for (const child of node.subgraph.iterateNodesInOrder()) { + this.onNodeRemoved(child, options) + } } } - // console.debug("Removed", node); this.eventBus.emit("nodeRemoved", node); } diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index 17e23ba..00cc87b 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -3,11 +3,11 @@ import type ComfyApp from "./components/ComfyApp"; import queueState from "./stores/queueState"; import { get, type Unsubscriber } from "svelte/store"; import uiState from "./stores/uiState"; -import layoutState from "./stores/layoutState"; import { Watch } from "@litegraph-ts/nodes-basic"; import { ComfyReroute } from "./nodes"; import type { Progress } from "./components/ComfyApp"; import selectionState from "./stores/selectionState"; +import type ComfyGraph from "./ComfyGraph"; export type SerializedGraphCanvasState = { offset: Vector2, @@ -18,10 +18,14 @@ export default class ComfyGraphCanvas extends LGraphCanvas { app: ComfyApp | null; private _unsubscribe: Unsubscriber; + get comfyGraph(): ComfyGraph | null { + return this.graph as ComfyGraph; + } + constructor( app: ComfyApp, - graph: LGraph, canvas: HTMLCanvasElement | string, + graph?: ComfyGraph, options: { skip_render?: boolean; skip_events?: boolean; @@ -282,7 +286,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { selectionState.update(ss => { ss.currentSelectionNodes = Object.values(nodes) ss.currentSelection = [] - const ls = get(layoutState) + const ls = get(this.comfyGraph.layoutState) for (const node of ss.currentSelectionNodes) { const widget = ls.allItemsByNode[node.id] if (widget) @@ -299,7 +303,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { ss.currentHoveredNodes.add(node.id) } ss.currentHovered.clear() - const ls = get(layoutState) + const ls = get(this.comfyGraph.layoutState) for (const nodeID of ss.currentHoveredNodes) { const widget = ls.allItemsByNode[nodeID] if (widget) diff --git a/src/lib/api.ts b/src/lib/api.ts index 78ea676..3d0a960 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -3,7 +3,7 @@ import type TypedEmitter from "typed-emitter"; import EventEmitter from "events"; import type { ComfyImageLocation } from "$lib/utils"; import type { SerializedLGraph, UUID } from "@litegraph-ts/core"; -import type { SerializedLayoutState } from "./stores/layoutState"; +import type { SerializedLayoutState } from "./stores/layoutStates"; import type { ComfyNodeDef } from "./ComfyNodeDef"; export type ComfyPromptRequest = { diff --git a/src/lib/components/AccordionContainer.svelte b/src/lib/components/AccordionContainer.svelte index 279aafe..cde9290 100644 --- a/src/lib/components/AccordionContainer.svelte +++ b/src/lib/components/AccordionContainer.svelte @@ -11,11 +11,12 @@ // notice - fade in works fine but don't add svelte's fade-out (known issue) import {cubicIn} from 'svelte/easing'; import { flip } from 'svelte/animate'; - import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState"; + import { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutStates"; import { startDrag, stopDrag } from "$lib/utils" import { writable, type Writable } from "svelte/store"; import { isHidden } from "$lib/widgets/utils"; + export let layoutState: WritableLayoutStateStore; export let container: ContainerLayout | null = null; export let zIndex: number = 0; export let classes: string[] = []; @@ -59,6 +60,14 @@ navigator.vibrate(20) $isOpen = e.detail } + + function _startDrag(e: MouseEvent | TouchEvent) { + startDrag(e, layoutState) + } + + function _stopDrag(e: MouseEvent | TouchEvent) { + stopDrag(e, layoutState) + } {#if container} @@ -93,7 +102,7 @@ animate:flip={{duration:flipDurationMs}} style={item?.attrs?.style || ""} > - + {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
{/if} @@ -101,10 +110,18 @@ {/each}
{#if isHidden(container) && edit} -
+
{/if} {#if showHandles} -
+
{/if} @@ -112,7 +129,7 @@ {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} - + {/each} diff --git a/src/lib/components/BlockContainer.svelte b/src/lib/components/BlockContainer.svelte index 80b85ae..5d230b5 100644 --- a/src/lib/components/BlockContainer.svelte +++ b/src/lib/components/BlockContainer.svelte @@ -10,11 +10,12 @@ // notice - fade in works fine but don't add svelte's fade-out (known issue) import {cubicIn} from 'svelte/easing'; import { flip } from 'svelte/animate'; - import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState"; + import { type ContainerLayout, type WidgetLayout, type IDragItem, type WritableLayoutStateStore } from "$lib/stores/layoutStates"; import { startDrag, stopDrag } from "$lib/utils" import type { Writable } from "svelte/store"; import { isHidden } from "$lib/widgets/utils"; + export let layoutState: WritableLayoutStateStore; export let container: ContainerLayout | null = null; export let zIndex: number = 0; export let classes: string[] = []; @@ -53,6 +54,14 @@ children = layoutState.updateChildren(container, evt.detail.items) // Ensure dragging is stopped on drag finish }; + + function _startDrag(e: MouseEvent | TouchEvent) { + startDrag(e, layoutState) + } + + function _stopDrag(e: MouseEvent | TouchEvent) { + stopDrag(e, layoutState) + } {#if container} @@ -92,7 +101,7 @@ animate:flip={{duration:flipDurationMs}} style={item?.attrs?.style || ""} > - + {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
{/if} @@ -100,10 +109,18 @@ {/each}
{#if isHidden(container) && edit} -
+
{/if} {#if showHandles} -
+
{/if}
diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index 9da5eb7..28e77bb 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -2,7 +2,6 @@ import { ListIcon as List, ImageIcon as Image, SettingsIcon as Settings } from "svelte-feather-icons"; import ComfyApp, { type A1111PromptAndInfo, type SerializedAppState } from "./ComfyApp"; import uiState from "$lib/stores/uiState"; - import layoutState from "$lib/stores/layoutState"; import { SvelteToast, toast } from '@zerodevx/svelte-toast' import LightboxModal from "./LightboxModal.svelte"; @@ -18,8 +17,6 @@ let hasShownUIHelpToast: boolean = false; let uiTheme: string = "gradio-dark"; - let debugLayout: boolean = false; - const toastOptions = { intro: { duration: 200 }, theme: { @@ -32,12 +29,6 @@ notify("Right-click to open context menu.") } - if (debugLayout) { - layoutState.subscribe(s => { - console.warn("UPDATESTATE", s) - }) - } - $: if (uiTheme === "gradio-dark") { document.getElementById("app-root").classList.add("dark") } diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index d611768..904501b 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -21,8 +21,7 @@ import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode"; import queueState from "$lib/stores/queueState"; import { type SvelteComponentDev } from "svelte/internal"; import type IComfyInputSlot from "$lib/IComfyInputSlot"; -import type { LayoutState, SerializedLayoutState, WritableLayoutStateStore } from "$lib/stores/layoutState"; -import layoutState from "$lib/stores/layoutState"; +import type { LayoutState, SerializedLayoutState, WritableLayoutStateStore } from "$lib/stores/layoutStates"; import { toast } from '@zerodevx/svelte-toast' import ComfyGraph from "$lib/ComfyGraph"; import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode"; @@ -41,7 +40,10 @@ import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111"; import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt"; import type { ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt"; import ComfyBoxStdPromptSerializer from "$lib/ComfyBoxStdPromptSerializer"; -import { v4 as uuidv4 } from "uuid"; +import selectionState from "$lib/stores/selectionState"; +import layoutStates from "$lib/stores/layoutStates"; +import { ComfyWorkflow } from "$lib/stores/workflowState"; +import workflowState from "$lib/stores/workflowState"; export const COMFYBOX_SERIAL_VERSION = 1; @@ -134,133 +136,6 @@ type CanvasState = { canvas: ComfyGraphCanvas, } -type ActiveCanvas = { - canvas: LGraphCanvas | null; - canvasHandler: () => void | null; - state: SerializedGraphCanvasState; -} - -export type SerializedWorkflowState = { - graph: SerializedLGraph, - layout: SerializedLayoutState -} - -/* - * ID for an opened workflow. - * - * Unlike NodeID and PromptID, these are *not* saved to the workflow itself. - * They are only used for identifying an open workflow in the program. If the - * workflow is closed and reopened, a different workflow ID will be assigned to - * it. - */ -export type WorkflowInstID = UUID; - -export class ComfyWorkflow { - /* - * Used for uniquely identifying the instance of the opened workflow in the frontend. - */ - id: WorkflowInstID; - title: string; - graph: ComfyGraph; - layout: WritableLayoutStateStore; - - canvases: Record = {}; - - constructor(title: string, graph: ComfyGraph, layout: WritableLayoutStateStore) { - this.id = uuidv4(); - this.title = title; - this.layout = layout; - this.graph = graph; - } - - start(key: string, canvas: ComfyGraphCanvas) { - if (this.canvases[key] != null) - throw new Error(`This workflow is already being displayed on canvas ${key}`) - - const canvasHandler = () => canvas.draw(true); - - this.canvases[key] = { - canvas, - canvasHandler, - state: { - // TODO - offset: [0, 0], - scale: 1 - } - } - - this.graph.attachCanvas(canvas); - this.graph.eventBus.on("afterExecute", canvasHandler) - - if (Object.keys(this.canvases).length === 1) - this.graph.start(); - } - - stop(key: string) { - const canvas = this.canvases[key] - if (canvas == null) - throw new Error(`This workflow is not being displayed on canvas ${key}`) - - this.graph.detachCanvas(canvas.canvas); - this.graph.eventBus.removeListener("afterExecute", canvas.canvasHandler) - - delete this.canvases[key] - - if (Object.keys(this.canvases).length === 0) - this.graph.stop(); - } - - stopAll() { - for (const key of Object.keys(this.canvases)) - this.stop(key) - this.graph.stop() - } - - serialize(): SerializedWorkflowState { - const graph = this.graph; - - const serializedGraph = graph.serialize() - const serializedLayout = this.layout.serialize() - - return { - graph: serializedGraph, - layout: serializedLayout - } - } - - static deserialize(data: SerializedWorkflowState): ComfyWorkflow { - const layout = layoutState; // TODO - // Ensure loadGraphData does not trigger any state changes in layoutState - // (isConfiguring is set to true here) - // lGraph.configure will add new nodes, triggering onNodeAdded, but we - // want to restore the layoutState ourselves - layout.onStartConfigure(); - - // Patch T2IAdapterLoader to ControlNetLoader since they are the same node now - for (let n of data.graph.nodes) { - if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader"; - } - - const graph = new ComfyGraph(); - graph.configure(data.graph); - - for (const node of graph._nodes) { - const size = node.computeSize(); - size[0] = Math.max(node.size[0], size[0]); - size[1] = Math.max(node.size[1], size[1]); - node.size = size; - // this.#invokeExtensions("loadedGraphNode", node); - } - - // Now restore the layout - // Subsequent added nodes will add the UI data to layoutState - // TODO - layout.deserialize(data.layout, graph) - - return new ComfyWorkflow("Workflow X", graph, layout); - } -} - export default class ComfyApp { api: ComfyAPI; @@ -269,22 +144,6 @@ export default class ComfyApp { canvasCtx: CanvasRenderingContext2D | null = null; lCanvas: ComfyGraphCanvas | null = null; - openedWorkflows: ComfyWorkflow[] = []; - openedWorkflowsByID: Record = {}; - activeWorkflowIdx: number = -1; - - get activeWorkflow(): ComfyWorkflow | null { - return this.openedWorkflows[this.activeWorkflowIdx] - } - - get activeGraph(): ComfyGraph | null { - return this.activeWorkflow?.graph; - } - - getWorkflow(id: WorkflowInstID): ComfyWorkflow | null { - return this.openedWorkflowsByID[id]; - } - shiftDown: boolean = false; ctrlDown: boolean = false; selectedGroupMoving: boolean = false; @@ -312,7 +171,7 @@ export default class ComfyApp { this.rootEl = document.getElementById("app-root") as HTMLDivElement; this.canvasEl = document.getElementById("graph-canvas") as HTMLCanvasElement; - this.lCanvas = new ComfyGraphCanvas(this, null, this.canvasEl); + this.lCanvas = new ComfyGraphCanvas(this, this.canvasEl); this.canvasCtx = this.canvasEl.getContext("2d"); const uiUnlocked = get(uiState).uiUnlocked; @@ -371,12 +230,12 @@ export default class ComfyApp { this.lCanvas.draw(true, true); } - serialize(): SerializedAppState { - const workflow = this.activeWorkflow - if (workflow == null) - throw new Error("No workflow active!") + serialize(workflow: ComfyWorkflow): SerializedAppState { + const layoutState = layoutStates.getLayout(workflow.id); + if (layoutState == null) + throw new Error("Workflow has no layout!") - const { graph, layout } = workflow.serialize(); + const { graph, layout } = workflow.serialize(layoutState); const canvas = this.lCanvas.serialize(); return { @@ -390,14 +249,15 @@ export default class ComfyApp { } saveStateToLocalStorage() { - if (this.activeWorkflow == null) { + const workflow = workflowState.getActiveWorkflow(); + if (workflow == null) { notify("No active workflow!", { type: "error" }) return; } try { uiState.update(s => { s.isSavingToLocalStorage = true; return s; }) - const savedWorkflow = this.serialize(); + const savedWorkflow = this.serialize(workflow); const json = JSON.stringify(savedWorkflow); localStorage.setItem("workflow", json) notify("Saved to local storage.") @@ -548,25 +408,27 @@ export default class ComfyApp { this.api.addEventListener("progress", (progress: Progress) => { queueState.progressUpdated(progress); - this.activeGraph?.setDirtyCanvas(true, false); // TODO PromptID + workflowState.getActiveWorkflow()?.graph?.setDirtyCanvas(true, false); // TODO PromptID }); this.api.addEventListener("executing", (promptID: PromptID | null, nodeID: ComfyNodeID | null) => { const queueEntry = queueState.executingUpdated(promptID, nodeID); if (queueEntry != null) { - const workflow = this.getWorkflow(queueEntry.workflowID) - workflow?.graph.setDirtyCanvas(true, false); + const workflow = workflowState.getWorkflow(queueEntry.workflowID); + workflow?.graph?.setDirtyCanvas(true, false); } }); this.api.addEventListener("executed", (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput) => { const queueEntry = queueState.onExecuted(promptID, nodeID, output) if (queueEntry != null) { - const workflow = this.getWorkflow(queueEntry.workflowID) - workflow?.graph.setDirtyCanvas(true, false); - const node = workflow?.graph.getNodeByIdRecursive(nodeID) as ComfyGraphNode; - if (node?.onExecuted) { - node.onExecuted(output); + const workflow = workflowState.getWorkflow(queueEntry.workflowID); + if (workflow != null) { + workflow.graph.setDirtyCanvas(true, false); + const node = workflow.graph.getNodeByIdRecursive(nodeID) as ComfyGraphNode; + if (node?.onExecuted) { + node.onExecuted(output); + } } } }); @@ -635,68 +497,31 @@ export default class ComfyApp { setColor(BuiltInSlotType.ACTION, "lightseagreen") } - createNewWorkflow(): ComfyWorkflow { - // TODO remove - const workflow = ComfyWorkflow.deserialize({ graph: blankGraph.workflow, layout: blankGraph.layout }) - this.openedWorkflows.push(workflow); - this.setActiveWorkflow(this.openedWorkflows.length - 1) - return workflow; - } - async openWorkflow(data: SerializedAppState): Promise { if (data.version !== COMFYBOX_SERIAL_VERSION) { throw `Invalid ComfyBox saved data format: ${data.version}` } - this.clean(); - const workflow = ComfyWorkflow.deserialize({ graph: data.workflow, layout: data.layout }) + const workflow = workflowState.openWorkflow(data); // Restore canvas offset/zoom this.lCanvas.deserialize(data.canvas) await this.refreshComboInNodes(workflow); - this.openedWorkflows.push(workflow); - this.setActiveWorkflow(this.openedWorkflows.length - 1) - return workflow; } - closeWorkflow(index: number) { - if (index < 0 || index >= this.openedWorkflows.length) - return; - - const workflow = this.openedWorkflows[index]; - workflow.stopAll(); - - this.openedWorkflows.splice(index, 1) - this.setActiveWorkflow(0); - } - - closeAllWorkflows() { - while (this.openedWorkflows.length > 0) - this.closeWorkflow(0) - } - setActiveWorkflow(index: number) { - if (this.openedWorkflows.length === 0) { - this.activeWorkflowIdx = -1; - return; + const workflow = workflowState.setActiveWorkflow(index); + + if (workflow != null) { + workflow.start("app", this.lCanvas); + this.lCanvas.deserialize(workflow.canvases["app"].state) } - if (index < 0 || index >= this.openedWorkflows.length || this.activeWorkflowIdx === index) - return; - - if (this.activeWorkflow != null) - this.activeWorkflow.stop("app") - - const workflow = this.openedWorkflows[index] - this.activeWorkflowIdx = index; - - console.warn("START") - workflow.start("app", this.lCanvas); - this.lCanvas.deserialize(workflow.canvases["app"].state) + selectionState.clear(); } async initDefaultGraph() { @@ -717,7 +542,7 @@ export default class ComfyApp { this.clean(); this.lCanvas.closeAllSubgraphs(); - this.closeAllWorkflows(); + workflowState.closeAllWorkflows(); uiState.update(s => { s.uiUnlocked = true; s.uiEditMode = "widgets"; @@ -726,16 +551,17 @@ export default class ComfyApp { } runDefaultQueueAction() { - if (this.activeWorkflow == null) + const workflow = workflowState.getActiveWorkflow(); + if (workflow == null) return; - for (const node of this.activeGraph.iterateNodesInOrderRecursive()) { + for (const node of workflow.graph.iterateNodesInOrderRecursive()) { if ("onDefaultQueueAction" in node) { (node as ComfyGraphNode).onDefaultQueueAction() } } - if (get(layoutState).attrs.queuePromptButtonRunWorkflow) { + if (get(workflow.layout).attrs.queuePromptButtonRunWorkflow) { // Hold control to queue at the front const num = this.ctrlDown ? -1 : 0; this.queuePrompt(num, 1); @@ -743,7 +569,8 @@ export default class ComfyApp { } querySave() { - if (this.activeWorkflow == null) { + const workflow = workflowState.getActiveWorkflow(); + if (workflow == null) { notify("No active workflow!", { type: "error" }) return; } @@ -765,7 +592,7 @@ export default class ComfyApp { } const indent = 2 - const json = JSON.stringify(this.serialize(), null, indent) + const json = JSON.stringify(this.serialize(workflow), null, indent) download(filename, json, "application/json") @@ -781,12 +608,13 @@ export default class ComfyApp { } async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) { - if (this.activeWorkflow === null) { + const activeWorkflow = workflowState.getActiveWorkflow(); + if (activeWorkflow == null) { notify("No workflow is opened!", { type: "error" }) return; } - this.queueItems.push({ num, batchCount, workflow: this.activeWorkflow }); + this.queueItems.push({ num, batchCount, workflow: activeWorkflow }); // Only have one action process the items so each one gets a unique seed correctly if (this.processingQueue) { @@ -830,7 +658,7 @@ export default class ComfyApp { } const p = this.graphToPrompt(workflow, tag); - const l = layoutState.serialize(); + const l = workflow.layout.serialize(); console.debug(graphToGraphVis(workflow.graph)) console.debug(promptToGraphVis(p)) @@ -942,7 +770,7 @@ export default class ComfyApp { * Refresh combo list on whole nodes */ async refreshComboInNodes(workflow?: ComfyWorkflow, flashUI: boolean = false) { - workflow ||= this.activeWorkflow; + workflow ||= workflowState.getActiveWorkflow(); if (workflow == null) { notify("No active workflow!", { type: "error" }) return diff --git a/src/lib/components/ComfyProperties.svelte b/src/lib/components/ComfyProperties.svelte index c3efcb4..86d9019 100644 --- a/src/lib/components/ComfyProperties.svelte +++ b/src/lib/components/ComfyProperties.svelte @@ -2,43 +2,47 @@ import { Block, BlockTitle } from "@gradio/atoms"; import { TextBox, Checkbox } from "@gradio/form"; import { LGraphNode } from "@litegraph-ts/core" - import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState" + import { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec, type WritableLayoutStateStore } from "$lib/stores/layoutStates" import uiState from "$lib/stores/uiState" + import layoutStates from "$lib/stores/layoutStates" import selectionState from "$lib/stores/selectionState" import { get, type Writable, writable } from "svelte/store" import ComfyNumberProperty from "./ComfyNumberProperty.svelte"; import ComfyComboProperty from "./ComfyComboProperty.svelte"; - import type { ComfyWidgetNode } from "$lib/nodes/widgets"; + import type { ComfyWidgetNode } from "$lib/nodes/widgets"; + import type { ComfyWorkflow } from "$lib/stores/workflowState"; + + export let workflow: ComfyWorkflow | null; + + let layoutState: WritableLayoutStateStore | null = null let target: IDragItem | null = null; let node: LGraphNode | null = null; - let attrsChanged: Writable | null = null; - let refreshPropsPanel: Writable | null - - $: refreshPropsPanel = $layoutState.refreshPropsPanel; - - $: if ($selectionState.currentSelection.length > 0) { - node = null; - const targetId = $selectionState.currentSelection.slice(-1)[0] - const entry = $layoutState.allItems[targetId] - if (entry != null) { - target = entry.dragItem - attrsChanged = target.attrsChanged; - if (target.type === "widget") { - node = (target as WidgetLayout).node + $: if (layoutState) { + if ($selectionState.currentSelection.length > 0) { + node = null; + const targetId = $selectionState.currentSelection.slice(-1)[0] + const entry = $layoutState.allItems[targetId] + if (entry != null) { + target = entry.dragItem + if (target.type === "widget") { + node = (target as WidgetLayout).node + } } } - } - else if ($selectionState.currentSelectionNodes.length > 0) { - target = null; - node = $selectionState.currentSelectionNodes[0] - attrsChanged = null; + else if ($selectionState.currentSelectionNodes.length > 0) { + target = null; + node = $selectionState.currentSelectionNodes[0] + } + else { + target = null + node = null; + } } else { - target = null + target = null; node = null; - attrsChanged = null; } $: if (target) { @@ -55,7 +59,7 @@ let value = spec.defaultValue; target.attrs[spec.name] = value; if (spec.refreshPanelOnChange) - $refreshPropsPanel += 1; + doRefreshPanel(); } } } @@ -265,7 +269,7 @@ function doRefreshPanel() { console.warn("[ComfyProperties] doRefreshPanel") - $refreshPropsPanel += 1; + $layoutStates.refreshPropsPanel += 1; } @@ -282,172 +286,174 @@
- {#key $refreshPropsPanel} - {#each ALL_ATTRIBUTES as category(category.categoryName)} -
- - {category.categoryName} - -
- {#each category.specs as spec(spec.id)} - {#if validWidgetAttribute(spec, target)} -
- {#if spec.type === "string"} - updateAttribute(spec, target, e.detail)} - on:input={(e) => updateAttribute(spec, target, e.detail)} - disabled={!$uiState.uiUnlocked || !spec.editable} - label={spec.name} - max_lines={spec.multiline ? 5 : 1} - /> - {:else if spec.type === "boolean"} - + + {category.categoryName} + +
+ {#each category.specs as spec(spec.id)} + {#if validWidgetAttribute(spec, target)} +
+ {#if spec.type === "string"} + updateAttribute(spec, target, e.detail)} + on:input={(e) => updateAttribute(spec, target, e.detail)} disabled={!$uiState.uiUnlocked || !spec.editable} label={spec.name} + max_lines={spec.multiline ? 5 : 1} /> - {:else if spec.type === "number"} - updateAttribute(spec, target, e.detail)} - /> - {:else if spec.type === "enum"} - updateAttribute(spec, target, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} + /> + {:else if spec.type === "number"} + updateAttribute(spec, target, e.detail)} /> - {/if} -
- {:else if node} - {#if validNodeProperty(spec, node)} -
- {#if spec.type === "string"} - updateProperty(spec, e.detail)} - on:input={(e) => updateProperty(spec, e.detail)} - label={spec.name} - disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={spec.multiline ? 5 : 1} - /> - {:else if spec.type === "boolean"} - updateProperty(spec, e.detail)} - /> - {:else if spec.type === "number"} - updateProperty(spec, e.detail)} - /> {:else if spec.type === "enum"} updateProperty(spec, e.detail)} + on:change={(e) => updateAttribute(spec, target, e.detail)} /> {/if}
- {:else if validNodeVar(spec, node)} + {:else if node} + {#if validNodeProperty(spec, node)} +
+ {#if spec.type === "string"} + updateProperty(spec, e.detail)} + on:input={(e) => updateProperty(spec, e.detail)} + label={spec.name} + disabled={!$uiState.uiUnlocked || !spec.editable} + max_lines={spec.multiline ? 5 : 1} + /> + {:else if spec.type === "boolean"} + updateProperty(spec, e.detail)} + /> + {:else if spec.type === "number"} + updateProperty(spec, e.detail)} + /> + {:else if spec.type === "enum"} + updateProperty(spec, e.detail)} + /> + {/if} +
+ {:else if validNodeVar(spec, node)} +
+ {#if spec.type === "string"} + updateVar(spec, e.detail)} + on:input={(e) => updateVar(spec, e.detail)} + label={spec.name} + disabled={!$uiState.uiUnlocked || !spec.editable} + max_lines={spec.multiline ? 5 : 1} + /> + {:else if spec.type === "boolean"} + updateVar(spec, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} + /> + {:else if spec.type === "number"} + updateVar(spec, e.detail)} + /> + {:else if spec.type === "enum"} + updateVar(spec, e.detail)} + /> + {/if} +
+ {/if} + {:else if !node && !target && validWorkflowAttribute(spec)}
{#if spec.type === "string"} updateVar(spec, e.detail)} - on:input={(e) => updateVar(spec, e.detail)} + value={getWorkflowAttribute(spec)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} + on:input={(e) => updateWorkflowAttribute(spec, e.detail)} label={spec.name} disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={spec.multiline ? 5 : 1} + max_lines={spec.multiline ? 5 : 1} /> {:else if spec.type === "boolean"} updateVar(spec, e.detail)} + value={getWorkflowAttribute(spec)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} disabled={!$uiState.uiUnlocked || !spec.editable} label={spec.name} /> {:else if spec.type === "number"} updateVar(spec, e.detail)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} /> {:else if spec.type === "enum"} updateVar(spec, e.detail)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} /> {/if}
{/if} - {:else if !node && !target && validWorkflowAttribute(spec)} -
- {#if spec.type === "string"} - updateWorkflowAttribute(spec, e.detail)} - on:input={(e) => updateWorkflowAttribute(spec, e.detail)} - label={spec.name} - disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={spec.multiline ? 5 : 1} - /> - {:else if spec.type === "boolean"} - updateWorkflowAttribute(spec, e.detail)} - disabled={!$uiState.uiUnlocked || !spec.editable} - label={spec.name} - /> - {:else if spec.type === "number"} - updateWorkflowAttribute(spec, e.detail)} - /> - {:else if spec.type === "enum"} - updateWorkflowAttribute(spec, e.detail)} - /> - {/if} -
- {/if} + {/each} {/each} - {/each} - {/key} + {/key} + {/if}
diff --git a/src/lib/components/ComfyUIPane.svelte b/src/lib/components/ComfyWorkflowView.svelte similarity index 85% rename from src/lib/components/ComfyUIPane.svelte rename to src/lib/components/ComfyWorkflowView.svelte index b4a2bc8..2338857 100644 --- a/src/lib/components/ComfyUIPane.svelte +++ b/src/lib/components/ComfyWorkflowView.svelte @@ -1,34 +1,23 @@ -
- -
+{#if layoutState != null} +
+ +
+{/if} {#if showMenu} @@ -188,7 +179,7 @@ {/if}