diff --git a/README.md b/README.md index a40463a..f7c7b53 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A lightweight, high-performance ComfyUI extension that replaces the cluttered st * A compact topbar layout displaying both workflow tabs and the control panel on a single unified top bar. See the 'Comfy Layout' toggle in the settings. * Toggles to hide buttons you don't need from the side panel. * **[NEW]** Preview compare slider - just click the result image preview in the sidebar, and then `Shift+Click` another to load it alongside the first one for pixel by pixel comparison. -* **[NEW]** Prevent certain nodes from showing the output in the queue bar and intermediate results. `Ctrl+Q` to toggle. +* **[NEW]** Prevent selected nodes from showing the output in the queue bar and intermediate results. `Ctrl+Q` to toggle. image @@ -33,8 +33,10 @@ Restart your ComfyUI server and refresh the browser tab. ## ⚙️ Configuration -Additional tweaks are seamlessly integrated into the standard ComfyUI Settings panel. From there, you can adjust thresholds or fully replace the stock V2 panel by enabling options like **"Override Stock Job History Tab"**. +See additional tweaks in ComfyUI settings menu > Comfy Sidebar. From there, you can adjust the layout or fully replace the stock V2 panel by enabling options like **"Comfy Layout"** and **"Replace the stock Job History sidebar with Comfy Queue"**. ## ⌨️ Shortcuts * **`Q`**: Instantly toggles the sidebar panel open or closed. Safely ignores inputs if you are currently typing inside a text field, input area, or combo box. +* **`Ctrl+Q on a selected node`**: Sets the node to be ignored in the sidebar panel results. +* **`Click`** on a card to enter preview. **`Shift+Click`** on another card in preview to enter compare mode. \ No newline at end of file diff --git a/js/dragdrop.js b/js/dragdrop.js index f8346a6..f2bbb06 100644 --- a/js/dragdrop.js +++ b/js/dragdrop.js @@ -1,4 +1,5 @@ import { app } from "/scripts/app.js"; +import { State } from "./state.js"; async function uploadDroppedImageToInput(imageObj) { const src = `/view?filename=${encodeURIComponent(imageObj.filename)}&type=${imageObj.type || 'output'}&subfolder=${encodeURIComponent(imageObj.subfolder || '')}`; @@ -26,6 +27,13 @@ export function setupDragAndDrop() { }); document.addEventListener("drop", async (e) => { + // Cancel loading entirely if dropping inside the sidebar area + const isSidebarDrop = e.target.closest('.comfyui-sidebar, .comfy-sidebar, [class*="sidebar"]') || + (State.sidebarContainer && State.sidebarContainer.contains(e.target)); + if (isSidebarDrop) { + return; + } + const canvas = app.canvas; if (!canvas || !canvas.graph) return; diff --git a/js/queue.js b/js/queue.js index b4d61eb..e72e75c 100644 --- a/js/queue.js +++ b/js/queue.js @@ -1,6 +1,6 @@ import { app } from "/scripts/app.js"; import { api } from "/scripts/api.js"; -import { State, promptStates, pruneHistory, loadStatesFromLocalStorage } from "./state.js"; +import { State, promptStates, pruneHistory, loadStatesFromLocalStorage, cardElements } from "./state.js"; import { findImagesInOutputs, findTextsInOutputs } from "./utils.js"; export let renderDOMFn = () => {}; @@ -203,11 +203,14 @@ export async function initSessionAndHistory() { } const storedSessionId = localStorage.getItem("comfy_sidebar_backend_session_id"); - if (backendSessionId && backendSessionId === storedSessionId) { - loadStatesFromLocalStorage(); - } else { + if (backendSessionId && backendSessionId !== storedSessionId) { + // Session changed (server restarted)! Clear cached memory. + promptStates.clear(); + cardElements.clear(); localStorage.removeItem("comfy_sidebar_prompt_states"); if (backendSessionId) localStorage.setItem("comfy_sidebar_backend_session_id", backendSessionId); + } else if (!storedSessionId && backendSessionId) { + localStorage.setItem("comfy_sidebar_backend_session_id", backendSessionId); } const historyData = await api.getHistory(); diff --git a/js/state.js b/js/state.js index fd635ec..f872925 100644 --- a/js/state.js +++ b/js/state.js @@ -66,4 +66,7 @@ export function loadStatesFromLocalStorage() { } catch (e) { console.error("Comfy Sidebar: Failed to load state from localStorage", e); } -} \ No newline at end of file +} + +// Load instantly and synchronously on script import to prevent race conditions during early renders! +loadStatesFromLocalStorage(); \ No newline at end of file diff --git a/js/ui.js b/js/ui.js index c5d9dc4..216163d 100644 --- a/js/ui.js +++ b/js/ui.js @@ -367,26 +367,32 @@ function renderCardImages(cardObj, state, keepAspect) { mediaEl.onmouseenter = () => { playIcon.style.opacity = "0"; mediaEl.play().catch(()=>{}); }; mediaEl.onmouseleave = () => { playIcon.style.opacity = "1"; mediaEl.pause(); }; } else { - mediaEl.setAttribute("draggable", "true"); - mediaEl.onload = () => { - applyDimensions(mediaEl.naturalWidth, mediaEl.naturalHeight); - }; + const isUnfinished = state.status && state.status !== "completed"; + if (isUnfinished) { + mediaEl.setAttribute("draggable", "false"); + mediaEl.style.cursor = "grab"; + mediaEl.removeEventListener("dragstart", mediaEl._currentDragStart); + delete mediaEl._currentDragStart; + } else { + mediaEl.setAttribute("draggable", "true"); + mediaEl.style.cursor = "zoom-in"; + + const dragStartHandler = (e) => { + State.currentDraggedImgData = { ...img, workflow: state.workflow }; + try { + e.dataTransfer.setData("text/uri-list", src); + e.dataTransfer.setData("text/plain", src); + } catch (err) {} + e.dataTransfer.effectAllowed = "copy"; + e.stopPropagation(); + }; + mediaEl.removeEventListener("dragstart", mediaEl._currentDragStart); + mediaEl.addEventListener("dragstart", dragStartHandler); + mediaEl._currentDragStart = dragStartHandler; + } const playIcon = wrapper.querySelector(".comfy-sidebar-play-icon"); if (playIcon) playIcon.remove(); - - const dragStartHandler = (e) => { - State.currentDraggedImgData = { ...img, workflow: state.workflow }; - try { - e.dataTransfer.setData("text/uri-list", src); - e.dataTransfer.setData("text/plain", src); - } catch (err) {} - e.dataTransfer.effectAllowed = "copy"; - e.stopPropagation(); - }; - mediaEl.removeEventListener("dragstart", mediaEl._currentDragStart); - mediaEl.addEventListener("dragstart", dragStartHandler); - mediaEl._currentDragStart = dragStartHandler; } // Smooth in-memory source swapping for blob live previews @@ -924,9 +930,10 @@ export function renderDOM() { }); card.addEventListener("dragstart", (e) => { - // Custom dragstart is only used for JSON-only workflows to supply a local DownloadURL. - // For image cards, dragging is handled natively by the browser on the child tag. - if (state.workflow && (!state.images || state.images.length === 0)) { + // Custom dragstart is used for JSON-only or unfinished workflows to supply a local DownloadURL. + // For completed image cards, dragging is handled natively by the browser on the child tag. + const isUnfinished = state.status && state.status !== "completed"; + if (state.workflow && (!state.images || state.images.length === 0 || isUnfinished)) { if (cardObj.firstImgElement) e.dataTransfer.setDragImage(cardObj.firstImgElement, 15, 15); const jsonStr = JSON.stringify(state.workflow, null, 2); try { @@ -943,9 +950,10 @@ export function renderDOM() { cardObj.element.className = `comfy-sidebar-card ${state.status}`; - // Native drag isolation: completely remove draggable flags on image containers. + // Native drag isolation: completely remove draggable flags on image containers for finished cards. // This exposes the child tag directly to Chrome's native filesystem drag APIs. - if (state.images && state.images.length > 0) { + const isUnfinished = state.status && state.status !== "completed"; + if (state.images && state.images.length > 0 && !isUnfinished) { cardObj.element.removeAttribute("draggable"); } else { cardObj.element.setAttribute("draggable", "true");