Compare commits

..
1 Commits
Author SHA1 Message Date
m0rtus59 9adfb2a53f Improve 'view intermediate' button logic 2026-09-12 22:04:51 +05:00
4 changed files with 147 additions and 15 deletions
+61 -3
View File
@@ -2,7 +2,7 @@ import { app } from "/scripts/app.js";
import { PromptStatus } from "../core/constants.js";
import { store } from "../core/store.js";
import { renderCardImages } from "./mediaPreview.js";
import { copyImageToClipboard, openFileOrFolder, deleteFileOnServer, findNodeIdForImage } from "./mediaActions.js";
import { copyImageToClipboard, openFileOrFolder, deleteFileOnServer, findNodeIdForImage, removeImageFromNodeOutputs } from "./mediaActions.js";
import { centerAndSelectCanvasNode } from "../comfy/adapter.js";
import { getRunOutputs, extractWorkflowFromPng, isImageFormat } from "../utils/utils.js";
import { showFullscreenPreview } from "../utils/comparison.js";
@@ -158,17 +158,75 @@ export function syncCardButtonVisibility(cardObj, state) {
(state.status !== PromptStatus.COMPLETED && validOutputs.length > 0);
if (hasIntermediates) {
// Check if intermediate files still exist on disk
for (const out of distinctOutputs) {
for (const img of out.images) {
const src = img.url ? img.url : `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}`;
if (!src.startsWith("blob:")) {
fetch(src, { method: "HEAD" }).then(res => {
if (res.status === 404) {
const liveState = store.getPrompt(state.pid);
if (liveState && liveState.nodeOutputs) {
removeImageFromNodeOutputs(liveState.nodeOutputs, img);
store.updatePrompt(liveState.pid, liveState);
syncCardButtonVisibility(cardObj, liveState);
}
}
}).catch(() => {});
}
}
}
cardObj.leftHoverBtn.style.removeProperty("display");
cardObj.leftHoverBtn.style.display = "inline-flex";
cardObj.leftHoverBtn.onclick = (ev) => {
cardObj.leftHoverBtn.onclick = async (ev) => {
ev.stopPropagation();
// On click: verify distinct outputs still exist before opening
const liveState = store.getPrompt(state.pid) || state;
const curOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
const primSigs = new Set((liveState.images || []).map(i => `${i.subfolder || ""}/${i.filename}`));
const curDistinct = curOutputs.filter(o => o.images && o.images.some(img => !primSigs.has(`${img.subfolder || ""}/${img.filename}`)));
if (curDistinct.length === 0) {
cardObj.leftHoverBtn.style.setProperty("display", "none", "important");
return;
}
let anyMissing = false;
for (const out of curDistinct) {
for (const img of out.images) {
const src = img.url ? img.url : `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}`;
if (!src.startsWith("blob:")) {
try {
const res = await fetch(src, { method: "HEAD" });
if (res.status === 404) {
removeImageFromNodeOutputs(liveState.nodeOutputs, img);
anyMissing = true;
}
} catch (e) {}
}
}
}
if (anyMissing) {
store.updatePrompt(liveState.pid, liveState);
syncCardButtonVisibility(cardObj, liveState);
const remaining = getRunOutputs(liveState.nodeOutputs, liveState.workflow)
.filter(o => o.images && o.images.some(img => !primSigs.has(`${img.subfolder || ""}/${img.filename}`)));
if (remaining.length === 0) {
return; // All intermediate outputs were wiped; button is now hidden
}
}
const scrollEl = document.querySelector('.sidebar-content-container, [class*="sidebar-content-container"], [class*="overflow-y-auto"]')
|| store.ui.cardStack?.parentElement
|| store.ui.cardStack;
if (scrollEl) {
store.ui.mainQueueScrollTop = scrollEl.scrollTop;
}
store.openOutputsSubmenu(state.pid);
store.openOutputsSubmenu(liveState.pid);
};
} else {
cardObj.leftHoverBtn.style.setProperty("display", "none", "important");
+84 -11
View File
@@ -1,8 +1,9 @@
import { isImageFormat, isVideoFormat, is3DFormat, isAudioFormat, getFilenameFromUrl } from "../utils/utils.js";
import { openFileOrFolder } from "./mediaActions.js";
import { openFileOrFolder, removeImageFromNodeOutputs } from "./mediaActions.js";
import { stopAllAudioPlayback, isAudioViewerOpen, setCurrentlyPlayingAudio, getCurrentlyPlayingAudio } from "./audioController.js";
import { showFullscreenPreview } from "../utils/comparison.js";
import { store } from "../core/store.js";
import { PromptStatus } from "../core/constants.js";
export function render3DCardPreview(cardObj, wrapper, src, img, state) {
let preview3D = wrapper.querySelector(".comfy-sidebar-3d-wrapper");
@@ -272,6 +273,9 @@ export function renderAudioCardPreview(cardObj, wrapper, src, img, state) {
}
export function renderGenericFileCardPreview(cardObj, wrapper, src, img, state) {
if (cardObj.dimEl) cardObj.dimEl.style.display = "none";
const playIcon = wrapper.querySelector(".comfy-sidebar-play-icon");
if (playIcon) playIcon.remove();
let previewFile = wrapper.querySelector(".comfy-sidebar-file-wrapper");
const fullUrl = img.url ? img.url : window.location.origin + `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}`;
const filename = img.filename || getFilenameFromUrl(src) || "output_file";
@@ -441,18 +445,87 @@ export function renderCardImages(cardObj, state, onNavigateBatch) {
showFullscreenPreview([activeSrc], ev.shiftKey, state?.pid);
};
mediaEl.onerror = async () => {
if (src && !src.startsWith("blob:") && !is3D && !isAudio) {
try {
const res = await fetch(src, { method: "HEAD" });
if (res.status === 404) {
store.deletePrompt(state.pid);
if (cardObj.element) cardObj.element.remove();
return;
mediaEl.onerror = () => {
mediaEl.onerror = null;
if ((src && src.startsWith("blob:")) || is3D || isAudio) return;
const cardId = cardObj.element?.id || "";
// 1. If an intermediate output item is missing, remove only this item
if (cardId.startsWith("card-submenu-")) {
const parentState = store.getPrompt(state.pid);
if (parentState && parentState.nodeOutputs) {
if (state.nodeId && parentState.nodeOutputs[state.nodeId]) {
delete parentState.nodeOutputs[state.nodeId];
} else {
removeImageFromNodeOutputs(parentState.nodeOutputs, img);
}
} catch (e) {}
renderGenericFileCardPreview(cardObj, wrapper, src, img, state);
store.updatePrompt(parentState.pid, parentState);
}
cardObj.element?.remove();
const remaining = store.ui.cardStack?.querySelectorAll('.comfy-sidebar-card');
if (!remaining || remaining.length === 0) {
store.closeSubmenu();
}
return;
}
// 2. If a batch item is missing, remove only this item from the batch
if (cardId.startsWith("card-batch-")) {
const parentPid = state.parentPromptId || state.pid;
const parentState = store.getPrompt(parentPid);
if (parentState) {
if (Array.isArray(parentState.images)) {
const idx = parentState.images.findIndex(i => i.filename === img.filename && (i.subfolder || "") === (img.subfolder || ""));
if (idx > -1) parentState.images.splice(idx, 1);
}
if (parentState.nodeOutputs) {
removeImageFromNodeOutputs(parentState.nodeOutputs, img);
}
store.updatePrompt(parentPid, parentState);
}
cardObj.element?.remove();
const remaining = store.ui.cardStack?.querySelectorAll('.comfy-sidebar-card');
if (!remaining || remaining.length === 0) {
store.closeSubmenu();
}
return;
}
// 3. Main card: if this file is gone, clean it up
const liveState = store.getPrompt(state.pid);
if (liveState) {
if (Array.isArray(liveState.images)) {
liveState.images = liveState.images.filter(i =>
!(i.filename === img.filename && (i.subfolder || "") === (img.subfolder || ""))
);
}
if (liveState.nodeOutputs) {
removeImageFromNodeOutputs(liveState.nodeOutputs, img);
}
// If the card still has other images (e.g. from a batch), persist and switch to next image
if (liveState.images && liveState.images.length > 0) {
cardObj.currentImageIndex = 0;
cardObj.lastImagesSignature = "";
store.updatePrompt(liveState.pid, liveState);
return;
}
// If the card has text outputs or is still in progress, persist
if ((liveState.texts && liveState.texts.length > 0) || (liveState.status && liveState.status !== PromptStatus.COMPLETED)) {
cardObj.lastImagesSignature = "";
store.updatePrompt(liveState.pid, liveState);
return;
}
// Only delete the main card when all items belonging to it are gone
store.deletePrompt(liveState.pid);
cardObj.element?.remove();
return;
}
renderGenericFileCardPreview(cardObj, wrapper, src, img, state);
};
const applyDimensions = (width, height) => {
+1
View File
@@ -749,6 +749,7 @@ export function renderSidebar() {
cardObj.placeholder.style.display = "none";
renderCardImages(cardObj, {
pid: st.pid,
nodeId: out.nodeId,
status: PromptStatus.COMPLETED,
images: out.images,
workflow: st.workflow,
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfy-sidebar"
description = "A custom queue sidebar and image comparison extension that reintroduces and improves a sidebar queue menu and unified compact topbar layout for ComfyUI."
version = "1.5.1"
version = "1.5.2"
license = { file = "LICENSE" }
dependencies = []