185 lines
4.0 KiB
JavaScript
185 lines
4.0 KiB
JavaScript
|
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;
|
||
|
};
|
||
|
},
|
||
|
});
|