ComfyUI/web/extensions/core/nodeTemplates.js

185 lines
4.0 KiB
JavaScript
Raw Normal View History

import { app } from "/scripts/app.js";
import { ComfyDialog, $el } from "/scripts/ui.js";
// Adds the ability to save and add multiple nodes as a template
// To save:
// Select multiple nodes (ctrl + drag to select a region or ctrl+click individual nodes)
// Right click the canvas
// Save Node Template -> give it a name
//
// To add:
// Right click the canvas
// Node templates -> click the one to add
//
// To delete/rename:
// Right click the canvas
// Node templates -> Manage
const id = "Comfy.NodeTemplates";
class ManageTemplates extends ComfyDialog {
constructor() {
super();
this.element.classList.add("comfy-manage-templates");
this.templates = this.load();
}
createButtons() {
const btns = super.createButtons();
btns[0].textContent = "Cancel";
btns.unshift(
$el("button", {
type: "button",
textContent: "Save",
onclick: () => this.save(),
})
);
return btns;
}
load() {
const templates = localStorage.getItem(id);
if (templates) {
return JSON.parse(templates);
} else {
return [];
}
}
save() {
// Find all visible inputs and save them as our new list
const inputs = this.element.querySelectorAll("input");
const updated = [];
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
if (input.parentElement.style.display !== "none") {
const t = this.templates[i];
t.name = input.value.trim() || input.getAttribute("data-name");
updated.push(t);
}
}
this.templates = updated;
this.store();
this.close();
}
store() {
localStorage.setItem(id, JSON.stringify(this.templates));
}
show() {
// Show list of template names + delete button
super.show(
$el(
"div",
{
style: {
display: "grid",
gridTemplateColumns: "1fr auto",
gap: "5px",
},
},
this.templates.flatMap((t) => {
let nameInput;
return [
$el(
"label",
{
textContent: "Name: ",
},
[
$el("input", {
value: t.name,
dataset: { name: t.name },
$: (el) => (nameInput = el),
}),
]
),
$el("button", {
textContent: "Delete",
style: {
fontSize: "12px",
color: "red",
fontWeight: "normal",
},
onclick: (e) => {
nameInput.value = "";
e.target.style.display = "none";
e.target.previousElementSibling.style.display = "none";
},
}),
];
})
)
);
}
}
app.registerExtension({
name: id,
setup() {
const manage = new ManageTemplates();
const clipboardAction = (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();
localStorage.setItem("litegrapheditor_clipboard", old);
};
const orig = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = orig.apply(this, arguments);
options.push(null);
options.push({
content: `Save Selected as Template`,
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
callback: () => {
const name = prompt("Enter name");
if (!name || !name.trim()) return;
clipboardAction(() => {
app.canvas.copyToClipboard();
manage.templates.push({
name,
data: localStorage.getItem("litegrapheditor_clipboard"),
});
manage.store();
});
},
});
// 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();
});
},
}));
if (subItems.length) {
subItems.push(null, {
content: "Manage",
callback: () => manage.show(),
});
options.push({
content: "Node Templates",
submenu: {
options: subItems,
},
});
}
return options;
};
},
});