Compare commits

...
2 Commits
Author SHA1 Message Date
M0rtus59 cad4098496 UI/UX improvements 2026-11-06 16:35:46 +05:00
m0rtus59 6c3422e5c0 gallery improve nav panel usability 2026-11-05 23:57:51 +05:00
6 changed files with 327 additions and 112 deletions
+34 -32
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+13
View File
@@ -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
View File
@@ -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",