Files
ComfyUI-Dataset-Gates/web/pool_profile.js
T

231 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
// Pool Profile — companion to the Image Pool. A dropdown of named profiles
// (registry under input/grid_pool/profiles.json) plus create/rename/delete/
// duplicate/export/import actions. Selecting a profile propagates its id into
// any connected Image Pool node's pool_id widget and refreshes that grid, so the
// pool's images switch live at edit time. (Modeled on JSON-Manager/project_key.)
const NODE = "PoolProfile";
const POOL_NODE = "GridImagePool";
const R = "/grid_pool/profiles";
// ---- server calls -----------------------------------------------------------
async function listProfiles() {
const r = await api.fetchApi(`${R}/list`);
return (await r.json()).profiles || [];
}
async function postJson(path, body) {
const r = await api.fetchApi(`${R}/${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!r.ok) throw new Error(await r.text());
return await r.json();
}
// ---- widget helpers ---------------------------------------------------------
function hideWidget(w) {
if (!w) return;
if (w.origType === undefined) w.origType = w.type;
w.type = "hidden";
w.hidden = true;
w.computeSize = () => [0, -4];
}
function profileWidget(node) {
return node.widgets?.find((w) => w.name === "profile");
}
function idWidget(node) {
return node.widgets?.find((w) => w.name === "profile_id");
}
function currentEntry(node) {
const combo = profileWidget(node);
return (node._profiles || []).find((p) => p.name === combo?.value);
}
// Replace a STRING widget with a real combo, preserving its serialized value.
function replaceWithCombo(node, name, values, callback) {
const idx = node.widgets?.findIndex((w) => w.name === name);
if (idx === undefined || idx === -1) return null;
const old = node.widgets[idx];
const saved = old.value || "";
const vals = values.length ? values.slice() : [""];
if (saved && !vals.includes(saved)) vals.unshift(saved);
node.widgets.splice(idx, 1);
const combo = node.addWidget("combo", name, saved || vals[0], callback, { values: vals });
// move from the end back to the original slot
node.widgets.splice(node.widgets.length - 1, 1);
node.widgets.splice(idx, 0, combo);
return combo;
}
// ---- propagation ------------------------------------------------------------
// Push the selected profile id into every connected Image Pool node's pool_id
// widget (the grid keys off getPoolId), then refresh that grid.
function propagate(node) {
const id = idWidget(node)?.value || "default";
const out = node.outputs?.[0];
if (!out?.links) return;
for (const linkId of out.links) {
const link = node.graph?.links?.[linkId];
if (!link) continue;
const target = node.graph?.getNodeById?.(link.target_id);
if (!target || target.type !== POOL_NODE) continue;
const pw = target.widgets?.find((w) => w.name === "pool_id");
if (pw) pw.value = id;
target._datasetePoolRefresh?.();
target.setDirtyCanvas?.(true, true);
}
}
function applySelection(node) {
const entry = currentEntry(node);
const idw = idWidget(node);
if (idw) idw.value = entry?.id || "";
propagate(node);
node.setDirtyCanvas?.(true, true);
}
async function refreshList(node, selectName) {
const profs = await listProfiles();
node._profiles = profs;
const names = profs.map((p) => p.name);
const combo = profileWidget(node);
if (combo) {
combo.options = combo.options || {};
combo.options.values = names.length ? names : [""];
if (selectName !== undefined) combo.value = selectName;
else if (!names.includes(combo.value)) combo.value = names[0] || "";
applySelection(node);
}
}
// ---- actions ----------------------------------------------------------------
async function actionCreate(node) {
const name = prompt("New profile name:");
if (!name) return;
try {
const e = await postJson("create", { name });
await refreshList(node, e.name);
} catch (err) { alert("Create failed: " + err); }
}
async function actionRename(node) {
const e = currentEntry(node);
if (!e) return alert("Select a profile first");
const name = prompt("Rename profile:", e.name);
if (!name || name === e.name) return;
try {
await postJson("rename", { id: e.id, name });
await refreshList(node, name);
} catch (err) { alert("Rename failed: " + err); }
}
async function actionDuplicate(node) {
const e = currentEntry(node);
if (!e) return alert("Select a profile first");
const name = prompt("Duplicate as:", e.name + " copy");
if (!name) return;
try {
const ne = await postJson("duplicate", { id: e.id, name });
await refreshList(node, ne.name);
} catch (err) { alert("Duplicate failed: " + err); }
}
async function actionDelete(node) {
const e = currentEntry(node);
if (!e) return alert("Select a profile first");
if (!confirm(`Delete profile "${e.name}"? This removes its images.`)) return;
try {
await postJson("delete", { id: e.id });
await refreshList(node);
} catch (err) { alert("Delete failed: " + err); }
}
function actionExport(node) {
const e = currentEntry(node);
if (!e) return alert("Select a profile first");
window.open(`${R}/export?id=${encodeURIComponent(e.id)}`);
}
function actionImport(node) {
const input = document.createElement("input");
input.type = "file";
input.accept = ".zip";
input.onchange = async () => {
if (!input.files?.length) return;
const fd = new FormData();
fd.append("file", input.files[0], input.files[0].name);
try {
const r = await api.fetchApi(`${R}/import`, { method: "POST", body: fd });
if (!r.ok) throw new Error(await r.text());
const e = await r.json();
await refreshList(node, e.name);
} catch (err) { alert("Import failed: " + err); }
};
input.click();
}
// ---- node setup -------------------------------------------------------------
function setupProfileNode(node) {
hideWidget(idWidget(node));
replaceWithCombo(node, "profile", [], () => applySelection(node));
node.addWidget("button", " Create", null, () => actionCreate(node));
node.addWidget("button", "✎ Rename", null, () => actionRename(node));
node.addWidget("button", "⧉ Duplicate", null, () => actionDuplicate(node));
node.addWidget("button", "🗑 Delete", null, () => actionDelete(node));
node.addWidget("button", "⬇ Export", null, () => actionExport(node));
node.addWidget("button", "⬆ Import", null, () => actionImport(node));
node.setSize(node.computeSize());
refreshList(node); // async: populate the dropdown
}
app.registerExtension({
name: "datasete.gates.poolprofile",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name !== NODE) return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated?.apply(this, arguments);
setupProfileNode(this);
return r;
};
// loaded workflows restore the combo + profile_id after create — re-list and
// re-propagate the saved id once the graph is ready.
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = onConfigure?.apply(this, arguments);
const node = this;
queueMicrotask(() => {
propagate(node); // propagate saved id immediately
refreshList(node, profileWidget(node)?.value);
});
return r;
};
// when our output gets connected to a pool, propagate right away
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function () {
const r = onConnectionsChange?.apply(this, arguments);
propagate(this);
return r;
};
},
});