Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cad4098496 | ||
|
|
6c3422e5c0 |
+34
-32
@@ -5,7 +5,7 @@ import { store } from "../core/store.js";
|
||||
import { renderCardImages } from "./mediaPreview.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 { getRunOutputs, extractWorkflowFromPng, isImageFormat, animateCardRemoval } from "../utils/utils.js";
|
||||
import { showFullscreenPreview } from "../utils/comparison.js";
|
||||
import { stopAllAudioPlayback } from "./audioController.js";
|
||||
import { deleteHistoryItem, cancelPendingTask, interruptActive } from "../queue/queueService.js";
|
||||
@@ -528,44 +528,46 @@ export function getOrCreateCard(state, callbacks = {}) {
|
||||
const shouldDeleteFromDisk = isDiskDelete || wantsDiskDelete;
|
||||
resetDeleteBtn();
|
||||
|
||||
const liveState = store.getPrompt(state.pid) || state;
|
||||
animateCardRemoval(card, async () => {
|
||||
const liveState = store.getPrompt(state.pid) || state;
|
||||
|
||||
if (shouldDeleteFromDisk) {
|
||||
const filesToDelete = [];
|
||||
const seen = new Set();
|
||||
const addFile = (item) => {
|
||||
if (item && item.filename) {
|
||||
const key = `${item.type || ""}/${item.subfolder || ""}/${item.filename}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
filesToDelete.push(item);
|
||||
if (shouldDeleteFromDisk) {
|
||||
const filesToDelete = [];
|
||||
const seen = new Set();
|
||||
const addFile = (item) => {
|
||||
if (item && item.filename) {
|
||||
const key = `${item.type || ""}/${item.subfolder || ""}/${item.filename}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
filesToDelete.push(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(liveState.images || []).forEach(addFile);
|
||||
const allOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
|
||||
allOutputs.forEach(out => (out.images || []).forEach(addFile));
|
||||
|
||||
let deletedCount = 0;
|
||||
let lastAction = "trashed";
|
||||
for (const fileItem of filesToDelete) {
|
||||
const action = await deleteFileOnServer(fileItem);
|
||||
if (action) {
|
||||
deletedCount++;
|
||||
lastAction = action;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(liveState.images || []).forEach(addFile);
|
||||
const allOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
|
||||
allOutputs.forEach(out => (out.images || []).forEach(addFile));
|
||||
|
||||
let deletedCount = 0;
|
||||
let lastAction = "trashed";
|
||||
for (const fileItem of filesToDelete) {
|
||||
const action = await deleteFileOnServer(fileItem);
|
||||
if (action) {
|
||||
deletedCount++;
|
||||
lastAction = action;
|
||||
if (deletedCount > 0) {
|
||||
const msg = lastAction === "trashed"
|
||||
? `Moved ${deletedCount} file${deletedCount > 1 ? "s" : ""} to Recycle Bin.`
|
||||
: `Permanently deleted ${deletedCount} file${deletedCount > 1 ? "s" : ""}.`;
|
||||
showNotification(msg, "success", "Delete Files");
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedCount > 0) {
|
||||
const msg = lastAction === "trashed"
|
||||
? `Moved ${deletedCount} file${deletedCount > 1 ? "s" : ""} to Recycle Bin.`
|
||||
: `Permanently deleted ${deletedCount} file${deletedCount > 1 ? "s" : ""}.`;
|
||||
showNotification(msg, "success", "Delete Files");
|
||||
}
|
||||
}
|
||||
|
||||
deleteHistoryItem(state.pid);
|
||||
deleteHistoryItem(state.pid);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+109
-10
@@ -2,7 +2,7 @@ import { app } from "/scripts/app.js";
|
||||
import { SettingIds, PromptStatus } from "../core/constants.js";
|
||||
import { t } from "../core/i18n.js";
|
||||
import { deleteFileOnServer, openFileOrFolder } from "./mediaActions.js";
|
||||
import { extractWorkflowFromPng, isImageFormat } from "../utils/utils.js";
|
||||
import { extractWorkflowFromPng, isImageFormat, animateCardRemoval } from "../utils/utils.js";
|
||||
import { renderCardImages } from "./mediaPreview.js";
|
||||
import { showNotification } from "../utils/nodeRenumber.js";
|
||||
|
||||
@@ -12,6 +12,10 @@ let scrollToTopBtnEl = null;
|
||||
let currentColumnCount = 1;
|
||||
let currentSubfolder = "";
|
||||
let currentSearchQuery = "";
|
||||
let stickyHeaderGroupEl = null;
|
||||
let lastScrollTop = 0;
|
||||
let scrollDeltaAccumulator = 0;
|
||||
let isHeaderHidden = false;
|
||||
|
||||
// Persistent sort order
|
||||
let sortOrder = "newest";
|
||||
@@ -41,14 +45,18 @@ function getScrollContainer() {
|
||||
}
|
||||
|
||||
function updateScrollTopVisibility() {
|
||||
if (!scrollToTopBtnEl) return;
|
||||
if (!galleryContainerEl || !galleryContainerEl.isConnected) {
|
||||
scrollToTopBtnEl.style.display = "none";
|
||||
if (scrollToTopBtnEl) scrollToTopBtnEl.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const scrollEl = getScrollContainer();
|
||||
if (scrollEl) {
|
||||
scrollToTopBtnEl.style.display = scrollEl.scrollTop > 200 ? "flex" : "none";
|
||||
if (scrollToTopBtnEl) {
|
||||
scrollToTopBtnEl.style.display = scrollEl.scrollTop > 200 ? "flex" : "none";
|
||||
}
|
||||
|
||||
// Handle smart scroll-up reveal for header & navigation
|
||||
handleHeaderScroll(scrollEl);
|
||||
|
||||
if (scrollEl.scrollTop + scrollEl.clientHeight >= scrollEl.scrollHeight - 300) {
|
||||
loadNextBatch();
|
||||
@@ -56,6 +64,55 @@ function updateScrollTopVisibility() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleHeaderScroll(scrollEl) {
|
||||
if (!stickyHeaderGroupEl) return;
|
||||
|
||||
// Never hide header if search bar is currently active
|
||||
const searchContainer = stickyHeaderGroupEl.querySelector(".comfy-sidebar-search-container");
|
||||
if (currentSearchQuery || (searchContainer && searchContainer.style.display !== "none")) {
|
||||
stickyHeaderGroupEl.style.transform = "translateY(0)";
|
||||
isHeaderHidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentScroll = scrollEl.scrollTop;
|
||||
const delta = currentScroll - lastScrollTop;
|
||||
lastScrollTop = currentScroll;
|
||||
|
||||
// Near the very top: always keep visible & transparent
|
||||
if (currentScroll <= 35) {
|
||||
stickyHeaderGroupEl.classList.remove("is-scrolled");
|
||||
if (isHeaderHidden) {
|
||||
stickyHeaderGroupEl.style.transform = "translateY(0)";
|
||||
isHeaderHidden = false;
|
||||
}
|
||||
scrollDeltaAccumulator = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
stickyHeaderGroupEl.classList.add("is-scrolled");
|
||||
|
||||
if (delta > 0) {
|
||||
// Scrolling DOWN: hide after small buffer
|
||||
if (scrollDeltaAccumulator < 0) scrollDeltaAccumulator = 0;
|
||||
scrollDeltaAccumulator += delta;
|
||||
|
||||
if (scrollDeltaAccumulator > 25 && !isHeaderHidden) {
|
||||
stickyHeaderGroupEl.style.transform = "translateY(-100%)";
|
||||
isHeaderHidden = true;
|
||||
}
|
||||
} else if (delta < 0) {
|
||||
// Scrolling UP: reveal as soon as user scrolls up a little bit (~15px)
|
||||
if (scrollDeltaAccumulator > 0) scrollDeltaAccumulator = 0;
|
||||
scrollDeltaAccumulator += delta;
|
||||
|
||||
if (scrollDeltaAccumulator < -15 && isHeaderHidden) {
|
||||
stickyHeaderGroupEl.style.transform = "translateY(0)";
|
||||
isHeaderHidden = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchGalleryData(subfolder = "", searchQuery = "") {
|
||||
try {
|
||||
isLoading = true;
|
||||
@@ -243,7 +300,7 @@ function createFileCard(file) {
|
||||
}
|
||||
|
||||
const btnDel = document.createElement("span");
|
||||
btnDel.className = "pi pi-trash comfy-sidebar-card-action-btn comfy-sidebar-btn-del";
|
||||
btnDel.className = "pi pi-trash comfy-sidebar-card-action-btn comfy-sidebar-btn-del comfy-sidebar-gallery-btn-del";
|
||||
btnDel.title = t("Delete file (Hold Ctrl to delete file from disk)");
|
||||
const btnDelLabel = document.createElement("span");
|
||||
btnDelLabel.className = "comfy-sidebar-del-label";
|
||||
@@ -278,7 +335,14 @@ function createFileCard(file) {
|
||||
const msg = action === "trashed" ? t("Moved file to Recycle Bin.") : t("Permanently deleted file.");
|
||||
showNotification(msg, "success", t("Delete File"));
|
||||
cachedFiles = cachedFiles.filter(f => f.filename !== file.filename || f.subfolder !== file.subfolder);
|
||||
renderGallery();
|
||||
displayedFilesCount = Math.max(0, displayedFilesCount - 1);
|
||||
|
||||
animateCardRemoval(card, () => {
|
||||
card.remove();
|
||||
if (cachedFiles.length === 0) {
|
||||
renderGallery();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -440,7 +504,8 @@ export function setupGalleryView() {
|
||||
|
||||
Object.assign(container.style, {
|
||||
display: "flex", flexDirection: "column", height: "100%", padding: "14px", boxSizing: "border-box",
|
||||
background: "var(--comfy-menu-bg, #121212)", color: "var(--fg-color, #eee)", position: "relative"
|
||||
background: "transparent",
|
||||
color: "var(--fg-color, var(--p-text-color, #eee))", position: "relative"
|
||||
});
|
||||
|
||||
const header = document.createElement("div");
|
||||
@@ -513,6 +578,7 @@ export function setupGalleryView() {
|
||||
standardHeader.appendChild(actionsGroup);
|
||||
|
||||
const searchContainer = document.createElement("div");
|
||||
searchContainer.className = "comfy-sidebar-search-container";
|
||||
Object.assign(searchContainer.style, {
|
||||
display: "none", width: "100%", alignItems: "center", background: "var(--comfy-input-bg, #181818)",
|
||||
border: "1px solid var(--border-color, #555)", borderRadius: "4px", padding: "2px 8px",
|
||||
@@ -534,10 +600,18 @@ export function setupGalleryView() {
|
||||
|
||||
searchContainer.append(sIcon, sInput, sClear);
|
||||
header.append(standardHeader, searchContainer);
|
||||
container.appendChild(header);
|
||||
|
||||
const stickyHeaderGroup = document.createElement("div");
|
||||
stickyHeaderGroup.className = "comfy-sidebar-sticky-header-group";
|
||||
stickyHeaderGroupEl = stickyHeaderGroup;
|
||||
stickyHeaderGroup.appendChild(header);
|
||||
|
||||
let scrollBeforeSearch = 0;
|
||||
|
||||
searchIcon.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
const scrollEl = getScrollContainer();
|
||||
scrollBeforeSearch = scrollEl ? scrollEl.scrollTop : 0;
|
||||
standardHeader.style.display = "none";
|
||||
searchContainer.style.display = "flex";
|
||||
sInput.focus();
|
||||
@@ -545,12 +619,25 @@ export function setupGalleryView() {
|
||||
|
||||
let searchDebounce = null;
|
||||
const closeSearch = () => {
|
||||
const hadFilter = Boolean(currentSearchQuery || sInput.value.trim());
|
||||
sInput.value = "";
|
||||
currentSearchQuery = "";
|
||||
sIcon.className = "pi pi-search";
|
||||
searchContainer.style.display = "none";
|
||||
standardHeader.style.display = "flex";
|
||||
fetchGalleryData(currentSubfolder, "").then(() => renderGallery());
|
||||
|
||||
// If the user didn't type a search query, do NOT re-fetch or wipe cards.
|
||||
// The scroll position stays right where you are.
|
||||
if (hadFilter) {
|
||||
fetchGalleryData(currentSubfolder, "").then(() => {
|
||||
renderGallery();
|
||||
const scrollEl = getScrollContainer();
|
||||
if (scrollEl && scrollBeforeSearch > 0) {
|
||||
scrollEl.scrollTop = scrollBeforeSearch;
|
||||
requestAnimationFrame(() => { scrollEl.scrollTop = scrollBeforeSearch; });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sClear.onclick = (e) => { e.stopPropagation(); closeSearch(); };
|
||||
@@ -573,7 +660,8 @@ export function setupGalleryView() {
|
||||
|
||||
const breadcrumbBar = document.createElement("div");
|
||||
breadcrumbBar.className = "comfy-sidebar-breadcrumb-bar";
|
||||
container.appendChild(breadcrumbBar);
|
||||
stickyHeaderGroup.appendChild(breadcrumbBar);
|
||||
container.appendChild(stickyHeaderGroup);
|
||||
|
||||
const cardStack = document.createElement("div");
|
||||
cardStackEl = cardStack;
|
||||
@@ -607,6 +695,16 @@ export function setupGalleryView() {
|
||||
window.addEventListener("keydown", handleCtrlDown);
|
||||
window.addEventListener("keyup", handleCtrlUp);
|
||||
|
||||
// Reveal Spotlight: track cursor position over Gallery card and folder borders
|
||||
container.addEventListener("pointermove", (e) => {
|
||||
const card = e.target.closest(".comfy-sidebar-card, .comfy-sidebar-folder-card");
|
||||
if (card) {
|
||||
const rect = card.getBoundingClientRect();
|
||||
card.style.setProperty("--mouse-x", `${e.clientX - rect.left}px`);
|
||||
card.style.setProperty("--mouse-y", `${e.clientY - rect.top}px`);
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener("scroll", updateScrollTopVisibility, { capture: true, passive: true });
|
||||
|
||||
new ResizeObserver((entries) => {
|
||||
@@ -627,6 +725,7 @@ export function teardownGalleryView() {
|
||||
document.removeEventListener("scroll", updateScrollTopVisibility, { capture: true, passive: true });
|
||||
cachedFolders = [];
|
||||
cachedFiles = [];
|
||||
stickyHeaderGroupEl = null;
|
||||
galleryContainerEl = null;
|
||||
cardStackEl = null;
|
||||
scrollToTopBtnEl = null;
|
||||
|
||||
+74
-59
@@ -5,7 +5,7 @@ import { t } from "../core/i18n.js";
|
||||
import { getOrCreateCard, updateCardDOM, resetAllCardHoverStates, cardPool } from "./cardRenderer.js";
|
||||
import { clearCancelledOrFailed, clearAllHistory, cancelAllPending } from "../queue/queueService.js";
|
||||
import { updateSidebarBadge, centerAndSelectCanvasNode } from "../comfy/adapter.js";
|
||||
import { matchesFilter, getRunOutputs, isImageFormat } from "../utils/utils.js";
|
||||
import { matchesFilter, getRunOutputs, isImageFormat, animateCardRemoval } from "../utils/utils.js";
|
||||
import { renderCardImages } from "./mediaPreview.js";
|
||||
import { deleteFileOnServer, removeImageFromNodeOutputs, copyImageToClipboard, findNodeIdForImage } from "./mediaActions.js";
|
||||
import { toggleIgnoreActiveNode } from "../comfy/nodes.js";
|
||||
@@ -166,7 +166,8 @@ export function setupSidebarView() {
|
||||
|
||||
Object.assign(container.style, {
|
||||
display: "flex", flexDirection: "column", height: "100%", padding: "14px", boxSizing: "border-box",
|
||||
background: "var(--comfy-menu-bg, #121212)", color: "var(--fg-color, #eee)", position: "relative"
|
||||
background: "transparent",
|
||||
color: "var(--fg-color, var(--p-text-color, #eee))", position: "relative"
|
||||
});
|
||||
|
||||
const header = document.createElement("div");
|
||||
@@ -356,6 +357,16 @@ export function setupSidebarView() {
|
||||
|
||||
container.appendChild(scrollToTopBtnEl);
|
||||
|
||||
// Reveal Spotlight: track cursor position over Queue card borders
|
||||
container.addEventListener("pointermove", (e) => {
|
||||
const card = e.target.closest(".comfy-sidebar-card");
|
||||
if (card) {
|
||||
const rect = card.getBoundingClientRect();
|
||||
card.style.setProperty("--mouse-x", `${e.clientX - rect.left}px`);
|
||||
card.style.setProperty("--mouse-y", `${e.clientY - rect.top}px`);
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener("scroll", updateScrollTopBtnVisibility, { capture: true, passive: true });
|
||||
|
||||
new ResizeObserver((entries) => {
|
||||
@@ -516,46 +527,48 @@ export function renderSidebar() {
|
||||
const shouldDeleteFromDisk = isDiskDelete || wantsDiskDelete;
|
||||
resetDelete();
|
||||
|
||||
if (shouldDeleteFromDisk && img && img.filename) {
|
||||
const action = await deleteFileOnServer(img);
|
||||
if (action) {
|
||||
const msg = action === "trashed" ? t("Moved file to Recycle Bin.") : t("Permanently deleted file.");
|
||||
showNotification(msg, "success", t("Delete File"));
|
||||
animateCardRemoval(card, async () => {
|
||||
if (shouldDeleteFromDisk && img && img.filename) {
|
||||
const action = await deleteFileOnServer(img);
|
||||
if (action) {
|
||||
const msg = action === "trashed" ? t("Moved file to Recycle Bin.") : t("Permanently deleted file.");
|
||||
showNotification(msg, "success", t("Delete File"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pid = batchInfo.parentPromptId || batchInfo.pid;
|
||||
const state = store.getPrompt(pid);
|
||||
const imgIdx = batchInfo.images.indexOf(img);
|
||||
if (imgIdx > -1) batchInfo.images.splice(imgIdx, 1);
|
||||
const pid = batchInfo.parentPromptId || batchInfo.pid;
|
||||
const state = store.getPrompt(pid);
|
||||
const imgIdx = batchInfo.images.indexOf(img);
|
||||
if (imgIdx > -1) batchInfo.images.splice(imgIdx, 1);
|
||||
|
||||
if (state) {
|
||||
if (Array.isArray(state.images)) {
|
||||
const stIdx = state.images.findIndex(i => i.filename === img.filename && (i.subfolder || "") === (img.subfolder || ""));
|
||||
if (stIdx > -1) state.images.splice(stIdx, 1);
|
||||
if (state) {
|
||||
if (Array.isArray(state.images)) {
|
||||
const stIdx = state.images.findIndex(i => i.filename === img.filename && (i.subfolder || "") === (img.subfolder || ""));
|
||||
if (stIdx > -1) state.images.splice(stIdx, 1);
|
||||
}
|
||||
if (state.nodeOutputs) {
|
||||
removeImageFromNodeOutputs(state.nodeOutputs, img);
|
||||
}
|
||||
state.rendered = false;
|
||||
store.updatePrompt(pid, state);
|
||||
}
|
||||
if (state.nodeOutputs) {
|
||||
removeImageFromNodeOutputs(state.nodeOutputs, img);
|
||||
|
||||
const mainCardObj = cardPool.get(pid);
|
||||
if (mainCardObj) {
|
||||
mainCardObj.currentImageIndex = 0;
|
||||
mainCardObj.lastImagesSignature = "";
|
||||
}
|
||||
state.rendered = false;
|
||||
store.updatePrompt(pid, state);
|
||||
}
|
||||
|
||||
const mainCardObj = cardPool.get(pid);
|
||||
if (mainCardObj) {
|
||||
mainCardObj.currentImageIndex = 0;
|
||||
mainCardObj.lastImagesSignature = "";
|
||||
}
|
||||
|
||||
if (batchInfo.images.length === 0) {
|
||||
if (batchInfo.parentPromptId) {
|
||||
store.openOutputsSubmenu(batchInfo.parentPromptId);
|
||||
} else {
|
||||
store.closeSubmenu();
|
||||
if (batchInfo.images.length === 0) {
|
||||
if (batchInfo.parentPromptId) {
|
||||
store.openOutputsSubmenu(batchInfo.parentPromptId);
|
||||
} else {
|
||||
store.closeSubmenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderSidebar();
|
||||
renderSidebar();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -787,35 +800,37 @@ export function renderSidebar() {
|
||||
const shouldDeleteFromDisk = isDiskDelete || wantsDiskDelete;
|
||||
resetDelete();
|
||||
|
||||
if (shouldDeleteFromDisk && out.images) {
|
||||
let count = 0;
|
||||
let lastAct = "trashed";
|
||||
for (const imgItem of out.images) {
|
||||
const action = await deleteFileOnServer(imgItem);
|
||||
if (action) { count++; lastAct = action; }
|
||||
animateCardRemoval(card, async () => {
|
||||
if (shouldDeleteFromDisk && out.images) {
|
||||
let count = 0;
|
||||
let lastAct = "trashed";
|
||||
for (const imgItem of out.images) {
|
||||
const action = await deleteFileOnServer(imgItem);
|
||||
if (action) { count++; lastAct = action; }
|
||||
}
|
||||
if (count > 0) {
|
||||
const msg = lastAct === "trashed"
|
||||
? `Moved ${count} file${count > 1 ? "s" : ""} to Recycle Bin.`
|
||||
: `Permanently deleted ${count} file${count > 1 ? "s" : ""}.`;
|
||||
showNotification(msg, "success", "Delete Files");
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
const msg = lastAct === "trashed"
|
||||
? `Moved ${count} file${count > 1 ? "s" : ""} to Recycle Bin.`
|
||||
: `Permanently deleted ${count} file${count > 1 ? "s" : ""}.`;
|
||||
showNotification(msg, "success", "Delete Files");
|
||||
|
||||
if (st.nodeOutputs && st.nodeOutputs[out.nodeId]) {
|
||||
delete st.nodeOutputs[out.nodeId];
|
||||
}
|
||||
}
|
||||
|
||||
if (st.nodeOutputs && st.nodeOutputs[out.nodeId]) {
|
||||
delete st.nodeOutputs[out.nodeId];
|
||||
}
|
||||
const remainingOutputs = getRunOutputs(st.nodeOutputs, st.workflow);
|
||||
if (remainingOutputs.length > 0) {
|
||||
st.images = remainingOutputs[remainingOutputs.length - 1].images || [];
|
||||
} else {
|
||||
st.images = [];
|
||||
store.closeSubmenu();
|
||||
}
|
||||
|
||||
const remainingOutputs = getRunOutputs(st.nodeOutputs, st.workflow);
|
||||
if (remainingOutputs.length > 0) {
|
||||
st.images = remainingOutputs[remainingOutputs.length - 1].images || [];
|
||||
} else {
|
||||
st.images = [];
|
||||
store.closeSubmenu();
|
||||
}
|
||||
|
||||
store.updatePrompt(st.pid, st);
|
||||
renderSidebar();
|
||||
store.updatePrompt(st.pid, st);
|
||||
renderSidebar();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+96
-10
@@ -4,17 +4,69 @@ export function injectStyles() {
|
||||
style.id = "comfy-sidebar-styles";
|
||||
style.textContent = `
|
||||
.comfy-sidebar-card {
|
||||
background: var(--comfy-input-bg, #181818);
|
||||
background: var(--comfy-input-bg, var(--p-surface-800, rgba(128, 128, 128, 0.08)));
|
||||
border-radius: 4px; padding: 8px; position: relative;
|
||||
min-height: 80px; margin-bottom: 0px; break-inside: avoid;
|
||||
user-select: none; -webkit-user-select: none;
|
||||
border: 2px solid var(--border-color, #333);
|
||||
border: 2px solid var(--border-color, rgba(128, 128, 128, 0.25));
|
||||
color: var(--comfy-input-color, var(--fg-color, #eee));
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Subtle neutral border hover (No blue clash with running jobs) */
|
||||
.comfy-sidebar-card:hover {
|
||||
border-color: var(--p-primary-color, var(--primary-color, #555)) !important;
|
||||
border-color: color-mix(in srgb, var(--border-color, #555) 50%, var(--fg-color, #fff) 50%) !important;
|
||||
background: color-mix(in srgb, var(--comfy-input-bg, var(--p-surface-800, rgba(128, 128, 128, 0.1))) 85%, var(--fg-color, #888) 15%) !important;
|
||||
transition: border-color 0.2s, background-color 0.2s;
|
||||
}
|
||||
|
||||
/* Border & Frame Spotlight (Renders BEHIND the image so PNG is never washed out) */
|
||||
.comfy-sidebar-card::before,
|
||||
.comfy-sidebar-folder-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: radial-gradient(
|
||||
160px circle at var(--mouse-x, -999px) var(--mouse-y, -999px),
|
||||
color-mix(in srgb, var(--p-primary-color, #38bdf8) 30%, var(--fg-color, #fff) 70%),
|
||||
transparent 75%
|
||||
);
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.comfy-sidebar-card:hover::before,
|
||||
.comfy-sidebar-folder-card:hover::before {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
/* Windows 10 Reveal Spotlight Effect (UI Folder Buttons Only) */
|
||||
.comfy-sidebar-folder-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.comfy-sidebar-folder-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: radial-gradient(
|
||||
140px circle at var(--mouse-x, -999px) var(--mouse-y, -999px),
|
||||
rgba(255, 255, 255, 0.16),
|
||||
transparent 75%
|
||||
);
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.comfy-sidebar-folder-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.comfy-sidebar-card.active { --border-color: #3b82f6; --hover-color: #60a5fa; }
|
||||
.comfy-sidebar-card.pending { --border-color: #6c757d; --hover-color: #adb5bd; }
|
||||
.comfy-sidebar-card.cancelled { --border-color: #ffc107; --hover-color: #ffe082; }
|
||||
@@ -555,9 +607,10 @@ export function injectStyles() {
|
||||
z-index: 1005 !important;
|
||||
}
|
||||
|
||||
/* Explicit card element layering so badges and [X] are ALWAYS on top of images */
|
||||
/* Media wrapper sits on z-index 2 so the spotlight shines on the border behind it */
|
||||
.comfy-sidebar-card .comfy-sidebar-media-wrapper {
|
||||
z-index: 1 !important;
|
||||
z-index: 2 !important;
|
||||
position: relative;
|
||||
}
|
||||
.comfy-sidebar-card-timer {
|
||||
z-index: 10 !important;
|
||||
@@ -584,6 +637,35 @@ export function injectStyles() {
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
/* Gallery delete button only shows in the Ctrl alternative actions menu */
|
||||
.comfy-sidebar-gallery-btn-del {
|
||||
display: none !important;
|
||||
}
|
||||
body.comfy-sidebar-ctrl-active .comfy-sidebar-gallery-btn-del,
|
||||
.comfy-sidebar-gallery-btn-del.confirm-delete,
|
||||
.comfy-sidebar-gallery-btn-del.confirm-delete-disk {
|
||||
display: inline-flex !important;
|
||||
}
|
||||
|
||||
/* Sticky Header & Nav Reveal (Seamlessly transparent at rest, frosted on scroll) */
|
||||
.comfy-sidebar-sticky-header-group {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
margin: -14px -14px 6px -14px;
|
||||
padding: 14px 14px 0 14px;
|
||||
background: transparent;
|
||||
z-index: 35;
|
||||
transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1), background 0.2s ease, box-shadow 0.2s ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.comfy-sidebar-sticky-header-group.is-scrolled {
|
||||
background: color-mix(in srgb, var(--p-content-background, var(--bg-color, var(--comfy-menu-bg, #181818))) 65%, transparent);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
/* Gallery Subfolders & Breadcrumb Styles */
|
||||
.comfy-sidebar-breadcrumb-bar {
|
||||
display: flex;
|
||||
@@ -623,10 +705,10 @@ export function injectStyles() {
|
||||
width: 100%;
|
||||
}
|
||||
.comfy-sidebar-folder-card {
|
||||
background: var(--comfy-input-bg, #181818);
|
||||
background: var(--comfy-input-bg, var(--p-surface-800, rgba(128, 128, 128, 0.08)));
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border-color, #333);
|
||||
border: 1px solid var(--border-color, rgba(128, 128, 128, 0.25));
|
||||
color: var(--comfy-input-color, var(--fg-color, #eee));
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -635,19 +717,23 @@ export function injectStyles() {
|
||||
transition: border-color 0.15s ease, background 0.15s ease, transform 0.1s ease;
|
||||
user-select: none;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.comfy-sidebar-folder-card:hover {
|
||||
border-color: var(--p-primary-color, #0ea5e9) !important;
|
||||
background: rgba(14, 165, 233, 0.12);
|
||||
border-color: color-mix(in srgb, var(--border-color, #555) 50%, var(--fg-color, #fff) 50%);
|
||||
background: color-mix(in srgb, var(--comfy-input-bg, var(--p-surface-800, rgba(128, 128, 128, 0.1))) 85%, var(--fg-color, #888) 15%);
|
||||
}
|
||||
.comfy-sidebar-folder-icon {
|
||||
font-size: 16px;
|
||||
color: var(--p-primary-color, #0ea5e9);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
body.comfy-sidebar-ctrl-active .comfy-sidebar-folder-card {
|
||||
body.comfy-sidebar-ctrl-active .comfy-sidebar-folder-card:hover {
|
||||
cursor: alias !important;
|
||||
border-color: var(--p-primary-color, #0ea5e9) !important;
|
||||
box-shadow: 0 0 0 1px var(--p-primary-color, #0ea5e9), 0 0 10px rgba(14, 165, 233, 0.3) !important;
|
||||
background: rgba(14, 165, 233, 0.15) !important;
|
||||
}
|
||||
.comfy-sidebar-folder-name {
|
||||
font-size: 11px;
|
||||
|
||||
@@ -431,4 +431,17 @@ export async function extractWorkflowFromPng(imageUrl) {
|
||||
console.warn("Comfy Sidebar: Could not extract workflow from PNG", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function animateCardRemoval(cardEl, callback, duration = 200) {
|
||||
if (!cardEl) {
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
cardEl.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
|
||||
cardEl.style.opacity = "0";
|
||||
cardEl.style.transform = "scale(0.85)";
|
||||
setTimeout(() => {
|
||||
if (callback) callback();
|
||||
}, duration);
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfy-sidebar"
|
||||
description = "A compact and powerful layout overhaul for ComfyUI that makes generation results easier to browse, preview, compare, and manage."
|
||||
version = "1.8.0"
|
||||
version = "1.8.1"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = [
|
||||
"send2trash>=1.8.0",
|
||||
|
||||
Reference in New Issue
Block a user