diff --git a/README.md b/README.md index 8cce78e..dbd64db 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ This project is *still under construction* and many features are missing, be awa ![Screenshot](./static/screenshot.png) +![Screenshot](./static/screenshot2.png) + +## Installation + +1. Download the latest release [here](https://nightly.link/space-nuko/ComfyBox/workflows/build-and-publish/master/ComfyBox-dist.zip) and extract it somewhere +2. Start the ComfyUI backend with `python main.py --enable-cors-header` +3. In the folder you extracted open the `run.bat`/`run.sh` script (requires Python 3 to be on your PATH). Alternatively you can serve the contents of the folder with a web server. + ## NOTE This frontend isn't compatible with regular ComfyUI's workflow format since extra metadata is saved like panel layout, so you'll have to spend a bit of time recreating them. This project also isn't compatible with regular ComfyUI's frontend extension format, but useful extensions can be integrated into this repo with some effort. @@ -13,16 +21,19 @@ This frontend isn't compatible with regular ComfyUI's workflow format since extr ## Proposed Features - All the power of ComfyUI with more convenience on top - Autocreation of UI widgets from your workflow, quickly creating a personalized dashboard -- Custom widget and node types -- Look up queued and finished generations and their configs in realtime +- Arrange the UI however you like and attach custom classes/styles to each widget +- Custom widget types +- See the status of queued and finished generations and their configs in realtime - Development with TypeScript -## Requirements +## Development + +### Requirements - `pnpm` - An installation of vanilla [ComfyUI](https://github.com/comfyanonymous/ComfyUI) for the backend -## Installation +### Installation 1. Clone the repo with submodules: @@ -33,5 +44,5 @@ git clone https://github.com/space-nuko/ComfyBox --recursive 2. `pnpm install` 4. `pnpm build:css` 5. `pnpm dev` -6. Start ComfyUI as usual with `python main.py --enable-cors-header` +6. Start ComfyUI with `python main.py --enable-cors-header` 7. Visit `http://localhost:3000` in your browser diff --git a/bin/run.bat b/bin/run.bat new file mode 100644 index 0000000..c46f9e3 --- /dev/null +++ b/bin/run.bat @@ -0,0 +1,11 @@ +@echo off + +echo Starting ComfyBox. +echo Be sure you've started ComfyUI already using this command: +echo[ +echo python main.py --enable-cors-header +echo[ +echo Serving at http://localhost:8000 +echo[ + +python -m http.server 8000 diff --git a/bin/run.sh b/bin/run.sh new file mode 100644 index 0000000..c121467 --- /dev/null +++ b/bin/run.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh + +echo "Starting ComfyBox." +echo "Be sure you've started ComfyUI already using this command:" +echo "" +echo " python main.py --enable-cors-header" +echo "" +echo "Serving at http://localhost:8000" +echo "" + +python -m http.server 8000 diff --git a/dist/.keep b/dist/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/litegraph b/litegraph index 115bef4..6cbae97 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit 115bef46a58616395d7d3b54a1421472ac28e519 +Subproject commit 6cbae97b3c385dba16b9100352428af4d6219dae diff --git a/package.json b/package.json index 23ae656..21713bd 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "svelte-dnd-action": "^0.9.22", "typescript": "^5.0.3", "vite": "^4.3.1", + "vite-plugin-static-copy": "^0.14.0", "vite-tsconfig-paths": "^4.0.8", "vitest": "^0.25.8" }, @@ -45,6 +46,7 @@ "@litegraph-ts/nodes-basic": "workspace:*", "@litegraph-ts/nodes-events": "workspace:*", "@litegraph-ts/nodes-math": "workspace:*", + "@litegraph-ts/nodes-strings": "workspace:*", "@litegraph-ts/tsconfig": "workspace:*", "@sveltejs/vite-plugin-svelte": "^2.1.1", "@tsconfig/svelte": "^4.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fccfd3..9cdbb2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: '@litegraph-ts/nodes-math': specifier: workspace:* version: link:litegraph/packages/nodes-math + '@litegraph-ts/nodes-strings': + specifier: workspace:* + version: link:litegraph/packages/nodes-strings '@litegraph-ts/tsconfig': specifier: workspace:* version: link:litegraph/packages/tsconfig @@ -122,6 +125,9 @@ importers: vite: specifier: ^4.3.1 version: 4.3.1(sass@1.61.0) + vite-plugin-static-copy: + specifier: ^0.14.0 + version: 0.14.0(vite@4.3.1) vite-tsconfig-paths: specifier: ^4.0.8 version: 4.0.8(typescript@5.0.3)(vite@4.3.1) @@ -805,6 +811,22 @@ importers: specifier: ^4.2.1 version: 4.3.1 + litegraph/packages/nodes-strings: + dependencies: + '@litegraph-ts/core': + specifier: workspace:* + version: link:../core + devDependencies: + '@litegraph-ts/tsconfig': + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: ^5.0.3 + version: 5.0.3 + vite: + specifier: ^4.2.1 + version: 4.3.1 + litegraph/packages/tsconfig: {} packages: @@ -7065,6 +7087,19 @@ packages: vite: 4.3.1(sass@1.61.0) dev: false + /vite-plugin-static-copy@0.14.0(vite@4.3.1): + resolution: {integrity: sha512-RMFmb4czomcrsbQBiUZs9HcDGN3kxGvF+OrtkfTVocp12CuoUCuJQhcY26RK35A6KS4WasGzEwcYZqHMjkAvVw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 + dependencies: + chokidar: 3.5.3 + fast-glob: 3.2.12 + fs-extra: 11.1.1 + picocolors: 1.0.0 + vite: 4.3.1(sass@1.61.0) + dev: true + /vite-tsconfig-paths@4.0.8(typescript@5.0.3)(vite@4.3.1): resolution: {integrity: sha512-p04zH+Ey+NT78571x0pdX7nVRIJSlmKVvYryFglSWOK3Hc72eDL0+JJfbyQiugaIBApJkaEqbBQvqpsFZOSVGg==} peerDependencies: diff --git a/run.bat b/run.bat deleted file mode 100644 index 53da77d..0000000 --- a/run.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off - -pushd dist -python -m http.server 8000 diff --git a/run.sh b/run.sh deleted file mode 100644 index c673001..0000000 --- a/run.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh - -pushd dist/ -python -m http.server 8000 diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index 3a464bf..8e1db17 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -1,7 +1,9 @@ -import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4 } from "@litegraph-ts/core"; +import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode } from "@litegraph-ts/core"; import type ComfyApp from "./components/ComfyApp"; import queueState from "./stores/queueState"; import { get } from "svelte/store"; +import uiState from "./stores/uiState"; +import layoutState from "./stores/layoutState"; export type SerializedGraphCanvasState = { offset: Vector2, @@ -101,7 +103,39 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } } + private alignToGrid(node: LGraphNode, ctx: CanvasRenderingContext2D) { + const x = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[0] / LiteGraph.CANVAS_GRID_SIZE); + const y = LiteGraph.CANVAS_GRID_SIZE * Math.round(node.pos[1] / LiteGraph.CANVAS_GRID_SIZE); + + const shiftX = x - node.pos[0]; + let shiftY = y - node.pos[1]; + + let w, h; + if (node.flags.collapsed) { + w = node._collapsed_width; + h = LiteGraph.NODE_TITLE_HEIGHT; + shiftY -= LiteGraph.NODE_TITLE_HEIGHT; + } else { + w = node.size[0]; + h = node.size[1]; + let titleMode = node.titleMode + if (titleMode !== TitleMode.TRANSPARENT_TITLE && titleMode !== TitleMode.NO_TITLE) { + h += LiteGraph.NODE_TITLE_HEIGHT; + shiftY -= LiteGraph.NODE_TITLE_HEIGHT; + } + } + const f = ctx.fillStyle; + ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; + ctx.fillRect(shiftX, shiftY, w, h); + ctx.fillStyle = f; + } + override drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void { + if ((window as any)?.app?.shiftDown && this.node_dragged && node.id in this.selected_nodes) { + this.alignToGrid(node, ctx) + } + + // Fade out inactive nodes var editor_alpha = this.editor_alpha; if (node.mode === NodeMode.NEVER) { // never this.editor_alpha = 0.4; @@ -196,4 +230,23 @@ export default class ComfyGraphCanvas extends LGraphCanvas { return res; } + + override onSelectionChange(nodes: Record) { + const ls = get(layoutState) + ls.currentSelectionNodes = Object.values(nodes) + ls.currentSelection = [] + layoutState.set(ls) + } + + override onNodeMoved(node: LGraphNode) { + if (super.onNodeMoved) + super.onNodeMoved(node); + + if ((window as any)?.app?.shiftDown) { + // Ensure all selected nodes are realigned + for (const id in this.selected_nodes) { + this.selected_nodes[id].alignToGrid(); + } + } + } } diff --git a/src/lib/components/BlockContainer.svelte b/src/lib/components/BlockContainer.svelte index 263ebfb..0f910d2 100644 --- a/src/lib/components/BlockContainer.svelte +++ b/src/lib/components/BlockContainer.svelte @@ -16,14 +16,17 @@ export let zIndex: number = 0; export let classes: string[] = []; export let showHandles: boolean = false; + let attrsChanged: Writable | null = null; let children: IDragItem[] | null = null; const flipDurationMs = 100; $: if (container) { children = $layoutState.allItems[container.id].children; + attrsChanged = container.attrsChanged } else { children = null; + attrsChanged = null } function handleConsider(evt: any) { @@ -38,52 +41,57 @@ {#if container && children} -
1}> - - {#if container.attrs.showTitle} - - {/if} -
1} - use:dndzone="{{ - items: children, - flipDurationMs, - centreDraggedOnCursor: true, - morphDisabled: true, - dropFromOthersDisabled: zIndex === 0, - dragDisabled: zIndex === 0 || $layoutState.currentSelection.length > 2 || $uiState.uiEditMode === "disabled" - }}" - on:consider="{handleConsider}" - on:finalize="{handleFinalize}" - > - {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} -
- - {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} -
- {/if} + {@const edit = $uiState.uiEditMode === "widgets" && zIndex > 1} + {#key $attrsChanged} +
+ + {#if container.attrs.title !== ""} + + {/if} +
+ {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} + {@const hidden = item?.attrs?.hidden} +
+ + {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} +
+ {/if} +
+ {/each}
- {/each} + {#if container.attrs.hidden && edit} +
+ {/if} + {#if showHandles} +
+ {/if} +
- {#if showHandles} -
- {/if} - -
+ {/key} {/if} diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 72ac37c..794064f 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -10,6 +10,8 @@ import type TypedEmitter from "typed-emitter"; import "@litegraph-ts/nodes-basic" import "@litegraph-ts/nodes-events" import "@litegraph-ts/nodes-math" +import "@litegraph-ts/nodes-strings" +import "$lib/nodes/index" import * as nodes from "$lib/nodes/index" import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas"; @@ -24,10 +26,13 @@ import { toast } from '@zerodevx/svelte-toast' import ComfyGraph from "$lib/ComfyGraph"; import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode"; import { get } from "svelte/store"; +import uiState from "$lib/stores/uiState"; +import { promptToGraphVis } from "$lib/utils"; export const COMFYBOX_SERIAL_VERSION = 1; LiteGraph.catch_exceptions = false; +LiteGraph.CANVAS_GRID_SIZE = 32; if (typeof window !== "undefined") { // Load default visibility @@ -52,9 +57,11 @@ export type SerializedPromptInputs = { class_type: string } +export type SerializedPromptOutput = Record + export type SerializedPrompt = { workflow: SerializedLGraph, - output: Record + output: SerializedPromptOutput } export type Progress = { @@ -318,12 +325,12 @@ export default class ComfyApp { } // Distinguish frontend/backend connections - const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "IMAGE", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"] + const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"] for (const type of BACKEND_TYPES) { setColor(type, "orange") } - setColor("OUTPUT", "rebeccapurple") + setColor("IMAGE", "rebeccapurple") setColor(BuiltInSlotType.EVENT, "lightseagreen") setColor(BuiltInSlotType.ACTION, "lightseagreen") } @@ -418,7 +425,7 @@ export default class ComfyApp { * Converts the current graph workflow for sending to the API * @returns The workflow and node links */ - async graphToPrompt(): Promise { + async graphToPrompt(tag: string | null = null): Promise { // Run frontend-only logic this.lGraph.runStep(1) @@ -436,6 +443,11 @@ export default class ComfyApp { const node = node_ as ComfyBackendNode; + if (tag && node.tags.indexOf(tag) === -1) { + console.debug("Skipping tagged node", tag, node.tags) + continue; + } + if (node.mode === NodeMode.NEVER) { // Don't serialize muted nodes continue; @@ -449,6 +461,11 @@ export default class ComfyApp { const inp = node.inputs[i]; const inputLink = node.getInputLink(i) const inputNode = node.getInputNode(i) + + if (inputNode && tag && "tags" in inputNode && (inputNode.tags as string[]).indexOf(tag) === -1) { + continue; + } + if (!inputLink || !inputNode) { if ("config" in inp) { const defaultValue = (inp as IComfyInputSlot).config?.defaultValue @@ -487,17 +504,36 @@ export default class ComfyApp { if (parent) { const seen = {} let link = node.getInputLink(i); - while (parent && !parent.isBackendNode) { + + const isValidParent = (parent: ComfyGraphNode) => { + if (!parent || parent.isBackendNode) + return false; + if ("tags" in parent && (parent.tags as string[]).indexOf(tag) === -1) + return false; + return true; + } + + while (isValidParent(parent)) { link = parent.getInputLink(link.origin_slot); if (link && !seen[link.id]) { seen[link.id] = true - parent = parent.getInputNode(link.origin_slot) as ComfyGraphNode; + const inputNode = parent.getInputNode(link.origin_slot) as ComfyGraphNode; + if (inputNode && "tags" in inputNode && tag && (inputNode.tags as string[]).indexOf(tag) === -1) { + console.debug("Skipping tagged parent node", tag, node.tags) + parent = null; + } + else { + parent = inputNode; + } } else { parent = null; } } if (link && parent && parent.isBackendNode) { + if ("tags" in parent && tag && (parent.tags as string[]).indexOf(tag) === -1) + continue; + const input = node.inputs[i] // TODO can null be a legitimate value in some cases? // Nodes like CLIPLoader will never have a value in the frontend, hence "null". @@ -520,15 +556,19 @@ export default class ComfyApp { if (Array.isArray(output[o].inputs[i]) && output[o].inputs[i].length === 2 && !output[output[o].inputs[i][0]]) { + console.debug("Prune removed node link", o, i, output[o].inputs[i]) delete output[o].inputs[i]; } } } + console.warn({ workflow, output }) + console.warn(promptToGraphVis({ workflow, output })) + return { workflow, output }; } - async queuePrompt(num: number, batchCount: number = 1) { + async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) { this.queueItems.push({ num, batchCount }); // Only have one action process the items so each one gets a unique seed correctly @@ -536,14 +576,23 @@ export default class ComfyApp { return; } + if (tag === "") + tag = null; + this.processingQueue = true; try { while (this.queueItems.length) { ({ num, batchCount } = this.queueItems.pop()); - console.log(`Queue get! ${num} ${batchCount}`); + console.debug(`Queue get! ${num} ${batchCount} ${tag}`); for (let i = 0; i < batchCount; i++) { - const p = await this.graphToPrompt(); + for (const node of this.lGraph._nodes_in_order) { + if ("beforeQueued" in node) { + (node as ComfyGraphNode).beforeQueued(); + } + } + + const p = await this.graphToPrompt(tag); try { await this.api.queuePrompt(num, p); @@ -624,12 +673,8 @@ export default class ComfyApp { if ("config" in input) { const comfyInput = input as IComfyInputSlot; - console.warn("RefreshCombo", comfyInput.defaultWidgetNode, comfyInput) - if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) { comfyInput.config.values = def["input"]["required"][comfyInput.name][0]; - - console.warn("RefreshCombo", comfyInput.config.values, def["input"]["required"][comfyInput.name]) const inputNode = node.getInputNode(index) if (inputNode && "doAutoConfig" in inputNode) { diff --git a/src/lib/components/ComfyComboProperty.svelte b/src/lib/components/ComfyComboProperty.svelte new file mode 100644 index 0000000..11b746e --- /dev/null +++ b/src/lib/components/ComfyComboProperty.svelte @@ -0,0 +1,56 @@ + + + + + diff --git a/src/lib/components/ComfyNumberProperty.svelte b/src/lib/components/ComfyNumberProperty.svelte new file mode 100644 index 0000000..5e96516 --- /dev/null +++ b/src/lib/components/ComfyNumberProperty.svelte @@ -0,0 +1,45 @@ + + + + + diff --git a/src/lib/components/ComfyProperties.svelte b/src/lib/components/ComfyProperties.svelte new file mode 100644 index 0000000..2d9c617 --- /dev/null +++ b/src/lib/components/ComfyProperties.svelte @@ -0,0 +1,310 @@ + + +
+
+
+ + {target?.attrs?.title || node?.title || "Workflow"} + {#if targetType !== ""} + ({targetType}) + {/if} + +
+
+
+ {#each ALL_ATTRIBUTES as category(category.categoryName)} +
+ + {category.categoryName} + +
+ {#each category.specs as spec(spec.name)} + {#if spec.location === "widget" && target && spec.name in target.attrs} +
+ {#if spec.type === "string"} + updateAttribute(spec, target, e.detail)} + on:input={(e) => updateAttribute(spec, target, e.detail)} + label={spec.name} + max_lines={1} + /> + {:else if spec.type === "boolean"} + updateAttribute(spec, target, e.detail)} + label={spec.name} + /> + {:else if spec.type === "number"} + updateAttribute(spec, target, e.detail)} + /> + {:else if spec.type === "enum"} + updateAttribute(spec, target, e.detail)} + /> + {/if} +
+ {:else if node} + {#if spec.location === "nodeProps" && validNodeProperty(spec, node)} +
+ {#if spec.type === "string"} + updateProperty(spec, e.detail)} + on:input={(e) => updateProperty(spec, e.detail)} + label={spec.name} + max_lines={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 spec.location === "nodeVars" && spec.name in node} +
+ {#if spec.type === "string"} + updateVar(spec, e.detail)} + on:input={(e) => updateVar(spec, e.detail)} + label={spec.name} + max_lines={1} + /> + {:else if spec.type === "boolean"} + updateVar(spec, e.detail)} + 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 spec.location === "workflow" && spec.name in $layoutState.attrs} +
+ {#if spec.type === "string"} + updateWorkflowAttribute(spec, e.detail)} + on:input={(e) => updateWorkflowAttribute(spec, e.detail)} + label={spec.name} + max_lines={1} + /> + {:else if spec.type === "boolean"} + updateWorkflowAttribute(spec, e.detail)} + 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} +
+
+ + diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index de2af8e..291b082 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -57,18 +57,18 @@ $: if (entries) { _entries = [] - for (const entry of entries) { - for (const outputs of Object.values(entry.outputs)) { - const allImages = outputs.images.map(r => { - // TODO configure backend URL - const url = "http://localhost:8188/view?" - const params = new URLSearchParams(r) - return url + params - }); - - _entries.push({ allImages, name: "Output" }) - } - } + // for (const entry of entries) { + // for (const outputs of Object.values(entry.outputs)) { + // const allImages = outputs.images.map(r => { + // // TODO configure backend URL + // const url = "http://localhost:8188/view?" + // const params = new URLSearchParams(r) + // return url + params + // }); + // + // _entries.push({ allImages, name: "Output" }) + // } + // } } diff --git a/src/lib/components/WidgetContainer.svelte b/src/lib/components/WidgetContainer.svelte index 1fd3b41..261e414 100644 --- a/src/lib/components/WidgetContainer.svelte +++ b/src/lib/components/WidgetContainer.svelte @@ -5,11 +5,15 @@ import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState"; import { startDrag, stopDrag } from "$lib/utils" import BlockContainer from "./BlockContainer.svelte" + import { type Writable } from "svelte/store" + import type { ComfyWidgetNode } from "$lib/nodes"; export let dragItem: IDragItem | null = null; export let zIndex: number = 0; export let classes: string[] = []; let container: ContainerLayout | null = null; + let attrsChanged: Writable | null = null; + let propsChanged: Writable | null = null; let widget: WidgetLayout | null = null; let showHandles: boolean = false; @@ -17,14 +21,23 @@ dragItem = null; container = null; widget = null; + attrsChanged = null; + propsChanged = null; } else if (dragItem.type === "container") { container = dragItem as ContainerLayout; + attrsChanged = container.attrsChanged; widget = null; + propsChanged = null; } else if (dragItem.type === "widget") { widget = dragItem as WidgetLayout; + attrsChanged = widget.attrsChanged; container = null; + if (widget.node && "propsChanged" in widget.node) + propsChanged = (widget.node as ComfyWidgetNode).propsChanged + else + propsChanged = null; } $: showHandles = $uiState.uiEditMode === "widgets" // TODO @@ -35,21 +48,38 @@ $: if ($queueState && widget && widget.node) { dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id; } + + function getWidgetClass() { + const title = widget.node.type.replace("/", "-").replace(".", "-") + return `widget--${title}` + } {#if container} - + {#key $attrsChanged} + + {/key} {:else if widget && widget.node} -
1} - class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)} - class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id} - > - -
- {#if showHandles} -
- {/if} + {@const edit = $uiState.uiEditMode === "widgets" && zIndex > 1} + {#key $attrsChanged} + {#key $propsChanged} +
+ +
+ {#if widget.attrs.hidden && edit} +
+ {/if} + {#if showHandles} +
+ {/if} + {/key} + {/key} {/if}