Save group nodes to templates

This commit is contained in:
pythongosssss 2023-10-22 14:16:57 +01:00
parent 288a1c6242
commit bb4e65d4e1
2 changed files with 95 additions and 72 deletions

View File

@ -2,10 +2,22 @@ import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"; import { api } from "../../scripts/api.js";
import { getWidgetType } from "../../scripts/widgets.js"; import { getWidgetType } from "../../scripts/widgets.js";
const IS_GROUP_NODE = Symbol(); export const IS_GROUP_NODE = Symbol();
const GROUP_DATA = Symbol(); export const GROUP_DATA = Symbol();
const GROUP_SLOTS = 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) { function getLinks(config) {
const linksFrom = {}; const linksFrom = {};
const linksTo = {}; const linksTo = {};
@ -30,7 +42,7 @@ function getLinks(config) {
return { linksTo, linksFrom }; return { linksTo, linksFrom };
} }
function buildNodeDef(config, nodeName, defs, workflow) { function buildNodeDef(config, nodeName, defs, source = "workflow") {
const slots = { const slots = {
inputs: {}, inputs: {},
widgets: {}, widgets: {},
@ -43,7 +55,7 @@ function buildNodeDef(config, nodeName, defs, workflow) {
output_is_list: [], output_is_list: [],
name: nodeName, name: nodeName,
display_name: nodeName, display_name: nodeName,
category: "group nodes" + (workflow ? "/workflow" : ""), category: "group nodes" + ("/" + source),
input: { required: {} }, input: { required: {} },
[IS_GROUP_NODE]: true, [IS_GROUP_NODE]: true,
@ -221,7 +233,7 @@ class ConvertToGroupAction {
link.push(type); link.push(type);
} }
const def = buildNodeDef(config, name, globalDefs, true); const def = buildNodeDef(config, name, globalDefs);
await app.registerNodeDef("workflow/" + name, def); await app.registerNodeDef("workflow/" + name, def);
return { config, def }; return { config, def };
} }
@ -380,13 +392,7 @@ const ext = {
} }
}, },
async beforeConfigureGraph(graphData) { async beforeConfigureGraph(graphData) {
const groupNodes = graphData?.extra?.groupNodes; registerGroupNodes(graphData?.extra?.groupNodes, "workflow");
if (!groupNodes) return;
for (const name in groupNodes) {
const def = buildNodeDef(groupNodes[name], name, globalDefs, true);
await app.registerNodeDef("workflow/" + name, def);
}
}, },
addCustomNodeDefs(defs) { addCustomNodeDefs(defs) {
globalDefs = defs; globalDefs = defs;
@ -579,7 +585,7 @@ const ext = {
for (const innerWidget of innerNode.widgets ?? []) { for (const innerWidget of innerNode.widgets ?? []) {
const groupWidgetName = slots.widgets[i]?.[innerWidget.name]; const groupWidgetName = slots.widgets[i]?.[innerWidget.name];
if(!groupWidgetName) continue; if (!groupWidgetName) continue;
const groupWidget = node.widgets.find((w) => w.name === groupWidgetName); const groupWidget = node.widgets.find((w) => w.name === groupWidgetName);
if (groupWidget) { if (groupWidget) {
innerWidget.value = groupWidget.value; innerWidget.value = groupWidget.value;

View File

@ -1,5 +1,6 @@
import { app } from "../../scripts/app.js"; import { app } from "../../scripts/app.js";
import { ComfyDialog, $el } from "../../scripts/ui.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 // Adds the ability to save and add multiple nodes as a template
// To save: // To save:
@ -27,7 +28,7 @@ class ManageTemplates extends ComfyDialog {
type: "file", type: "file",
accept: ".json", accept: ".json",
multiple: true, multiple: true,
style: {display: "none"}, style: { display: "none" },
parent: document.body, parent: document.body,
onchange: () => this.importAll(), onchange: () => this.importAll(),
}); });
@ -124,13 +125,13 @@ class ManageTemplates extends ComfyDialog {
return; return;
} }
const json = JSON.stringify({templates: this.templates}, null, 2); // convert the data to a JSON string 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 blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = $el("a", { const a = $el("a", {
href: url, href: url,
download: "node_templates.json", download: "node_templates.json",
style: {display: "none"}, style: { display: "none" },
parent: document.body, parent: document.body,
}); });
a.click(); a.click();
@ -168,48 +169,44 @@ class ManageTemplates extends ComfyDialog {
}), }),
] ]
), ),
$el( $el("div", {}, [
"div", $el("button", {
{}, textContent: "Export",
[ style: {
$el("button", { fontSize: "12px",
textContent: "Export", fontWeight: "normal",
style: { },
fontSize: "12px", onclick: (e) => {
fontWeight: "normal", const json = JSON.stringify({ templates: [t] }, null, 2); // convert the data to a JSON string
}, const blob = new Blob([json], { type: "application/json" });
onclick: (e) => { const url = URL.createObjectURL(blob);
const json = JSON.stringify({templates: [t]}, null, 2); // convert the data to a JSON string const a = $el("a", {
const blob = new Blob([json], {type: "application/json"}); href: url,
const url = URL.createObjectURL(blob); download: (nameInput.value || t.name) + ".json",
const a = $el("a", { style: { display: "none" },
href: url, parent: document.body,
download: (nameInput.value || t.name) + ".json", });
style: {display: "none"}, a.click();
parent: document.body, setTimeout(function () {
}); a.remove();
a.click(); window.URL.revokeObjectURL(url);
setTimeout(function () { }, 0);
a.remove(); },
window.URL.revokeObjectURL(url); }),
}, 0); $el("button", {
}, textContent: "Delete",
}), style: {
$el("button", { fontSize: "12px",
textContent: "Delete", color: "red",
style: { fontWeight: "normal",
fontSize: "12px", },
color: "red", onclick: (e) => {
fontWeight: "normal", nameInput.value = "";
}, e.target.parentElement.style.display = "none";
onclick: (e) => { e.target.parentElement.previousElementSibling.style.display = "none";
nameInput.value = ""; },
e.target.parentElement.style.display = "none"; }),
e.target.parentElement.previousElementSibling.style.display = "none"; ]),
},
}),
]
),
]; ];
}) })
) )
@ -222,11 +219,11 @@ app.registerExtension({
setup() { setup() {
const manage = new ManageTemplates(); 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 // We use the clipboard functions but dont want to overwrite the current user clipboard
// Restore it after we've run our callback // Restore it after we've run our callback
const old = localStorage.getItem("litegrapheditor_clipboard"); const old = localStorage.getItem("litegrapheditor_clipboard");
cb(); await cb();
localStorage.setItem("litegrapheditor_clipboard", old); localStorage.setItem("litegrapheditor_clipboard", old);
}; };
@ -240,13 +237,29 @@ app.registerExtension({
disabled: !Object.keys(app.canvas.selected_nodes || {}).length, disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
callback: () => { callback: () => {
const name = prompt("Enter name"); const name = prompt("Enter name");
if (!name || !name.trim()) return; if (!name?.trim()) return;
clipboardAction(() => { clipboardAction(() => {
app.canvas.copyToClipboard(); 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({ manage.templates.push({
name, name,
data: localStorage.getItem("litegrapheditor_clipboard"), data: JSON.stringify(data),
}); });
manage.store(); manage.store();
}); });
@ -254,15 +267,19 @@ app.registerExtension({
}); });
// Map each template to a menu item // Map each template to a menu item
const subItems = manage.templates.map((t) => ({ const subItems = manage.templates.map((t) => {
content: t.name, return {
callback: () => { content: t.name,
clipboardAction(() => { callback: () => {
localStorage.setItem("litegrapheditor_clipboard", t.data); clipboardAction(async () => {
app.canvas.pasteFromClipboard(); 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, { subItems.push(null, {
content: "Manage", content: "Manage",