comfyanonymous
2 years ago
3 changed files with 215 additions and 19 deletions
@ -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; |
||||||
|
}; |
||||||
|
}, |
||||||
|
}); |
Loading…
Reference in new issue