From bb4e65d4e110035a9002fc807085953cdf4b79f9 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:16:57 +0100 Subject: [PATCH] Save group nodes to templates --- web/extensions/core/groupNode.js | 32 ++++--- web/extensions/core/nodeTemplates.js | 135 +++++++++++++++------------ 2 files changed, 95 insertions(+), 72 deletions(-) diff --git a/web/extensions/core/groupNode.js b/web/extensions/core/groupNode.js index 8442d42f4..cc6642d4a 100644 --- a/web/extensions/core/groupNode.js +++ b/web/extensions/core/groupNode.js @@ -2,10 +2,22 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; import { getWidgetType } from "../../scripts/widgets.js"; -const IS_GROUP_NODE = Symbol(); -const GROUP_DATA = Symbol(); +export const IS_GROUP_NODE = Symbol(); +export const GROUP_DATA = Symbol(); const GROUP_SLOTS = Symbol(); +export async function registerGroupNodes(groupNodes, source, prefix) { + if (!groupNodes) return; + + for (const g in groupNodes) { + const def = buildNodeDef(groupNodes[g], g, globalDefs, source); + if (prefix) { + def.display_name = prefix + "/" + def.display_name; + } + await app.registerNodeDef(source + "/" + g, def); + } +} + function getLinks(config) { const linksFrom = {}; const linksTo = {}; @@ -30,7 +42,7 @@ function getLinks(config) { return { linksTo, linksFrom }; } -function buildNodeDef(config, nodeName, defs, workflow) { +function buildNodeDef(config, nodeName, defs, source = "workflow") { const slots = { inputs: {}, widgets: {}, @@ -43,7 +55,7 @@ function buildNodeDef(config, nodeName, defs, workflow) { output_is_list: [], name: nodeName, display_name: nodeName, - category: "group nodes" + (workflow ? "/workflow" : ""), + category: "group nodes" + ("/" + source), input: { required: {} }, [IS_GROUP_NODE]: true, @@ -221,7 +233,7 @@ class ConvertToGroupAction { link.push(type); } - const def = buildNodeDef(config, name, globalDefs, true); + const def = buildNodeDef(config, name, globalDefs); await app.registerNodeDef("workflow/" + name, def); return { config, def }; } @@ -380,13 +392,7 @@ const ext = { } }, async beforeConfigureGraph(graphData) { - const groupNodes = graphData?.extra?.groupNodes; - if (!groupNodes) return; - - for (const name in groupNodes) { - const def = buildNodeDef(groupNodes[name], name, globalDefs, true); - await app.registerNodeDef("workflow/" + name, def); - } + registerGroupNodes(graphData?.extra?.groupNodes, "workflow"); }, addCustomNodeDefs(defs) { globalDefs = defs; @@ -579,7 +585,7 @@ const ext = { for (const innerWidget of innerNode.widgets ?? []) { const groupWidgetName = slots.widgets[i]?.[innerWidget.name]; - if(!groupWidgetName) continue; + if (!groupWidgetName) continue; const groupWidget = node.widgets.find((w) => w.name === groupWidgetName); if (groupWidget) { innerWidget.value = groupWidget.value; diff --git a/web/extensions/core/nodeTemplates.js b/web/extensions/core/nodeTemplates.js index 434491075..6b8ef7a12 100644 --- a/web/extensions/core/nodeTemplates.js +++ b/web/extensions/core/nodeTemplates.js @@ -1,5 +1,6 @@ import { app } from "../../scripts/app.js"; import { ComfyDialog, $el } from "../../scripts/ui.js"; +import { GROUP_DATA, IS_GROUP_NODE, registerGroupNodes } from "./groupNode.js"; // Adds the ability to save and add multiple nodes as a template // To save: @@ -27,7 +28,7 @@ class ManageTemplates extends ComfyDialog { type: "file", accept: ".json", multiple: true, - style: {display: "none"}, + style: { display: "none" }, parent: document.body, onchange: () => this.importAll(), }); @@ -124,13 +125,13 @@ class ManageTemplates extends ComfyDialog { return; } - const json = JSON.stringify({templates: this.templates}, null, 2); // convert the data to a JSON string - const blob = new Blob([json], {type: "application/json"}); + const json = JSON.stringify({ templates: this.templates }, null, 2); // convert the data to a JSON string + const blob = new Blob([json], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = $el("a", { href: url, download: "node_templates.json", - style: {display: "none"}, + style: { display: "none" }, parent: document.body, }); a.click(); @@ -168,48 +169,44 @@ class ManageTemplates extends ComfyDialog { }), ] ), - $el( - "div", - {}, - [ - $el("button", { - textContent: "Export", - style: { - fontSize: "12px", - fontWeight: "normal", - }, - onclick: (e) => { - const json = JSON.stringify({templates: [t]}, null, 2); // convert the data to a JSON string - const blob = new Blob([json], {type: "application/json"}); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: (nameInput.value || t.name) + ".json", - style: {display: "none"}, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - }, - }), - $el("button", { - textContent: "Delete", - style: { - fontSize: "12px", - color: "red", - fontWeight: "normal", - }, - onclick: (e) => { - nameInput.value = ""; - e.target.parentElement.style.display = "none"; - e.target.parentElement.previousElementSibling.style.display = "none"; - }, - }), - ] - ), + $el("div", {}, [ + $el("button", { + textContent: "Export", + style: { + fontSize: "12px", + fontWeight: "normal", + }, + onclick: (e) => { + const json = JSON.stringify({ templates: [t] }, null, 2); // convert the data to a JSON string + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = $el("a", { + href: url, + download: (nameInput.value || t.name) + ".json", + style: { display: "none" }, + parent: document.body, + }); + a.click(); + setTimeout(function () { + a.remove(); + window.URL.revokeObjectURL(url); + }, 0); + }, + }), + $el("button", { + textContent: "Delete", + style: { + fontSize: "12px", + color: "red", + fontWeight: "normal", + }, + onclick: (e) => { + nameInput.value = ""; + e.target.parentElement.style.display = "none"; + e.target.parentElement.previousElementSibling.style.display = "none"; + }, + }), + ]), ]; }) ) @@ -222,11 +219,11 @@ app.registerExtension({ setup() { const manage = new ManageTemplates(); - const clipboardAction = (cb) => { + const clipboardAction = async (cb) => { // We use the clipboard functions but dont want to overwrite the current user clipboard // Restore it after we've run our callback const old = localStorage.getItem("litegrapheditor_clipboard"); - cb(); + await cb(); localStorage.setItem("litegrapheditor_clipboard", old); }; @@ -240,13 +237,29 @@ app.registerExtension({ disabled: !Object.keys(app.canvas.selected_nodes || {}).length, callback: () => { const name = prompt("Enter name"); - if (!name || !name.trim()) return; + if (!name?.trim()) return; clipboardAction(() => { app.canvas.copyToClipboard(); + let data = localStorage.getItem("litegrapheditor_clipboard"); + data = JSON.parse(data); + const nodeIds = Object.keys(app.canvas.selected_nodes); + for (let i = 0; i < nodeIds.length; i++) { + const node = app.graph.getNodeById(nodeIds[i]); + const nodeData = node?.constructor.nodeData; + if (nodeData?.[IS_GROUP_NODE]) { + const groupData = nodeData[GROUP_DATA]; + if (!data.groupNodes) { + data.groupNodes = {}; + } + data.groupNodes[nodeData.name] = groupData; + data.nodes[i].type = "workflow/" + nodeData.name; + } + } + manage.templates.push({ name, - data: localStorage.getItem("litegrapheditor_clipboard"), + data: JSON.stringify(data), }); manage.store(); }); @@ -254,15 +267,19 @@ app.registerExtension({ }); // Map each template to a menu item - const subItems = manage.templates.map((t) => ({ - content: t.name, - callback: () => { - clipboardAction(() => { - localStorage.setItem("litegrapheditor_clipboard", t.data); - app.canvas.pasteFromClipboard(); - }); - }, - })); + const subItems = manage.templates.map((t) => { + return { + content: t.name, + callback: () => { + clipboardAction(async () => { + const data = JSON.parse(t.data); + await registerGroupNodes(data.groupNodes, "workflow", t.name); + localStorage.setItem("litegrapheditor_clipboard", t.data); + app.canvas.pasteFromClipboard(); + }); + }, + }; + }); subItems.push(null, { content: "Manage",