From d85c54232a97c02dbd32fb44b4660a012765db98 Mon Sep 17 00:00:00 2001 From: M0rtus59 Date: Sun, 19 Jul 2026 00:20:58 +0500 Subject: [PATCH] Fixed missing batch arrows --- js/ui.js | 343 +++++++++++++++++++++++++++---------------------------- 1 file changed, 171 insertions(+), 172 deletions(-) diff --git a/js/ui.js b/js/ui.js index 7ee6209..7dd74fd 100644 --- a/js/ui.js +++ b/js/ui.js @@ -253,198 +253,196 @@ function renderCardImages(cardObj, state, keepAspect) { if (!img) { cardObj.grid.innerHTML = ""; cardObj.firstImgElement = null; + if (cardObj.dimEl) cardObj.dimEl.style.display = "none"; return; } const src = img.url ? img.url : window.location.origin + `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}`; const isVideo = isVideoFormat(src); - const currentMediaEl = cardObj.grid.querySelector("img, video"); - const matchesTagType = currentMediaEl && (isVideo === (currentMediaEl.tagName.toLowerCase() === "video")); - - if (matchesTagType) { - // --- IN-PLACE REUSE PATH --- - const mediaEl = currentMediaEl; - - // 1. Update interactive event listeners on reuse to bind correct closure variables - mediaEl.onclick = (ev) => { - ev.stopPropagation(); - showFullscreenPreview([src]); - }; - - if (!isVideo) { - // Re-bind native drag handlers with updated state values - const newDragStart = (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", newDragStart); - mediaEl._currentDragStart = newDragStart; - } - - // 2. Perform smooth in-memory source swapping for blob live previews - const currentSrc = mediaEl.getAttribute("src") || mediaEl.src; - if (currentSrc !== src && currentSrc !== (currentSrc + "#t=0.001")) { - if (src.startsWith("blob:") && !isVideo) { - // Smooth preload trick: hold current preview until the new one is fully loaded - const tempImg = new Image(); - tempImg.onload = () => { - const oldBlob = mediaEl._lastBlob; - mediaEl.src = src; - mediaEl._lastBlob = src; - - if (keepAspect && tempImg.naturalWidth && tempImg.naturalHeight) { - mediaEl.style.aspectRatio = `${tempImg.naturalWidth} / ${tempImg.naturalHeight}`; - } - if (cardObj.dimEl && tempImg.naturalWidth && tempImg.naturalHeight) { - cardObj.dimEl.textContent = `${tempImg.naturalWidth}x${tempImg.naturalHeight}`; - cardObj.dimEl.style.display = "block"; - } - - if (oldBlob && oldBlob !== src) { - try { URL.revokeObjectURL(oldBlob); } catch(e){} - } - }; - tempImg.src = src; - } else { - // Direct fast swap for standard server files - mediaEl.src = isVideo ? src + "#t=0.001" : src; - } - } - - // 3. Update the pagination label if it exists - const label = cardObj.grid.querySelector(".comfy-sidebar-batch-label"); - if (label) { - label.textContent = `${idx + 1}/${state.images.length}`; - } - return; + // Find or create the native wrapper + let wrapper = cardObj.grid.querySelector(".comfy-sidebar-media-wrapper"); + if (!wrapper) { + cardObj.grid.innerHTML = ""; + wrapper = document.createElement("div"); + wrapper.className = "comfy-sidebar-media-wrapper"; + Object.assign(wrapper.style, { position: "relative", width: "100%", display: "block" }); + cardObj.grid.appendChild(wrapper); } - // --- REBUILD PATH (ONLY RUNS ON TYPE CHANGE OR FIRST RENDER) --- - cardObj.grid.innerHTML = ""; - cardObj.firstImgElement = null; + // Find or create the media tag in-place + let mediaEl = wrapper.querySelector("img, video"); + const needsRebuild = !mediaEl || (isVideo !== (mediaEl.tagName.toLowerCase() === "video")); - const wrapper = document.createElement("div"); - Object.assign(wrapper.style, { position: "relative", width: "100%", display: "block" }); + if (needsRebuild) { + if (mediaEl) mediaEl.remove(); + mediaEl = isVideo ? document.createElement("video") : document.createElement("img"); + Object.assign(mediaEl.style, { + width: "100%", + borderRadius: "2px", + display: "block", + cursor: "zoom-in", + webkitUserDrag: "element", + zIndex: "1" + }); + wrapper.insertBefore(mediaEl, wrapper.firstChild); + } - const thumb = isVideo ? document.createElement("video") : document.createElement("img"); - - // Explicit low stacking index combined with standard native webkit dragging force rules - Object.assign(thumb.style, { - width: "100%", - borderRadius: "2px", - display: "block", - cursor: "zoom-in", - webkitUserDrag: "element", - zIndex: "1" - }); + cardObj.firstImgElement = mediaEl; + + // Binds interactive event listeners + mediaEl.onclick = (ev) => { + ev.stopPropagation(); + showFullscreenPreview(state.images.map(i => i.url ? i.url : window.location.origin + `/view?filename=${encodeURIComponent(i.filename)}&type=${encodeURIComponent(i.type || 'output')}&subfolder=${encodeURIComponent(i.subfolder || '')}`)); + }; + + // Shared dimensions layout compiler + const applyDimensions = (width, height) => { + if (keepAspect && width && height) { + mediaEl.style.aspectRatio = `${width} / ${height}`; + } + if (cardObj.dimEl && width && height) { + cardObj.dimEl.textContent = `${width}x${height}`; + cardObj.dimEl.style.display = "block"; + } + }; if (isVideo) { - thumb.muted = true; - thumb.playsInline = true; - thumb.preload = "metadata"; - thumb.loop = true; + mediaEl.muted = true; + mediaEl.playsInline = true; + mediaEl.preload = "metadata"; + mediaEl.loop = true; + mediaEl.setAttribute("draggable", "false"); + + mediaEl.onloadedmetadata = () => { + applyDimensions(mediaEl.videoWidth, mediaEl.videoHeight); + }; - const playIcon = document.createElement("div"); - Object.assign(playIcon.style, { - position: "absolute", top: "0", left: "0", width: "100%", height: "100%", - pointerEvents: "none", zIndex: "2", transition: "opacity 0.2s ease" - }); - - playIcon.innerHTML = ` - - - - - `; - wrapper.appendChild(playIcon); + let playIcon = wrapper.querySelector(".comfy-sidebar-play-icon"); + if (!playIcon) { + playIcon = document.createElement("div"); + playIcon.className = "comfy-sidebar-play-icon"; + Object.assign(playIcon.style, { + position: "absolute", top: "0", left: "0", width: "100%", height: "100%", + pointerEvents: "none", zIndex: "2", transition: "opacity 0.2s ease" + }); + playIcon.innerHTML = ` + + + + + `; + wrapper.appendChild(playIcon); + } - thumb.onmouseenter = () => { playIcon.style.opacity = "0"; thumb.play().catch(()=>{}); }; - thumb.onmouseleave = () => { playIcon.style.opacity = "1"; thumb.pause(); }; - } - - if (!keepAspect) { thumb.style.aspectRatio = "1 / 1"; thumb.style.objectFit = "cover"; } - - // Keep native image tags naturally draggable - if (!isVideo) { - thumb.setAttribute("draggable", "true"); + mediaEl.onmouseenter = () => { playIcon.style.opacity = "0"; mediaEl.play().catch(()=>{}); }; + mediaEl.onmouseleave = () => { playIcon.style.opacity = "1"; mediaEl.pause(); }; } else { - thumb.setAttribute("draggable", "false"); - } - - if (!isVideo) { - thumb.onload = () => { if (keepAspect && thumb.naturalWidth && thumb.naturalHeight) thumb.style.aspectRatio = `${thumb.naturalWidth} / ${thumb.naturalHeight}`; }; - if (src.startsWith("blob:")) { const tempImg = new Image(); tempImg.onload = () => { thumb.src = src; thumb._lastBlob = src; }; tempImg.src = src; } else thumb.src = src; - } else { - thumb.onloadedmetadata = () => { if (keepAspect && thumb.videoWidth && thumb.videoHeight) thumb.style.aspectRatio = `${thumb.videoWidth} / ${thumb.videoHeight}`; }; - thumb.src = src + "#t=0.001"; - } - - thumb.onclick = (ev) => { - ev.stopPropagation(); - showFullscreenPreview([src]); - }; - - wrapper.insertBefore(thumb, wrapper.firstChild); - cardObj.firstImgElement = thumb; - - // Overlay slick pagination controller bar if batch has multiple output images - if (state.images.length > 1) { - const navBar = document.createElement("div"); - Object.assign(navBar.style, { - position: "absolute", bottom: "6px", left: "50%", transform: "translateX(-50%)", - display: "flex", alignItems: "center", gap: "8px", background: "rgba(0,0,0,0.75)", - padding: "4px 10px", borderRadius: "12px", zIndex: "15", fontSize: "10px", - fontFamily: "monospace", color: "#eee", userSelect: "none", pointerEvents: "auto", - boxShadow: "0 1px 3px rgba(0,0,0,0.4)", transform: "translate3d(-50%, 0, 0)" // Forces a GPU compositor stacking context to prevent video clipping - }); - - const prevBtn = document.createElement("span"); - prevBtn.className = "pi pi-chevron-left"; - prevBtn.style.cursor = "pointer"; - prevBtn.onclick = (ev) => { - ev.stopPropagation(); - cardObj.currentImageIndex = (cardObj.currentImageIndex - 1 + state.images.length) % state.images.length; - renderCardImages(cardObj, state, keepAspect); + mediaEl.setAttribute("draggable", "true"); + mediaEl.onload = () => { + applyDimensions(mediaEl.naturalWidth, mediaEl.naturalHeight); }; - const label = document.createElement("span"); - label.className = "comfy-sidebar-batch-label"; - label.textContent = `${idx + 1}/${state.images.length}`; - label.style.cursor = "pointer"; - label.title = "View all images in this batch"; - - // Open the dynamic batch explorer explorer view when the middle counter label is clicked - label.onclick = (ev) => { - ev.stopPropagation(); - State.activeSubmenuBatchImages = { - pid: state.pid || cardObj.element.id.replace("card-", ""), - images: state.images, - workflow: state.workflow + 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 + const currentSrc = mediaEl.getAttribute("src") || mediaEl.src; + if (currentSrc !== src && currentSrc !== (src + "#t=0.001")) { + if (src.startsWith("blob:") && !isVideo) { + const tempImg = new Image(); + tempImg.onload = () => { + const oldBlob = mediaEl._lastBlob; + mediaEl.src = src; + mediaEl._lastBlob = src; + applyDimensions(tempImg.naturalWidth, tempImg.naturalHeight); + if (oldBlob && oldBlob !== src) { + try { URL.revokeObjectURL(oldBlob); } catch(e){} + } }; - renderDOM(); - }; - - const nextBtn = document.createElement("span"); - nextBtn.className = "pi pi-chevron-right"; - nextBtn.style.cursor = "pointer"; - nextBtn.onclick = (ev) => { - ev.stopPropagation(); - cardObj.currentImageIndex = (cardObj.currentImageIndex + 1) % state.images.length; - renderCardImages(cardObj, state, keepAspect); - }; - - navBar.append(prevBtn, label, nextBtn); - wrapper.appendChild(navBar); + tempImg.src = src; + } else { + mediaEl.src = isVideo ? src + "#t=0.001" : src; + } + } else { + // If the source is already correct, make sure we still force dimensions calculations + if (isVideo) { + if (mediaEl.videoWidth) applyDimensions(mediaEl.videoWidth, mediaEl.videoHeight); + } else { + if (mediaEl.naturalWidth) applyDimensions(mediaEl.naturalWidth, mediaEl.naturalHeight); + } } - cardObj.grid.appendChild(wrapper); + // Sync batch pagination controller overlay + let navBar = wrapper.querySelector(".comfy-sidebar-batch-navbar"); + if (state.images.length > 1) { + if (!navBar) { + navBar = document.createElement("div"); + navBar.className = "comfy-sidebar-batch-navbar"; + Object.assign(navBar.style, { + position: "absolute", bottom: "6px", left: "50%", + display: "flex", alignItems: "center", gap: "8px", background: "rgba(0,0,0,0.75)", + padding: "4px 10px", borderRadius: "12px", zIndex: "15", fontSize: "10px", + fontFamily: "monospace", color: "#eee", userSelect: "none", pointerEvents: "auto", + boxShadow: "0 1px 3px rgba(0,0,0,0.4)", transform: "translate3d(-50%, 0, 0)" + }); + + const prevBtn = document.createElement("span"); + prevBtn.className = "pi pi-chevron-left"; + prevBtn.style.cursor = "pointer"; + prevBtn.onclick = (ev) => { + ev.stopPropagation(); + cardObj.currentImageIndex = (cardObj.currentImageIndex - 1 + state.images.length) % state.images.length; + renderCardImages(cardObj, state, keepAspect); + }; + + const label = document.createElement("span"); + label.className = "comfy-sidebar-batch-label"; + label.style.cursor = "pointer"; + label.title = "View all images in this batch"; + + label.onclick = (ev) => { + ev.stopPropagation(); + State.activeSubmenuBatchImages = { + pid: state.pid || cardObj.element.id.replace("card-", ""), + images: state.images, + workflow: state.workflow + }; + renderDOM(); + }; + + const nextBtn = document.createElement("span"); + nextBtn.className = "pi pi-chevron-right"; + nextBtn.style.cursor = "pointer"; + nextBtn.onclick = (ev) => { + ev.stopPropagation(); + cardObj.currentImageIndex = (cardObj.currentImageIndex + 1) % state.images.length; + renderCardImages(cardObj, state, keepAspect); + }; + + navBar.append(prevBtn, label, nextBtn); + wrapper.appendChild(navBar); + } + + const label = navBar.querySelector(".comfy-sidebar-batch-label"); + if (label) label.textContent = `${idx + 1}/${state.images.length}`; + } else { + if (navBar) navBar.remove(); + } } let globalClickRegistered = false; @@ -832,6 +830,7 @@ export function renderDOM() { e.dataTransfer.effectAllowed = "copy"; } }); + cardObj.element.id = `card-${state.pid}`; // set ID helper cardElements.set(state.pid, cardObj); }