From 4ae4e716168e1367a4f2649c097c9891f288a52d Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 24 May 2023 21:00:48 -0500 Subject: [PATCH] Template saving/loading --- litegraph | 2 +- package.json | 1 + pnpm-lock.yaml | 10 ++ public/image/graph-bg.png | Bin 0 -> 350 bytes src/lib/ComfyBoxTemplate.ts | 11 ++- src/lib/ComfyGraph.ts | 89 +++++++++++++++++- src/lib/ComfyGraphCanvas.ts | 86 +++++------------ src/lib/components/ComfyApp.ts | 3 +- src/lib/components/ComfyTemplates.svelte | 84 +++++++++++++++-- src/lib/components/GlobalModal.svelte | 16 +++- src/lib/components/Modal.svelte | 5 +- src/lib/components/menu/Menu.svelte | 84 ++++++++++++----- .../components/modal/EditTemplateModal.svelte | 26 +++-- .../modal/SerializedLayoutPreviewNode.svelte | 2 +- src/lib/components/utils.ts | 35 ++++++- src/lib/nodes/widgets/ComfyWidgetNode.ts | 3 + src/lib/stores/layoutStates.ts | 86 ++++++++++++++++- src/lib/stores/templateState.ts | 27 ++++++ src/scss/global.scss | 1 + vite.config.ts | 6 +- 20 files changed, 457 insertions(+), 120 deletions(-) create mode 100644 public/image/graph-bg.png diff --git a/litegraph b/litegraph index a1bf4cb..fd0c428 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit a1bf4cb511c46831b371d79d1495b76052beecce +Subproject commit fd0c428237523606d0a339a81c6e244b3c306790 diff --git a/package.json b/package.json index 4df6182..aa66494 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "style-mod": "^4.0.3", "svelte-bootstrap-icons": "^2.3.1", "svelte-feather-icons": "^4.0.0", + "svelte-floating-ui": "^1.5.2", "svelte-preprocess": "^5.0.3", "svelte-select": "^5.5.3", "svelte-splitpanes": "^0.7.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7cfc57..8314f4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,6 +151,9 @@ importers: svelte-feather-icons: specifier: ^4.0.0 version: 4.0.0 + svelte-floating-ui: + specifier: ^1.5.2 + version: 1.5.2 svelte-preprocess: specifier: ^5.0.3 version: 5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3) @@ -8135,6 +8138,13 @@ packages: '@floating-ui/dom': 1.2.8 dev: false + /svelte-floating-ui@1.5.2: + resolution: {integrity: sha512-nV50eno74CEsFfFJ6iyN/oNYDEOck1TZjGV3lmJksVRbiiUAVF6bHspyAhR7GZ7c/4qbRWp9UyX24J+UXdEpag==} + dependencies: + '@floating-ui/core': 1.2.6 + '@floating-ui/dom': 1.2.8 + dev: false + /svelte-hmr@0.15.1(svelte@3.58.0): resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} engines: {node: ^12.20 || ^14.13.1 || >= 16} diff --git a/public/image/graph-bg.png b/public/image/graph-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..2feb956e60508cdf4677b214a370f53809ce3c37 GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^DImLX_-k74fcJ(mk*3VY}OKW@71{qOedm+@P#J*>RF zZFTVeb+4=DXMcM>OI{~pL&CwP)Rp3Y47_q^{#ucHxg{Zh6K$D*7ZOh8(3+=OrS=td$U=&rva66 zPXqF#Vh>ht25AK94*I|q2jua9Oiehr={>7_V3b#)Vdw`#uo+Mj|K{m4vN1fn;`)_M Tb^jM&fH8Qw`njxgN@xNA nodes[i].pos[0]) { + posMin[0] = nodes[i].pos[0]; + posMinIndexes[0] = i; + } + if (posMin[1] > nodes[i].pos[1]) { + posMin[1] = nodes[i].pos[1]; + posMinIndexes[1] = i; + } + } + else { + posMin = [nodes[i].pos[0], nodes[i].pos[1]]; + posMinIndexes = [i, i]; + } + } + + return posMin; +} type ComfyGraphEvents = { configured: (graph: LGraph) => void @@ -38,6 +63,10 @@ export default class ComfyGraph extends LGraph { return workflowState.getWorkflow(workflowID) } + get layout(): WritableLayoutStateStore | null { + return this.workflow?.layout; + } + constructor(workflowID?: WorkflowInstID) { super(); this.workflowID = workflowID; @@ -218,4 +247,62 @@ export default class ComfyGraph extends LGraph { // console.debug("ConnectionChange", node); this.eventBus.emit("nodeConnectionChanged", kind, node, slot, targetNode, targetSlot); } + + /* + * Inserts a template. + * Layout deserialization must be handled afterwards! + */ + insertTemplate(template: SerializedComfyBoxTemplate, pos: Vector2): Record { + const minPos = calculateMinPosOfNodes(template.nodes); + + const templateNodeIDToNewNode: Record = {} + + var nodes = []; + for (var i = 0; i < template.nodes.length; ++i) { + var node_data = template.nodes[i]; + var node = LiteGraph.createNode(node_data.type); + + let mapping: GraphIDMapping = null; + if (node_data.type === "graph/subgraph") { + mapping = reassignGraphIDs((node_data as any).subgraph as SerializedLGraph); + } + + if (node) { + const prevNodeId = node_data.id; + node_data.id = uuidv4(); + templateNodeIDToNewNode[prevNodeId] = node + + node.configure(node_data); + + if (mapping) { + for (const subnode of (node as Subgraph).subgraph.iterateNodesInOrderRecursive()) { + const oldNodeID = mapping.nodeIDs[subnode.id]; + templateNodeIDToNewNode[oldNodeID] = subnode; + } + } + + node.pos[0] += pos[0] - minPos[0]; //+= 5; + node.pos[1] += pos[1] - minPos[1]; //+= 5; + + this.add(node, { doProcessChange: false, addedBy: "template" as any }); + + nodes.push(node); + } + } + + //create links + for (var i = 0; i < template.links.length; ++i) { + var link_info = template.links[i]; + var origin_node = templateNodeIDToNewNode[link_info[0]]; + var target_node = templateNodeIDToNewNode[link_info[2]]; + if (origin_node && target_node) + origin_node.connect(link_info[1], target_node, link_info[3]); + else + console.error("[ComfyGraphCanvas] nodes missing on template insertion!", link_info); + } + + this.afterChange(); + + return templateNodeIDToNewNode; + } } diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index af302c9..c5738dd 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -3,47 +3,22 @@ import { get, type Unsubscriber } from "svelte/store"; import type ComfyGraph from "./ComfyGraph"; import type ComfyApp from "./components/ComfyApp"; import { ComfyReroute } from "./nodes"; -import layoutStates from "./stores/layoutStates"; +import layoutStates, { type ContainerLayout } from "./stores/layoutStates"; import queueState from "./stores/queueState"; import selectionState from "./stores/selectionState"; import templateState from "./stores/templateState"; import { createTemplate, type ComfyBoxTemplate, serializeTemplate, type SerializedComfyBoxTemplate } from "./ComfyBoxTemplate"; import notify from "./notify"; -import { v4 as uuidv4 } from "uuid" -import { download } from "./utils"; export type SerializedGraphCanvasState = { offset: Vector2, scale: number } -function getMinPos(nodes: SerializedLGraphNode[]): Vector2 { - var posMin: Vector2 = [0, 0] - var posMinIndexes: [number, number] | null = null; - - for (var i = 0; i < nodes.length; ++i) { - if (posMin) { - if (posMin[0] > nodes[i].pos[0]) { - posMin[0] = nodes[i].pos[0]; - posMinIndexes[0] = i; - } - if (posMin[1] > nodes[i].pos[1]) { - posMin[1] = nodes[i].pos[1]; - posMinIndexes[1] = i; - } - } - else { - posMin = [nodes[i].pos[0], nodes[i].pos[1]]; - posMinIndexes = [i, i]; - } - } - - return posMin; -} - export default class ComfyGraphCanvas extends LGraphCanvas { app: ComfyApp | null; private _unsubscribe: Unsubscriber; + isExportingSVG: boolean = false; get comfyGraph(): ComfyGraph | null { return this.graph as ComfyGraph; @@ -470,45 +445,21 @@ export default class ComfyGraphCanvas extends LGraphCanvas { * Inserts a ComfyBox template. Logic is similar to pasting from the * clipboard in vanilla litegraph. */ - insertTemplate(template: SerializedComfyBoxTemplate, pos: Vector2) { - const minPos = getMinPos(template.nodes); + insertTemplate(template: SerializedComfyBoxTemplate, pos: Vector2, container: ContainerLayout, containerIndex: number): [LGraphNode[], IDragItem] { + const comfyGraph = this.graph as ComfyGraph; - const templateNodeIDToNewNode: Record = {} - - var nodes = []; - for (var i = 0; i < template.nodes.length; ++i) { - var node_data = template.nodes[i]; - var node = LiteGraph.createNode(node_data.type); - if (node) { - const prevNodeId = node_data.id; - node_data.id = uuidv4(); - templateNodeIDToNewNode[prevNodeId] = node - - node.configure(node_data); - - node.pos[0] += pos[0] - minPos[0]; //+= 5; - node.pos[1] += pos[1] - minPos[1]; //+= 5; - - this.graph.add(node, { doProcessChange: false, addedBy: "template" as any }); - - nodes.push(node); - } + const layout = comfyGraph.layout; + if (layout == null) { + console.error("[ComfyGraphCanvas] graph has no layout!", comfyGraph) + return; } - //create links - for (var i = 0; i < template.links.length; ++i) { - var link_info = template.links[i]; - var origin_node = templateNodeIDToNewNode[link_info[0]]; - var target_node = templateNodeIDToNewNode[link_info[2]]; - if (origin_node && target_node) - origin_node.connect(link_info[1], target_node, link_info[3]); - else - console.error("[ComfyGraphCanvas] nodes missing on template insertion!", link_info); - } + const nodeMapping = comfyGraph.insertTemplate(template, pos); + const templateLayoutRoot = layout.insertTemplate(template, comfyGraph, nodeMapping, container, containerIndex); - this.selectNodes(nodes); + this.selectNodes(Object.values(nodeMapping).filter(n => n.graph === this.graph)); - this.graph.afterChange(); + return [Object.values(nodeMapping), templateLayoutRoot] } saveAsTemplate(_value: IContextMenuItem, _options, mouseEvent, prevMenu, node?: LGraphNode) { @@ -593,4 +544,17 @@ export default class ComfyGraphCanvas extends LGraphCanvas { return options } + + override onRenderBackground(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): boolean { + if (this.isExportingSVG) { + ctx.clearRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3] + ); + return true; + } + return false; + } } diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index b0b1b8e..9d9ce64 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -1013,7 +1013,8 @@ export default class ComfyApp { closeOnClick: false, showCloseButton: false, svelteProps: { - templateAndSvg + templateAndSvg, + editable: false }, buttons: [ { diff --git a/src/lib/components/ComfyTemplates.svelte b/src/lib/components/ComfyTemplates.svelte index 2b1bdeb..f9daa0d 100644 --- a/src/lib/components/ComfyTemplates.svelte +++ b/src/lib/components/ComfyTemplates.svelte @@ -1,8 +1,8 @@
@@ -92,9 +150,10 @@ on:consider={handleDndConsider} on:finalize={handleDndFinalize}> {#each _sorted.filter(i => i.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} -
-
{truncateString(item.template.metadata.title, 16)}
-
{truncateString(item.template.metadata.description, 24)}
+ +
handleClick(item)}> +
{item.template.metadata.title}
+
{item.template.metadata.description}
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
@@ -140,6 +199,9 @@ background: var(--panel-background-fill); max-height: 14rem; position: relative; + user-select: none; + text-overflow: ellipsis; + overflow: hidden; font-size: 13pt; .template-desc { @@ -147,11 +209,19 @@ font-size: 11pt; } + &.draggable { + border: 5px dashed var(--secondary-500); + margin: 0.2em; + } + &:hover:not(:has(img:hover)):not(:has(button:hover)) { + cursor: pointer; + background: var(--block-background-fill); + &.draggable { cursor: grab; + background: var(--secondary-700); } - background: var(--block-background-fill); &.running { background: var(--comfy-accent-soft); diff --git a/src/lib/components/GlobalModal.svelte b/src/lib/components/GlobalModal.svelte index 01bbe5e..25e76bd 100644 --- a/src/lib/components/GlobalModal.svelte +++ b/src/lib/components/GlobalModal.svelte @@ -29,9 +29,11 @@ {/if}
- {#if modal != null && modal.svelteComponent != null} - - {/if} +
{#if modal != null && modal.buttons?.length > 0} @@ -52,6 +54,12 @@ diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte index fe6a6b9..63f82a5 100644 --- a/src/lib/components/Modal.svelte +++ b/src/lib/components/Modal.svelte @@ -94,9 +94,6 @@ display: flex; flex-direction: row; padding-top: 0.5em; - } - - .button-row, .buttons { - gap: var(--spacing-sm); + gap: var(--spacing-md); } diff --git a/src/lib/components/menu/Menu.svelte b/src/lib/components/menu/Menu.svelte index 6322a94..34deacf 100644 --- a/src/lib/components/menu/Menu.svelte +++ b/src/lib/components/menu/Menu.svelte @@ -1,35 +1,73 @@ - - @@ -75,7 +90,7 @@