Browse Source

Merge branch 'node-templates' of https://github.com/pythongosssss/ComfyUI

pull/457/head
comfyanonymous 2 years ago
parent
commit
e07f8eab6a
  1. 184
      web/extensions/core/nodeTemplates.js
  2. 47
      web/scripts/ui.js
  3. 3
      web/style.css

184
web/extensions/core/nodeTemplates.js

@ -0,0 +1,184 @@
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;
};
},
});

47
web/scripts/ui.js

@ -8,14 +8,18 @@ export function $el(tag, propsOrChildren, children) {
if (Array.isArray(propsOrChildren)) {
element.append(...propsOrChildren);
} else {
const parent = propsOrChildren.parent;
const { parent, $: cb, dataset, style } = propsOrChildren;
delete propsOrChildren.parent;
const cb = propsOrChildren.$;
delete propsOrChildren.$;
delete propsOrChildren.dataset;
delete propsOrChildren.style;
if (propsOrChildren.style) {
Object.assign(element.style, propsOrChildren.style);
delete propsOrChildren.style;
if (style) {
Object.assign(element.style, style);
}
if (dataset) {
Object.assign(element.dataset, dataset);
}
Object.assign(element, propsOrChildren);
@ -76,7 +80,7 @@ function dragElement(dragEl, settings) {
dragEl.style.left = newPosX + "px";
dragEl.style.right = "unset";
}
dragEl.style.top = newPosY + "px";
dragEl.style.bottom = "unset";
@ -145,7 +149,7 @@ function dragElement(dragEl, settings) {
}
window.addEventListener("resize", () => {
ensureInBounds();
ensureInBounds();
});
function closeDragElement() {
@ -155,26 +159,33 @@ function dragElement(dragEl, settings) {
}
}
class ComfyDialog {
export class ComfyDialog {
constructor() {
this.element = $el("div.comfy-modal", { parent: document.body }, [
$el("div.comfy-modal-content", [
$el("p", { $: (p) => (this.textElement = p) }),
$el("button", {
type: "button",
textContent: "Close",
onclick: () => this.close(),
}),
]),
$el("div.comfy-modal-content", [$el("p", { $: (p) => (this.textElement = p) }), ...this.createButtons()]),
]);
}
createButtons() {
return [
$el("button", {
type: "button",
textContent: "Close",
onclick: () => this.close(),
}),
];
}
close() {
this.element.style.display = "none";
}
show(html) {
this.textElement.innerHTML = html;
if (typeof html === "string") {
this.textElement.innerHTML = html;
} else {
this.textElement.replaceChildren(html);
}
this.element.style.display = "flex";
}
}
@ -419,7 +430,7 @@ export class ComfyUI {
type: "boolean",
defaultValue: true,
});
const fileInput = $el("input", {
type: "file",
accept: ".json,image/png",

3
web/style.css

@ -202,7 +202,8 @@ button.comfy-queue-btn {
margin: 6px 0 !important;
}
.comfy-modal.comfy-settings {
.comfy-modal.comfy-settings,
.comfy-modal.comfy-manage-templates {
text-align: center;
font-family: sans-serif;
color: #999;

Loading…
Cancel
Save