refactor: align layout, settings, and node hooks with native ComfyUI frontend API
This commit is contained in:
+1
-13
@@ -1,20 +1,8 @@
|
||||
# Standard ComfyUI extension loader
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
import uuid
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
# Tells ComfyUI to serve and load the contents of the js/ folder
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
# Create a unique session ID for this backend boot to track server restarts
|
||||
server_session_id = str(uuid.uuid4())
|
||||
|
||||
# Register a custom route to check for backend restarts
|
||||
@PromptServer.instance.routes.get("/classic-sidebar/session")
|
||||
async def get_session_id(request):
|
||||
return web.json_response({"session_id": server_session_id})
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
+30
-37
@@ -97,46 +97,26 @@ button:has([data-pc-name="avatar"]),
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Native settings synchronization without direct DOM element querying or simulated clicks.
|
||||
*/
|
||||
export function syncStockHistoryAndProgressSettings(enable) {
|
||||
if (!app.ui?.settings) return;
|
||||
|
||||
const dockedVal = !!enable;
|
||||
const progressVal = !enable;
|
||||
const QPOV2_ID = "Comfy.Queue.QPOV2";
|
||||
|
||||
try {
|
||||
app.ui.settings.setSettingValue(QPOV2_ID, dockedVal);
|
||||
localStorage.setItem(QPOV2_ID, JSON.stringify(dockedVal));
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
const qpovContainer = document.querySelector('[data-setting-id="Comfy.Queue.QPOV2"]') ||
|
||||
document.getElementById(QPOV2_ID)?.closest('.setting-item, tr, div');
|
||||
if (qpovContainer) {
|
||||
const toggle = qpovContainer.querySelector('input[type="checkbox"], [role="switch"], .p-toggleswitch');
|
||||
if (toggle) {
|
||||
const isChecked = toggle.checked ||
|
||||
toggle.getAttribute('data-p-checked') === 'true' ||
|
||||
toggle.classList.contains('p-toggleswitch-checked');
|
||||
if (isChecked !== dockedVal) toggle.click();
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
const progressKey = "Comfy.Queue.ShowRunProgressBar";
|
||||
try {
|
||||
app.ui.settings.setSettingValue(progressKey, progressVal);
|
||||
localStorage.setItem(progressKey, JSON.stringify(progressVal));
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
const progressBarBtn = document.querySelector('[data-testid="show-run-progress-bar-action"]');
|
||||
if (progressBarBtn) {
|
||||
const isChecked = !!progressBarBtn.querySelector('[class*="lucide--check"], .icon-\\[lucide--check\\]');
|
||||
if (isChecked !== progressVal) progressBarBtn.click();
|
||||
}
|
||||
} catch (e) {}
|
||||
// Use official extensionManager setting API if present, with fallback to app.ui.settings
|
||||
if (app.extensionManager?.setting) {
|
||||
try { app.extensionManager.setting.set(QPOV2_ID, dockedVal); } catch (e) {}
|
||||
try { app.extensionManager.setting.set(progressKey, progressVal); } catch (e) {}
|
||||
} else if (app.ui?.settings) {
|
||||
try { app.ui.settings.setSettingValue(QPOV2_ID, dockedVal); } catch (e) {}
|
||||
try { app.ui.settings.setSettingValue(progressKey, progressVal); } catch (e) {}
|
||||
}
|
||||
|
||||
try { localStorage.setItem(QPOV2_ID, JSON.stringify(dockedVal)); } catch (e) {}
|
||||
try { localStorage.setItem(progressKey, JSON.stringify(progressVal)); } catch (e) {}
|
||||
try { window.dispatchEvent(new Event("storage")); } catch (e) {}
|
||||
}
|
||||
|
||||
@@ -144,8 +124,12 @@ export function applyClassicLayout(enable, updateSetting = false) {
|
||||
let styleEl = document.getElementById(STYLE_ID);
|
||||
|
||||
if (enable) {
|
||||
if (updateSetting && app.ui?.settings) {
|
||||
app.ui.settings.setSettingValue("Comfy.Workflow.WorkflowTabsPosition", "Topbar");
|
||||
if (updateSetting) {
|
||||
if (app.extensionManager?.setting) {
|
||||
app.extensionManager.setting.set("Comfy.Workflow.WorkflowTabsPosition", "Topbar");
|
||||
} else if (app.ui?.settings) {
|
||||
app.ui.settings.setSettingValue("Comfy.Workflow.WorkflowTabsPosition", "Topbar");
|
||||
}
|
||||
}
|
||||
if (!styleEl) {
|
||||
styleEl = document.createElement("style");
|
||||
@@ -155,8 +139,12 @@ export function applyClassicLayout(enable, updateSetting = false) {
|
||||
styleEl.textContent = CLASSIC_LAYOUT_CSS_MEDIA;
|
||||
} else {
|
||||
if (styleEl) styleEl.remove();
|
||||
if (updateSetting && app.ui?.settings) {
|
||||
app.ui.settings.setSettingValue("Comfy.Workflow.WorkflowTabsPosition", "Sidebar");
|
||||
if (updateSetting) {
|
||||
if (app.extensionManager?.setting) {
|
||||
app.extensionManager.setting.set("Comfy.Workflow.WorkflowTabsPosition", "Sidebar");
|
||||
} else if (app.ui?.settings) {
|
||||
app.ui.settings.setSettingValue("Comfy.Workflow.WorkflowTabsPosition", "Sidebar");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +154,11 @@ let domObserver = null;
|
||||
let syncScheduled = false;
|
||||
|
||||
function findOriginalButton() {
|
||||
const byTestId = document.querySelector('[data-testid="properties-panel-toggle"]');
|
||||
if (byTestId && !byTestId.classList.contains("comfy-sidebar-custom-properties-toggle")) {
|
||||
return byTestId;
|
||||
}
|
||||
|
||||
const icons = document.querySelectorAll('[class*="lucide--panel-right"], [class*="lucide--panel-left"], [class*="lucide--panel-bottom"]');
|
||||
for (const icon of icons) {
|
||||
const btn = icon.closest('button, [role="button"], .comfyui-menu-item, .p-button');
|
||||
|
||||
@@ -115,6 +115,12 @@ const concludeRun = async (pid, statusStr) => {
|
||||
export function setupApiListeners() {
|
||||
api.addEventListener("status", syncQueue);
|
||||
|
||||
api.addEventListener("reconnected", async () => {
|
||||
console.log("Comfy Sidebar: Server reconnected, syncing state and history");
|
||||
await initSessionAndHistory();
|
||||
await syncQueue();
|
||||
});
|
||||
|
||||
api.addEventListener("execution_start", (e) => {
|
||||
if (app.ui.settings.getSettingValue("Comfy Sidebar.Auto Clear Interrupted") ?? false) {
|
||||
const toDelete = [];
|
||||
|
||||
+111
-26
@@ -6,6 +6,8 @@ import { setupApiListeners, initSessionAndHistory, syncQueue, setUIDependencies
|
||||
import { applyClassicLayout, setupPropertiesPanelToggleFix, syncClassicLayout, syncStockHistoryAndProgressSettings } from "./layout.js";
|
||||
|
||||
let isInitialized = false;
|
||||
let activeKeydownHandler = null;
|
||||
let nodeDOMObserver = null;
|
||||
|
||||
// Helper to scan node container for the exact text element rendering the title (Vue Mode)
|
||||
function findHeaderByText(parent, text) {
|
||||
@@ -24,7 +26,12 @@ function findHeaderByText(parent, text) {
|
||||
}
|
||||
|
||||
function syncNodeVueBadge(node, isIgnored) {
|
||||
const nodeEl = document.querySelector(`.comfy-node[data-node-id="${node.id}"], [data-node-id="${node.id}"]`);
|
||||
// Robust multi-selector search for Vue node containers across different frontend versions
|
||||
const nodeEl = document.querySelector(
|
||||
`.comfy-node[data-node-id="${node.id}"], ` +
|
||||
`[data-node-id="${node.id}"], ` +
|
||||
`[data-id="${node.id}"]`
|
||||
);
|
||||
if (!nodeEl) return;
|
||||
|
||||
const titleTextNode = findHeaderByText(nodeEl, node.title || node.type);
|
||||
@@ -92,6 +99,40 @@ export function syncAllNodeBadges() {
|
||||
});
|
||||
}
|
||||
|
||||
function setupVueNodeObserver() {
|
||||
if (nodeDOMObserver) nodeDOMObserver.disconnect();
|
||||
|
||||
let timeoutId = null;
|
||||
nodeDOMObserver = new MutationObserver((mutations) => {
|
||||
let hasNodeMutation = false;
|
||||
for (const m of mutations) {
|
||||
for (const node of m.addedNodes) {
|
||||
if (node.nodeType === 1) {
|
||||
if (node.hasAttribute?.("data-node-id") ||
|
||||
node.classList?.contains("comfy-node") ||
|
||||
node.querySelector?.('[data-node-id], .comfy-node')) {
|
||||
hasNodeMutation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasNodeMutation) break;
|
||||
}
|
||||
|
||||
if (hasNodeMutation) {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
syncAllNodeBadges();
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
|
||||
nodeDOMObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
function toggleIgnoreActiveNode() {
|
||||
const canvas = app.canvas;
|
||||
if (!canvas) return;
|
||||
@@ -188,6 +229,33 @@ app.registerExtension({
|
||||
});
|
||||
},
|
||||
|
||||
commands: [
|
||||
{
|
||||
id: "ComfySidebar.ToggleSidebar",
|
||||
label: "Toggle Comfy Queue Sidebar",
|
||||
function: () => {
|
||||
const ourBtn = findOurSidebarButton();
|
||||
if (ourBtn) ourBtn.click();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "ComfySidebar.ToggleIgnoreNode",
|
||||
label: "Toggle Ignore Selected Node(s) in Queue",
|
||||
function: () => toggleIgnoreActiveNode()
|
||||
}
|
||||
],
|
||||
|
||||
nodeCreated(node) {
|
||||
if (node && node.properties?.ignoreInQueue) {
|
||||
node.boxcolor = "#ff3333";
|
||||
syncNodeVueBadge(node, true);
|
||||
}
|
||||
},
|
||||
|
||||
afterConfigureGraph() {
|
||||
requestAnimationFrame(() => syncAllNodeBadges());
|
||||
},
|
||||
|
||||
async setup() {
|
||||
if (!app.extensionManager || !app.extensionManager.registerSidebarTab) return;
|
||||
|
||||
@@ -215,41 +283,42 @@ app.registerExtension({
|
||||
|
||||
await initSessionAndHistory();
|
||||
|
||||
// Keyboard shortcuts listener
|
||||
document.addEventListener("keydown", (e) => {
|
||||
// Watch for Vue DOM node mounting events (e.g. LiteGraph ↔ Vue mode switches)
|
||||
setupVueNodeObserver();
|
||||
|
||||
// Remove any existing keydown listener from previous hot-reload
|
||||
if (activeKeydownHandler) {
|
||||
document.removeEventListener("keydown", activeKeydownHandler, true);
|
||||
activeKeydownHandler = null;
|
||||
}
|
||||
|
||||
// Clean, direct keyboard listener
|
||||
activeKeydownHandler = (e) => {
|
||||
const activeEl = document.activeElement;
|
||||
if (activeEl && (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA" || activeEl.isContentEditable || activeEl.tagName === "SELECT")) return;
|
||||
if (activeEl && (
|
||||
activeEl.tagName === "INPUT" ||
|
||||
activeEl.tagName === "TEXTAREA" ||
|
||||
activeEl.isContentEditable ||
|
||||
activeEl.tagName === "SELECT"
|
||||
)) return;
|
||||
|
||||
// Toggle sidebar with Q
|
||||
if (e.key.toLowerCase() === "q" && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const ourBtn = findOurSidebarButton();
|
||||
if (ourBtn) ourBtn.click();
|
||||
}
|
||||
|
||||
if (e.key.toLowerCase() === "q" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
// Toggle ignore node with Ctrl+Q or Cmd+Q (Mac)
|
||||
if (e.key.toLowerCase() === "q" && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleIgnoreActiveNode();
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Event hooks for node additions and workflow loading (eliminates continuous polling loops)
|
||||
if (app.graph) {
|
||||
const originalConfigure = app.graph.configure;
|
||||
app.graph.configure = function() {
|
||||
const res = originalConfigure ? originalConfigure.apply(this, arguments) : undefined;
|
||||
requestAnimationFrame(() => syncAllNodeBadges());
|
||||
return res;
|
||||
};
|
||||
};
|
||||
|
||||
const originalOnNodeAdded = app.graph.onNodeAdded;
|
||||
app.graph.onNodeAdded = function(node) {
|
||||
if (originalOnNodeAdded) originalOnNodeAdded.apply(this, arguments);
|
||||
if (node && node.properties?.ignoreInQueue) {
|
||||
node.boxcolor = "#ff3333";
|
||||
syncNodeVueBadge(node, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
document.addEventListener("keydown", activeKeydownHandler, true);
|
||||
|
||||
// Apply sidebar override when initializing
|
||||
applySidebarOverride();
|
||||
@@ -262,5 +331,21 @@ app.registerExtension({
|
||||
type: "custom",
|
||||
render: (el) => { el.appendChild(sidebarContainer); }
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (nodeDOMObserver) {
|
||||
nodeDOMObserver.disconnect();
|
||||
nodeDOMObserver = null;
|
||||
}
|
||||
|
||||
if (activeKeydownHandler) {
|
||||
document.removeEventListener("keydown", activeKeydownHandler, true);
|
||||
activeKeydownHandler = null;
|
||||
}
|
||||
|
||||
if (app.extensionManager?.unregisterSidebarTab) {
|
||||
app.extensionManager.unregisterSidebarTab("classic-comfy-sidebar");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -97,6 +97,63 @@ export function injectStyles() {
|
||||
color: #ffffff !important;
|
||||
box-shadow: 0 0 8px rgba(220, 53, 69, 0.6) !important;
|
||||
}
|
||||
|
||||
/* Header Icons & Header Action Buttons */
|
||||
.comfy-sidebar-header-btn {
|
||||
background: transparent;
|
||||
color: var(--desc-color, #aaa);
|
||||
border: 1px solid var(--border-color, #555);
|
||||
border-radius: 3px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.comfy-sidebar-header-btn:hover {
|
||||
border-color: var(--fg-color, #eee);
|
||||
color: var(--fg-color, #eee);
|
||||
}
|
||||
|
||||
.comfy-sidebar-icon-btn {
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.comfy-sidebar-icon-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Scroll To Top Floating Button */
|
||||
.comfy-sidebar-scroll-top-btn {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
color: #eee;
|
||||
border: 1px solid var(--border-color, #555);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.6);
|
||||
transition: all 0.15s ease-in-out;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.comfy-sidebar-scroll-top-btn:hover {
|
||||
opacity: 1;
|
||||
background: var(--p-primary-color, #3b82f6);
|
||||
color: #fff;
|
||||
border-color: var(--p-primary-color, #3b82f6);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
@@ -162,11 +162,8 @@ export function setupSidebarUI() {
|
||||
Object.assign(titleGroup.style, { display: "flex", alignItems: "center", gap: "8px" });
|
||||
|
||||
const searchIcon = document.createElement("span");
|
||||
searchIcon.className = "pi pi-search";
|
||||
searchIcon.className = "pi pi-search comfy-sidebar-icon-btn";
|
||||
searchIcon.title = "Search History";
|
||||
Object.assign(searchIcon.style, { cursor: "pointer", fontSize: "13px", opacity: "0.6", transition: "opacity 0.15s ease-in-out" });
|
||||
searchIcon.onmouseenter = () => searchIcon.style.opacity = "1";
|
||||
searchIcon.onmouseleave = () => searchIcon.style.opacity = "0.6";
|
||||
|
||||
const title = document.createElement("h3");
|
||||
title.textContent = "Queue";
|
||||
@@ -181,12 +178,8 @@ export function setupSidebarUI() {
|
||||
|
||||
const createActionBtn = (iconClass, tooltip, hoverColor, onClickFn) => {
|
||||
const btn = document.createElement("button");
|
||||
btn.className = iconClass;
|
||||
btn.className = `${iconClass} comfy-sidebar-header-btn`;
|
||||
btn.title = tooltip;
|
||||
Object.assign(btn.style, {
|
||||
background: "transparent", color: "var(--desc-color, #aaa)", border: "1px solid var(--border-color, #555)",
|
||||
borderRadius: "3px", padding: "4px 8px", cursor: "pointer", fontSize: "13px", transition: "all 0.15s ease-in-out"
|
||||
});
|
||||
|
||||
let timeout = null, isPending = false;
|
||||
const reset = () => {
|
||||
@@ -195,10 +188,7 @@ export function setupSidebarUI() {
|
||||
if (timeout) { clearTimeout(timeout); timeout = null; }
|
||||
};
|
||||
|
||||
btn.onmouseenter = () => { if (!isPending) { btn.style.borderColor = "var(--fg-color, #eee)"; btn.style.color = "var(--fg-color, #eee)"; } };
|
||||
btn.onmouseleave = () => { if (!isPending) { btn.style.borderColor = "var(--border-color, #555)"; btn.style.color = "var(--desc-color, #aaa)"; } };
|
||||
|
||||
btn.onclick = async (ev) => {
|
||||
btn.addEventListener("click", async (ev) => {
|
||||
ev.stopPropagation();
|
||||
if (!isPending) {
|
||||
isPending = true;
|
||||
@@ -208,7 +198,7 @@ export function setupSidebarUI() {
|
||||
reset();
|
||||
await onClickFn();
|
||||
}
|
||||
};
|
||||
});
|
||||
return btn;
|
||||
};
|
||||
|
||||
@@ -251,59 +241,38 @@ export function setupSidebarUI() {
|
||||
Object.assign(searchInput, { type: "text", placeholder: "Filter by text, images, nodes..." });
|
||||
Object.assign(searchInput.style, { flex: "1", background: "transparent", border: "none", outline: "none", color: "var(--comfy-input-color, var(--fg-color, #eee))", fontSize: "11px", padding: "0" });
|
||||
const clearSearchBtn = document.createElement("span");
|
||||
clearSearchBtn.className = "pi pi-times";
|
||||
clearSearchBtn.className = "pi pi-times comfy-sidebar-icon-btn";
|
||||
clearSearchBtn.title = "Clear & Close Search";
|
||||
Object.assign(clearSearchBtn.style, { cursor: "pointer", fontSize: "11px", opacity: "0.6", marginLeft: "6px", transition: "opacity 0.15s ease" });
|
||||
clearSearchBtn.onmouseenter = () => clearSearchBtn.style.opacity = "1"; clearSearchBtn.onmouseleave = () => clearSearchBtn.style.opacity = "0.6";
|
||||
Object.assign(clearSearchBtn.style, { marginLeft: "6px" });
|
||||
|
||||
searchContainer.appendChild(searchInputIcon); searchContainer.appendChild(searchInput); searchContainer.appendChild(clearSearchBtn);
|
||||
header.appendChild(standardHeader); header.appendChild(searchContainer); State.sidebarContainer.appendChild(header);
|
||||
|
||||
searchIcon.onclick = (e) => { e.stopPropagation(); standardHeader.style.display = "none"; searchContainer.style.display = "flex"; searchInput.focus(); };
|
||||
searchIcon.addEventListener("click", (e) => { e.stopPropagation(); standardHeader.style.display = "none"; searchContainer.style.display = "flex"; searchInput.focus(); });
|
||||
const closeSearch = () => { searchInput.value = ""; State.currentSearchQuery = ""; searchContainer.style.display = "none"; standardHeader.style.display = "flex"; renderDOM(); };
|
||||
clearSearchBtn.onclick = (e) => { e.stopPropagation(); closeSearch(); };
|
||||
searchInput.onkeydown = (e) => { if (e.key === "Escape") closeSearch(); };
|
||||
searchInput.oninput = () => { State.currentSearchQuery = searchInput.value.trim(); renderDOM(); };
|
||||
clearSearchBtn.addEventListener("click", (e) => { e.stopPropagation(); closeSearch(); });
|
||||
searchInput.addEventListener("keydown", (e) => { if (e.key === "Escape") closeSearch(); });
|
||||
searchInput.addEventListener("input", () => { State.currentSearchQuery = searchInput.value.trim(); renderDOM(); });
|
||||
|
||||
State.cardStack = document.createElement("div");
|
||||
Object.assign(State.cardStack.style, { flex: "1", overflowY: "visible", display: "block", paddingBottom: "28px" });
|
||||
State.sidebarContainer.appendChild(State.cardStack);
|
||||
|
||||
scrollToTopBtnEl = document.createElement("button");
|
||||
scrollToTopBtnEl.className = "pi pi-chevron-up";
|
||||
scrollToTopBtnEl.className = "pi pi-chevron-up comfy-sidebar-scroll-top-btn";
|
||||
scrollToTopBtnEl.title = "Scroll to Top";
|
||||
Object.assign(scrollToTopBtnEl.style, {
|
||||
position: "absolute", bottom: "12px", right: "12px", width: "32px", height: "32px",
|
||||
borderRadius: "50%", background: "rgba(30, 30, 30, 0.9)", color: "#eee",
|
||||
border: "1px solid var(--border-color, #555)", cursor: "pointer", fontSize: "13px",
|
||||
display: "none", alignItems: "center", justifyContent: "center", zIndex: "999",
|
||||
boxShadow: "0 4px 12px rgba(0,0,0,0.6)", transition: "all 0.15s ease-in-out", opacity: "0.85"
|
||||
});
|
||||
|
||||
scrollToTopBtnEl.onmouseenter = () => {
|
||||
scrollToTopBtnEl.style.opacity = "1";
|
||||
scrollToTopBtnEl.style.background = "var(--p-primary-color, #3b82f6)";
|
||||
scrollToTopBtnEl.style.color = "#fff";
|
||||
scrollToTopBtnEl.style.borderColor = "var(--p-primary-color, #3b82f6)";
|
||||
};
|
||||
scrollToTopBtnEl.onmouseleave = () => {
|
||||
scrollToTopBtnEl.style.opacity = "0.85";
|
||||
scrollToTopBtnEl.style.background = "rgba(30, 30, 30, 0.9)";
|
||||
scrollToTopBtnEl.style.color = "#eee";
|
||||
scrollToTopBtnEl.style.borderColor = "var(--border-color, #555)";
|
||||
};
|
||||
|
||||
scrollToTopBtnEl.onclick = (e) => {
|
||||
scrollToTopBtnEl.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const scrollEl = getScrollContainer();
|
||||
if (scrollEl) {
|
||||
scrollEl.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
State.sidebarContainer.appendChild(scrollToTopBtnEl);
|
||||
|
||||
State.sidebarContainer.onclick = (e) => {
|
||||
State.sidebarContainer.addEventListener("click", (e) => {
|
||||
if (State.activeSubmenuPromptId || State.activeSubmenuBatchImages) {
|
||||
if (e.target.closest('img, video, .comfy-sidebar-card-timer, .pi-times, .comfy-sidebar-left-hover-btn, .comfy-sidebar-queue-cancel-btn, button, span')) return;
|
||||
State.activeSubmenuPromptId = null;
|
||||
@@ -313,7 +282,7 @@ export function setupSidebarUI() {
|
||||
document.removeEventListener("click", handleGlobalClick, true);
|
||||
globalClickRegistered = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
new ResizeObserver((entries) => {
|
||||
const threshold = app.ui.settings.getSettingValue("Comfy Sidebar.Grid Columns Threshold") ?? 350;
|
||||
@@ -471,7 +440,7 @@ function renderCardImages(cardObj, state, keepAspect) {
|
||||
showFullscreenPreview([src], ev.shiftKey);
|
||||
};
|
||||
|
||||
// Auto-prune card if file was deleted by startup temp cleanup script
|
||||
// Auto-prune card if file was deleted
|
||||
mediaEl.onerror = () => {
|
||||
if (src && !src.startsWith("blob:")) {
|
||||
deletePromptState(state.pid);
|
||||
|
||||
Reference in New Issue
Block a user