mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-01-25 15:55:18 +00:00
Merge branch 'node-templates' of https://github.com/pythongosssss/ComfyUI
This commit is contained in:
commit
e07f8eab6a
184
web/extensions/core/nodeTemplates.js
Normal file
184
web/extensions/core/nodeTemplates.js
Normal file
@ -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;
|
||||
};
|
||||
},
|
||||
});
|
@ -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",
|
||||
|
@ -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…
Reference in New Issue
Block a user