diff --git a/web/index.js b/web/index.js index 46d349d..c8ad863 100644 --- a/web/index.js +++ b/web/index.js @@ -7,7 +7,7 @@ import { app } from "../../scripts/app.js"; // Bump on every release. Exposed on window so it's trivial to check in the // console (`window.__cnv_version`) whether the browser is on the latest JS // or still serving a cached older copy. -const CNV_VERSION = "0.0.6"; +const CNV_VERSION = "0.1.0"; try { window.__cnv_version = CNV_VERSION; console.info(`[comfyui-navigator] loaded v${CNV_VERSION}`); @@ -220,6 +220,25 @@ function findMuterOwningGroup(targetTitle) { return null; } +function getMuterWidgetForGroup(title) { + const owner = findMuterOwningGroup(title); + return owner ? owner.widgets.get(title) : null; +} + +function isGroupEnabled(title) { + const w = getMuterWidgetForGroup(title); + return w ? !!w?.value?.toggled : null; // null = no muter manages it +} + +function setGroupEnabled(title, on) { + const w = getMuterWidgetForGroup(title); + if (!w) return false; + setMuterWidget(w, on); + const owner = findMuterOwningGroup(title); + owner?.muter?.setDirtyCanvas?.(true, true); + return true; +} + function setMuterWidget(widget, toggled) { // Value is { toggled: bool }. Mutate in place AND call callback so rgthree // re-renders + dispatches its own change handlers. @@ -438,25 +457,77 @@ function renderList() { const row = document.createElement("div"); row.className = "cnv-row"; row.style.setProperty("--cnv-color", groupColorOrDefault(g)); + row.dataset.title = title; + + const enabled = isGroupEnabled(title); + const showCheckbox = enabled !== null; // null = no muter manages this group + row.innerHTML = ` + ${showCheckbox ? `` : ``} ${i < 9 ? `${i + 1}` : ""} + + `; row.querySelector(".cnv-row__title").textContent = title; + + // Checkbox: toggle muter widget (don't jump) + const cb = row.querySelector("input.cnv-row__cb"); + if (cb) { + cb.addEventListener("click", (e) => e.stopPropagation()); + cb.addEventListener("change", (e) => { + setGroupEnabled(title, cb.checked); + }); + } + + // Click the row body (anywhere except checkbox/jump/menu) → jump row.addEventListener("click", (e) => { if (e.button !== 0) return; + if (e.target.closest(".cnv-row__cb, .cnv-row__menu")) return; jumpToGroup(g); }); + + // Explicit jump button (visible affordance) + row.querySelector(".cnv-row__jump").addEventListener("click", (e) => { + e.stopPropagation(); + jumpToGroup(g); + }); + + // Three-dot menu + row.querySelector(".cnv-row__menu").addEventListener("click", (e) => { + e.stopPropagation(); + e.preventDefault(); + openRowMenu(g, e); + }); + + // Right-click anywhere on row also opens menu row.addEventListener("contextmenu", (e) => { e.preventDefault(); e.stopPropagation(); openRowMenu(g, e); }); + state.listEl.appendChild(row); } } +/** Cheap re-sync of just the checkbox states without rebuilding the list. + * Catches the case where the user toggles a widget directly on the muter + * node (or via rgthree's keyboard shortcuts) and we need to reflect it in + * the panel. */ +function syncCheckboxStates() { + if (!state.listEl) return; + for (const row of state.listEl.querySelectorAll(".cnv-row")) { + const title = row.dataset.title; + const cb = row.querySelector("input.cnv-row__cb"); + if (!cb) continue; + const enabled = isGroupEnabled(title); + if (enabled === null) continue; + if (cb.checked !== enabled) cb.checked = enabled; + } +} + // --- keyboard hotkeys --------------------------------------------------- function isTypingTarget(el) { @@ -509,15 +580,18 @@ app.registerExtension({ // Catch graph swaps (loading a different workflow). ComfyUI fires // a custom "graphConfigured" or similar; polling _groups length as - // a cheap fallback. + // a cheap fallback. Also re-sync checkbox states so that toggles + // made directly on the muter node propagate to the panel UI. let lastGroupCount = -1; setInterval(() => { const n = (app.graph?._groups?.length || 0); if (n !== lastGroupCount) { lastGroupCount = n; scheduleRender(); + } else { + syncCheckboxStates(); } - }, 800); + }, 600); }, async loadedGraphNode() { scheduleRender(); diff --git a/web/styles.css b/web/styles.css index 6e90c52..c2e1f10 100644 --- a/web/styles.css +++ b/web/styles.css @@ -85,8 +85,8 @@ .cnv-row { display: flex; align-items: center; - gap: 8px; - padding: 6px 8px; + gap: 6px; + padding: 4px 6px; border-radius: 6px; cursor: pointer; border-left: 3px solid var(--cnv-color, #3b82f6); @@ -94,9 +94,26 @@ .cnv-row:hover { background: rgba(255, 255, 255, 0.06); } .cnv-row--active { background: rgba(96, 165, 250, 0.14); } +.cnv-row__cb { + width: 14px; + height: 14px; + margin: 0; + cursor: pointer; + accent-color: var(--cnv-color, #3b82f6); + flex-shrink: 0; +} +.cnv-row__cb--placeholder { + display: inline-block; + width: 14px; + height: 14px; + border-radius: 3px; + border: 1px dashed rgba(255, 255, 255, 0.15); + background: transparent; +} + .cnv-row__dot { - width: 8px; - height: 8px; + width: 7px; + height: 7px; border-radius: 50%; background: var(--cnv-color, #3b82f6); flex-shrink: 0; @@ -118,5 +135,22 @@ font-family: ui-monospace, monospace; } +.cnv-row__jump, +.cnv-row__menu { + background: transparent; + border: none; + color: #94a3b8; + cursor: pointer; + padding: 1px 4px; + border-radius: 4px; + font-size: 13px; + line-height: 1; +} +.cnv-row__jump:hover, +.cnv-row__menu:hover { + background: rgba(255, 255, 255, 0.1); + color: #e5e7eb; +} + .cnv-panel--collapsed .cnv-panel__search-wrap, .cnv-panel--collapsed .cnv-panel__list { display: none; }