From 0dfa14384d4354104f3e95951b7e7ad2e77e8eda Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sun, 21 Jun 2026 14:27:14 +0200 Subject: [PATCH] feat(search): mirror search palette UI + enable actions --- js/nodes_stats.js | 102 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/js/nodes_stats.js b/js/nodes_stats.js index 9f34d6c..108660f 100644 --- a/js/nodes_stats.js +++ b/js/nodes_stats.js @@ -824,6 +824,108 @@ async function handleEnable(pkg, temporary, dialog) { } } +// --------------------------------------------------------------------------- +// Mirror search: a standalone palette over nodes of currently-disabled packs +// --------------------------------------------------------------------------- + +async function openMirrorSearch() { + const existing = document.getElementById("nodes-stats-mirror"); + if (existing) { existing.querySelector("#ns-mirror-input")?.focus(); return; } + + const overlay = document.createElement("div"); + overlay.id = "nodes-stats-mirror"; + overlay.style.cssText = + "position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10001;display:flex;align-items:flex-start;justify-content:center;"; + overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); }); + overlay.addEventListener("keydown", (e) => { if (e.key === "Escape") overlay.remove(); }); + + const box = document.createElement("div"); + box.style.cssText = + "margin-top:10vh;background:#1e1e1e;color:#ddd;border:1px solid #444;border-radius:8px;width:90%;max-width:640px;max-height:70vh;display:flex;flex-direction:column;font-family:monospace;font-size:13px;overflow:hidden;"; + box.innerHTML = ` + +
+ + +
+
+ `; + overlay.appendChild(box); + document.body.appendChild(overlay); + + const input = box.querySelector("#ns-mirror-input"); + const results = box.querySelector("#ns-mirror-results"); + const footer = box.querySelector("#ns-mirror-footer"); + + footer.textContent = "loading disabled-node catalog…"; + let catalog = await ensureDisabledCatalog(); + if (catalog === null) { footer.textContent = "ComfyUI Manager not available."; return; } + if (catalog.length === 0) { footer.textContent = "No disabled packages — nothing to search."; return; } + const packCount = new Set(catalog.map((e) => e.pack)).size; + footer.textContent = `${catalog.length} nodes across ${packCount} disabled packs · enabling needs a restart`; + + function render() { + const { rows, total } = filterCatalog(catalog, input.value); + if (!input.value.trim()) { + results.innerHTML = `
Type to search ${catalog.length} nodes in ${packCount} disabled packs.
`; + return; + } + if (total === 0) { results.innerHTML = `
No disabled nodes match “${escapeHtml(input.value)}”.
`; return; } + let html = ""; + for (const e of rows) { + html += `
+
+
${escapeHtml(e.class_type)}
+
${escapeHtml(e.pack)}
+
+ + +
`; + } + if (total > rows.length) html += `
+${total - rows.length} more — refine your search.
`; + results.innerHTML = html; + results.querySelectorAll(".ns-mirror-temp").forEach((b) => + b.addEventListener("click", () => mirrorEnable(b.dataset.pkg, true, overlay))); + results.querySelectorAll(".ns-mirror-perm").forEach((b) => + b.addEventListener("click", () => mirrorEnable(b.dataset.pkg, false, overlay))); + } + + input.addEventListener("input", render); + box.querySelector("#ns-mirror-refresh").addEventListener("click", async () => { + footer.textContent = "refreshing…"; + catalog = await ensureDisabledCatalog(true) || []; + footer.textContent = `${catalog.length} nodes across ${new Set(catalog.map((e)=>e.pack)).size} disabled packs · enabling needs a restart`; + render(); + }); + render(); + input.focus(); +} + +// Enable from the palette. Marks all rows for the pack as enabled on success. +async function mirrorEnable(pkg, temporary, overlay) { + const entry = (_disabledCatalog || []).find((e) => e.pack === pkg); + const info = entry && entry.info; + if (!info) return; + overlay.querySelectorAll(".ns-btn").forEach((b) => (b.disabled = true)); + try { + if (await enablePackage(pkg, info, temporary)) { + (_disabledCatalog || []).forEach((e) => { if (e.pack === pkg) e.info.state = "enabled"; }); + overlay.querySelectorAll(`.ns-mirror-temp[data-pkg="${cssEscape(pkg)}"], .ns-mirror-perm[data-pkg="${cssEscape(pkg)}"]`) + .forEach((b) => { b.replaceWith(Object.assign(document.createElement("span"), { textContent: "✓ enabled · restart", style: "color:#6a6;font-size:11px;" })); }); + } + } catch (e) { + notify("Failed to enable: " + e.message, "error"); + } finally { + overlay.querySelectorAll(".ns-btn").forEach((b) => (b.disabled = false)); + } +} + // Missing packages are deferred to ComfyUI Manager — the design treats "Missing" // as handled by Manager like always, and Manager already surfaces missing nodes // on workflow load. We intentionally do NOT replicate install: a not-installed