Switch to node-based installation
This commit is contained in:
@@ -5,7 +5,7 @@ Extension for ComyUI that enables saving and loading node presets using the node
|
||||
Right click the node and choose "Presets" from the context menu to access the node's presets.
|
||||
|
||||
# Installation
|
||||
Clone this repository into your `ComfyUI/web/extensions` folder using `git clone https://github.com/BlakeOne/ComfyUI-NodePresets` and reload ComfyUI.
|
||||
Clone this repository into your `custom_nodes` folder using `git clone https://github.com/BlakeOne/ComfyUI-NodePresets` and reload ComfyUI.
|
||||
|
||||
# Props
|
||||
This extension is based on ComfyUI's [NodeTemplates](https://github.com/comfyanonymous/ComfyUI/blob/master/web/extensions/core/nodeTemplates.js) extension. :)
|
||||
|
||||
+386
@@ -1 +1,387 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||
|
||||
// Adds the ability to save and load node presets using the node's context menu
|
||||
// To save:
|
||||
// Right click on the node
|
||||
// Presets -> Save -> give it a name
|
||||
//
|
||||
// To load:
|
||||
// Right click on the node
|
||||
// Presets -> click the one to add
|
||||
//
|
||||
// To delete/rename:
|
||||
// Right click the node
|
||||
// Presets -> Manage
|
||||
//
|
||||
// To rearrange:
|
||||
// Open the manage dialog and Drag and drop elements using the "Name:" label as handle
|
||||
|
||||
const id = "Comfy.NodePresets";
|
||||
const file = "comfy.presets.json";
|
||||
|
||||
class PresetManager extends ComfyDialog {
|
||||
constructor() {
|
||||
super();
|
||||
this.load().then((v) => {
|
||||
this.presets = v;
|
||||
});
|
||||
|
||||
this.element.classList.add("comfy-manage-presets");
|
||||
this.draggedEl = null;
|
||||
this.saveVisualCue = null;
|
||||
this.emptyImg = new Image();
|
||||
this.emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
|
||||
|
||||
this.importInput = $el("input", {
|
||||
type: "file",
|
||||
accept: ".json",
|
||||
multiple: true,
|
||||
style: { display: "none" },
|
||||
parent: document.body,
|
||||
onchange: () => this.importAll()
|
||||
});
|
||||
}
|
||||
|
||||
createButtons() {
|
||||
const btns = super.createButtons();
|
||||
btns[0].textContent = "Close";
|
||||
btns[0].onclick = (e) => {
|
||||
clearTimeout(this.saveVisualCue);
|
||||
this.close();
|
||||
};
|
||||
btns.unshift(
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Export",
|
||||
onclick: () => this.exportAll()
|
||||
})
|
||||
);
|
||||
btns.unshift(
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Import",
|
||||
onclick: () => {
|
||||
this.importInput.click();
|
||||
}
|
||||
})
|
||||
);
|
||||
return btns;
|
||||
}
|
||||
|
||||
async load() {
|
||||
let presets = {};
|
||||
if (app.storageLocation === "server") {
|
||||
try {
|
||||
if (app.isNewUserSession) {
|
||||
// New user so migrate existing presets
|
||||
const json = localStorage.getItem(id);
|
||||
if (json) {
|
||||
presets = JSON.parse(json);
|
||||
api.storeUserData(file, json, { stringify: false });
|
||||
}
|
||||
} else {
|
||||
const res = await api.getUserData(file);
|
||||
if (res.status === 200) {
|
||||
presets = await res.json();
|
||||
} else if (res.status !== 404) {
|
||||
console.error(res.status + " " + res.statusText);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
const json = localStorage.getItem(id);
|
||||
if (json) {
|
||||
presets = JSON.parse(json);
|
||||
}
|
||||
}
|
||||
|
||||
return presets ?? {};
|
||||
}
|
||||
|
||||
async store() {
|
||||
if(app.storageLocation === "server") {
|
||||
try {
|
||||
const presets = JSON.stringify(this.presets, undefined, 4);
|
||||
await api.storeUserData(file, presets, { stringify: false });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem(id, JSON.stringify(this.presets));
|
||||
}
|
||||
}
|
||||
|
||||
async importAll() {
|
||||
for (const file of this.importInput.files) {
|
||||
if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async () => {
|
||||
const importFile = JSON.parse(reader.result);
|
||||
if (importFile?.presets) {
|
||||
for (const nodeType in importFile.presets) {
|
||||
if (this.presets[nodeType]) {
|
||||
this.presets[nodeType] = this.presets[nodeType].concat(importFile.presets[nodeType]);
|
||||
} else {
|
||||
this.presets[nodeType] = importFile.presets[nodeType];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
this.importInput.value = null;
|
||||
this.store();
|
||||
this.close();
|
||||
}
|
||||
|
||||
exportAll() {
|
||||
if (Object.keys(this.presets).length === 0) {
|
||||
alert("No presets to export.");
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.stringify({ presets: this.presets }, null, 2); // convert the data to a JSON string
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = $el("a", {
|
||||
href: url,
|
||||
download: "node_presets.json",
|
||||
style: { display: "none" },
|
||||
parent: document.body,
|
||||
});
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
show(nodeType) {
|
||||
const nodePresets = this.presets[nodeType];
|
||||
super.show(
|
||||
$el(
|
||||
"div",
|
||||
{},
|
||||
nodePresets.flatMap((p, i) => {
|
||||
let nameInput;
|
||||
return [
|
||||
$el(
|
||||
"div",
|
||||
{
|
||||
dataset: { id: i },
|
||||
className: "presetManagerRow",
|
||||
style: {
|
||||
display: "grid",
|
||||
gridPresetColumns: "1fr auto",
|
||||
border: "1px dashed transparent",
|
||||
gap: "5px",
|
||||
backgroundColor: "var(--comfy-menu-bg)"
|
||||
},
|
||||
ondragstart: (e) => {
|
||||
this.draggedEl = e.currentTarget;
|
||||
e.currentTarget.style.opacity = "0.6";
|
||||
e.currentTarget.style.border = "1px dashed yellow";
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setDragImage(this.emptyImg, 0, 0);
|
||||
},
|
||||
ondragend: (e) => {
|
||||
e.target.style.opacity = "1";
|
||||
e.currentTarget.style.border = "1px dashed transparent";
|
||||
e.currentTarget.removeAttribute("draggable");
|
||||
|
||||
// rearrange the elements
|
||||
this.element.querySelectorAll('.presetManagerRow').forEach((el,i) => {
|
||||
var prev_i = el.dataset.id;
|
||||
|
||||
if ( el == this.draggedEl && prev_i != i ) {
|
||||
nodePresets.splice(i, 0, nodePresets.splice(prev_i, 1)[0]);
|
||||
}
|
||||
el.dataset.id = i;
|
||||
});
|
||||
this.store();
|
||||
},
|
||||
ondragover: (e) => {
|
||||
e.preventDefault();
|
||||
if ( e.currentTarget == this.draggedEl )
|
||||
return;
|
||||
|
||||
let rect = e.currentTarget.getBoundingClientRect();
|
||||
if (e.clientY > rect.top + rect.height / 2) {
|
||||
e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget.nextSibling);
|
||||
} else {
|
||||
e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
$el(
|
||||
"label",
|
||||
{
|
||||
textContent: "Name: ",
|
||||
style: {
|
||||
cursor: "grab",
|
||||
},
|
||||
onmousedown: (e) => {
|
||||
// enable dragging only from the label
|
||||
if (e.target.localName == 'label')
|
||||
e.currentTarget.parentNode.draggable = 'true';
|
||||
}
|
||||
},
|
||||
[
|
||||
$el("input", {
|
||||
value: p.name,
|
||||
dataset: { name: p.name },
|
||||
style: {
|
||||
transitionProperty: 'background-color',
|
||||
transitionDuration: '0s',
|
||||
},
|
||||
onchange: (e) => {
|
||||
clearTimeout(this.saveVisualCue);
|
||||
var el = e.target;
|
||||
var row = el.parentNode.parentNode;
|
||||
nodePresets[row.dataset.id].name = el.value.trim() || 'untitled';
|
||||
this.store();
|
||||
el.style.backgroundColor = 'rgb(40, 95, 40)';
|
||||
el.style.transitionDuration = '0s';
|
||||
this.saveVisualCue = setTimeout(function () {
|
||||
el.style.transitionDuration = '.7s';
|
||||
el.style.backgroundColor = 'var(--comfy-input-bg)';
|
||||
}, 15);
|
||||
},
|
||||
onkeypress: (e) => {
|
||||
var el = e.target;
|
||||
clearTimeout(this.saveVisualCue);
|
||||
el.style.transitionDuration = '0s';
|
||||
el.style.backgroundColor = 'var(--comfy-input-bg)';
|
||||
},
|
||||
$: (el) => (nameInput = el),
|
||||
})
|
||||
]
|
||||
),
|
||||
$el(
|
||||
"div",
|
||||
{},
|
||||
[
|
||||
$el("button", {
|
||||
textContent: "Export",
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
fontWeight: "normal",
|
||||
},
|
||||
onclick: (e) => {
|
||||
const presets = {};
|
||||
presets[nodeType] = [p];
|
||||
const json = JSON.stringify({presets: presets}, null, 2); // convert the data to a JSON string
|
||||
const blob = new Blob([json], {type: "application/json"});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = $el("a", {
|
||||
href: url,
|
||||
download: (nameInput.value || p.name) + ".json",
|
||||
style: {display: "none"},
|
||||
parent: document.body,
|
||||
});
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
},
|
||||
}),
|
||||
$el("button", {
|
||||
textContent: "Delete",
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
color: "red",
|
||||
fontWeight: "normal",
|
||||
},
|
||||
onclick: (e) => {
|
||||
const item = e.target.parentNode.parentNode;
|
||||
item.parentNode.removeChild(item);
|
||||
nodePresets.splice(item.dataset.id*1, 1);
|
||||
this.store();
|
||||
// update the rows index, setTimeout ensures that the list is updated
|
||||
var that = this;
|
||||
setTimeout(function (){
|
||||
that.element.querySelectorAll('.presetManagerRow').forEach((el,i) => {
|
||||
el.dataset.id = i;
|
||||
});
|
||||
}, 0);
|
||||
},
|
||||
}),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
];
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: id,
|
||||
setup() {
|
||||
const presetManager = new PresetManager();
|
||||
|
||||
const orig = LGraphCanvas.prototype.getNodeMenuOptions;
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function(node) {
|
||||
const options = orig.call(this, node);
|
||||
|
||||
const loadOptions = presetManager.presets[node.type]?.map((preset) => ({
|
||||
content: preset.name,
|
||||
callback: () => {
|
||||
node.widgets.forEach((widget) => {
|
||||
if (widget.name in preset.values) {
|
||||
widget.value = preset.values[widget.name];
|
||||
}
|
||||
});
|
||||
node.setDirtyCanvas(true, false);
|
||||
}
|
||||
}));
|
||||
|
||||
const saveCallback = () => {
|
||||
const name = prompt("Enter name");
|
||||
if (!name?.trim()) return;
|
||||
|
||||
const presetValues = {};
|
||||
node.widgets.forEach(widget => presetValues[widget.name] = widget.value);
|
||||
|
||||
const preset = {name: name, values: presetValues};
|
||||
if (presetManager.presets[node.type]) {
|
||||
presetManager.presets[node.type].push(preset);
|
||||
} else {
|
||||
presetManager.presets[node.type] = [preset];
|
||||
}
|
||||
presetManager.store();
|
||||
};
|
||||
|
||||
const presetOptions = [{
|
||||
content: `Save`,
|
||||
disabled: !node.widgets.length,
|
||||
callback: saveCallback
|
||||
}, {
|
||||
content: "Load",
|
||||
disabled: !presetManager.presets[node.type],
|
||||
submenu: {options: loadOptions}
|
||||
}, {
|
||||
content: "Manage",
|
||||
callback: () => presetManager.show(node.type)
|
||||
}];
|
||||
|
||||
const presetMenu = {
|
||||
content: "Presets",
|
||||
submenu: {options: presetOptions}
|
||||
};
|
||||
|
||||
options.push(null, presetMenu);
|
||||
return options;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
-387
@@ -1,387 +0,0 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||
|
||||
// Adds the ability to save and load node presets using the node's context menu
|
||||
// To save:
|
||||
// Right click on the node
|
||||
// Presets -> Save -> give it a name
|
||||
//
|
||||
// To load:
|
||||
// Right click on the node
|
||||
// Presets -> click the one to add
|
||||
//
|
||||
// To delete/rename:
|
||||
// Right click the node
|
||||
// Presets -> Manage
|
||||
//
|
||||
// To rearrange:
|
||||
// Open the manage dialog and Drag and drop elements using the "Name:" label as handle
|
||||
|
||||
const id = "Comfy.NodePresets";
|
||||
const file = "comfy.presets.json";
|
||||
|
||||
class PresetManager extends ComfyDialog {
|
||||
constructor() {
|
||||
super();
|
||||
this.load().then((v) => {
|
||||
this.presets = v;
|
||||
});
|
||||
|
||||
this.element.classList.add("comfy-manage-presets");
|
||||
this.draggedEl = null;
|
||||
this.saveVisualCue = null;
|
||||
this.emptyImg = new Image();
|
||||
this.emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
|
||||
|
||||
this.importInput = $el("input", {
|
||||
type: "file",
|
||||
accept: ".json",
|
||||
multiple: true,
|
||||
style: { display: "none" },
|
||||
parent: document.body,
|
||||
onchange: () => this.importAll()
|
||||
});
|
||||
}
|
||||
|
||||
createButtons() {
|
||||
const btns = super.createButtons();
|
||||
btns[0].textContent = "Close";
|
||||
btns[0].onclick = (e) => {
|
||||
clearTimeout(this.saveVisualCue);
|
||||
this.close();
|
||||
};
|
||||
btns.unshift(
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Export",
|
||||
onclick: () => this.exportAll()
|
||||
})
|
||||
);
|
||||
btns.unshift(
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Import",
|
||||
onclick: () => {
|
||||
this.importInput.click();
|
||||
}
|
||||
})
|
||||
);
|
||||
return btns;
|
||||
}
|
||||
|
||||
async load() {
|
||||
let presets = {};
|
||||
if (app.storageLocation === "server") {
|
||||
try {
|
||||
if (app.isNewUserSession) {
|
||||
// New user so migrate existing presets
|
||||
const json = localStorage.getItem(id);
|
||||
if (json) {
|
||||
presets = JSON.parse(json);
|
||||
api.storeUserData(file, json, { stringify: false });
|
||||
}
|
||||
} else {
|
||||
const res = await api.getUserData(file);
|
||||
if (res.status === 200) {
|
||||
presets = await res.json();
|
||||
} else if (res.status !== 404) {
|
||||
console.error(res.status + " " + res.statusText);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
const json = localStorage.getItem(id);
|
||||
if (json) {
|
||||
presets = JSON.parse(json);
|
||||
}
|
||||
}
|
||||
|
||||
return presets ?? {};
|
||||
}
|
||||
|
||||
async store() {
|
||||
if(app.storageLocation === "server") {
|
||||
try {
|
||||
const presets = JSON.stringify(this.presets, undefined, 4);
|
||||
await api.storeUserData(file, presets, { stringify: false });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem(id, JSON.stringify(this.presets));
|
||||
}
|
||||
}
|
||||
|
||||
async importAll() {
|
||||
for (const file of this.importInput.files) {
|
||||
if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async () => {
|
||||
const importFile = JSON.parse(reader.result);
|
||||
if (importFile?.presets) {
|
||||
for (const nodeType in importFile.presets) {
|
||||
if (this.presets[nodeType]) {
|
||||
this.presets[nodeType] = this.presets[nodeType].concat(importFile.presets[nodeType]);
|
||||
} else {
|
||||
this.presets[nodeType] = importFile.presets[nodeType];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
this.importInput.value = null;
|
||||
this.store();
|
||||
this.close();
|
||||
}
|
||||
|
||||
exportAll() {
|
||||
if (Object.keys(this.presets).length === 0) {
|
||||
alert("No presets to export.");
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.stringify({ presets: this.presets }, null, 2); // convert the data to a JSON string
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = $el("a", {
|
||||
href: url,
|
||||
download: "node_presets.json",
|
||||
style: { display: "none" },
|
||||
parent: document.body,
|
||||
});
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
show(nodeType) {
|
||||
const nodePresets = this.presets[nodeType];
|
||||
super.show(
|
||||
$el(
|
||||
"div",
|
||||
{},
|
||||
nodePresets.flatMap((p, i) => {
|
||||
let nameInput;
|
||||
return [
|
||||
$el(
|
||||
"div",
|
||||
{
|
||||
dataset: { id: i },
|
||||
className: "presetManagerRow",
|
||||
style: {
|
||||
display: "grid",
|
||||
gridPresetColumns: "1fr auto",
|
||||
border: "1px dashed transparent",
|
||||
gap: "5px",
|
||||
backgroundColor: "var(--comfy-menu-bg)"
|
||||
},
|
||||
ondragstart: (e) => {
|
||||
this.draggedEl = e.currentTarget;
|
||||
e.currentTarget.style.opacity = "0.6";
|
||||
e.currentTarget.style.border = "1px dashed yellow";
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setDragImage(this.emptyImg, 0, 0);
|
||||
},
|
||||
ondragend: (e) => {
|
||||
e.target.style.opacity = "1";
|
||||
e.currentTarget.style.border = "1px dashed transparent";
|
||||
e.currentTarget.removeAttribute("draggable");
|
||||
|
||||
// rearrange the elements
|
||||
this.element.querySelectorAll('.presetManagerRow').forEach((el,i) => {
|
||||
var prev_i = el.dataset.id;
|
||||
|
||||
if ( el == this.draggedEl && prev_i != i ) {
|
||||
nodePresets.splice(i, 0, nodePresets.splice(prev_i, 1)[0]);
|
||||
}
|
||||
el.dataset.id = i;
|
||||
});
|
||||
this.store();
|
||||
},
|
||||
ondragover: (e) => {
|
||||
e.preventDefault();
|
||||
if ( e.currentTarget == this.draggedEl )
|
||||
return;
|
||||
|
||||
let rect = e.currentTarget.getBoundingClientRect();
|
||||
if (e.clientY > rect.top + rect.height / 2) {
|
||||
e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget.nextSibling);
|
||||
} else {
|
||||
e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
$el(
|
||||
"label",
|
||||
{
|
||||
textContent: "Name: ",
|
||||
style: {
|
||||
cursor: "grab",
|
||||
},
|
||||
onmousedown: (e) => {
|
||||
// enable dragging only from the label
|
||||
if (e.target.localName == 'label')
|
||||
e.currentTarget.parentNode.draggable = 'true';
|
||||
}
|
||||
},
|
||||
[
|
||||
$el("input", {
|
||||
value: p.name,
|
||||
dataset: { name: p.name },
|
||||
style: {
|
||||
transitionProperty: 'background-color',
|
||||
transitionDuration: '0s',
|
||||
},
|
||||
onchange: (e) => {
|
||||
clearTimeout(this.saveVisualCue);
|
||||
var el = e.target;
|
||||
var row = el.parentNode.parentNode;
|
||||
nodePresets[row.dataset.id].name = el.value.trim() || 'untitled';
|
||||
this.store();
|
||||
el.style.backgroundColor = 'rgb(40, 95, 40)';
|
||||
el.style.transitionDuration = '0s';
|
||||
this.saveVisualCue = setTimeout(function () {
|
||||
el.style.transitionDuration = '.7s';
|
||||
el.style.backgroundColor = 'var(--comfy-input-bg)';
|
||||
}, 15);
|
||||
},
|
||||
onkeypress: (e) => {
|
||||
var el = e.target;
|
||||
clearTimeout(this.saveVisualCue);
|
||||
el.style.transitionDuration = '0s';
|
||||
el.style.backgroundColor = 'var(--comfy-input-bg)';
|
||||
},
|
||||
$: (el) => (nameInput = el),
|
||||
})
|
||||
]
|
||||
),
|
||||
$el(
|
||||
"div",
|
||||
{},
|
||||
[
|
||||
$el("button", {
|
||||
textContent: "Export",
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
fontWeight: "normal",
|
||||
},
|
||||
onclick: (e) => {
|
||||
const presets = {};
|
||||
presets[nodeType] = [p];
|
||||
const json = JSON.stringify({presets: presets}, null, 2); // convert the data to a JSON string
|
||||
const blob = new Blob([json], {type: "application/json"});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = $el("a", {
|
||||
href: url,
|
||||
download: (nameInput.value || p.name) + ".json",
|
||||
style: {display: "none"},
|
||||
parent: document.body,
|
||||
});
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
},
|
||||
}),
|
||||
$el("button", {
|
||||
textContent: "Delete",
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
color: "red",
|
||||
fontWeight: "normal",
|
||||
},
|
||||
onclick: (e) => {
|
||||
const item = e.target.parentNode.parentNode;
|
||||
item.parentNode.removeChild(item);
|
||||
nodePresets.splice(item.dataset.id*1, 1);
|
||||
this.store();
|
||||
// update the rows index, setTimeout ensures that the list is updated
|
||||
var that = this;
|
||||
setTimeout(function (){
|
||||
that.element.querySelectorAll('.presetManagerRow').forEach((el,i) => {
|
||||
el.dataset.id = i;
|
||||
});
|
||||
}, 0);
|
||||
},
|
||||
}),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
];
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: id,
|
||||
setup() {
|
||||
const presetManager = new PresetManager();
|
||||
|
||||
const orig = LGraphCanvas.prototype.getNodeMenuOptions;
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function(node) {
|
||||
const options = orig.call(this, node);
|
||||
|
||||
const loadOptions = presetManager.presets[node.type]?.map((preset) => ({
|
||||
content: preset.name,
|
||||
callback: () => {
|
||||
node.widgets.forEach((widget) => {
|
||||
if (widget.name in preset.values) {
|
||||
widget.value = preset.values[widget.name];
|
||||
}
|
||||
});
|
||||
node.setDirtyCanvas(true, false);
|
||||
}
|
||||
}));
|
||||
|
||||
const saveCallback = () => {
|
||||
const name = prompt("Enter name");
|
||||
if (!name?.trim()) return;
|
||||
|
||||
const presetValues = {};
|
||||
node.widgets.forEach(widget => presetValues[widget.name] = widget.value);
|
||||
|
||||
const preset = {name: name, values: presetValues};
|
||||
if (presetManager.presets[node.type]) {
|
||||
presetManager.presets[node.type].push(preset);
|
||||
} else {
|
||||
presetManager.presets[node.type] = [preset];
|
||||
}
|
||||
presetManager.store();
|
||||
};
|
||||
|
||||
const presetOptions = [{
|
||||
content: `Save`,
|
||||
disabled: !node.widgets.length,
|
||||
callback: saveCallback
|
||||
}, {
|
||||
content: "Load",
|
||||
disabled: !presetManager.presets[node.type],
|
||||
submenu: {options: loadOptions}
|
||||
}, {
|
||||
content: "Manage",
|
||||
callback: () => presetManager.show(node.type)
|
||||
}];
|
||||
|
||||
const presetMenu = {
|
||||
content: "Presets",
|
||||
submenu: {options: presetOptions}
|
||||
};
|
||||
|
||||
options.push(null, presetMenu);
|
||||
return options;
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user