Fix: Unified swappable display_mode (Scrollable vs Show All) with responsive auto-resizing on both LoRA and Image Loaders

This commit is contained in:
KOFiblto
2026-08-18 18:29:57 +02:00
parent b99d14916e
commit 8a3d4d2e48
2 changed files with 154 additions and 85 deletions
+94 -46
View File
@@ -46,15 +46,36 @@ visualStyles.textContent = `
border-radius: 8px;
padding: 8px;
box-sizing: border-box;
height: 100%;
min-height: 0;
max-height: 100%;
overflow-y: hidden;
}
.lora-visual-container.vue-mode {
height: 400px;
min-height: 200px;
resize: vertical;
.lora-grid-container {
flex: 1 1 0px;
min-height: 0;
max-height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
box-sizing: border-box;
padding-right: 4px;
}
.lora-visual-container.litegraph-mode {
height: 100%;
.lora-grid-container::-webkit-scrollbar {
width: 6px;
}
.lora-grid-container::-webkit-scrollbar-track {
background: rgba(0,0,0,0.1);
border-radius: 4px;
}
.lora-grid-container::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.15);
border-radius: 4px;
}
.lora-grid-container::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.3);
}
.lora-visual-container.grid-layout {
display: grid;
@@ -64,20 +85,6 @@ visualStyles.textContent = `
display: flex;
flex-direction: column;
}
.lora-visual-container::-webkit-scrollbar {
width: 6px;
}
.lora-visual-container::-webkit-scrollbar-track {
background: rgba(0,0,0,0.1);
border-radius: 4px;
}
.lora-visual-container::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.15);
border-radius: 4px;
}
.lora-visual-container::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.3);
}
.lora-none-container {
width: 100%;
display: grid;
@@ -838,26 +845,65 @@ app.registerExtension({
const initialZoom = localStorage.getItem("comfy_lora_picker_zoom") || "80";
viewContainer.style.setProperty("--lora-tile-size", `${initialZoom}px`);
const gridContainer = document.createElement("div");
gridContainer.className = "lora-grid-container";
// Isolate mouse wheel scroll events from triggering canvas zooming/panning
viewContainer.addEventListener("wheel", (e) => {
e.stopPropagation();
});
let userSavedHeight = 420;
const applyDisplayMode = (mode) => {
const isShowAll = mode === "Show All";
if (isShowAll) {
viewContainer.style.setProperty("height", "auto", "important");
viewContainer.style.setProperty("max-height", "none", "important");
viewContainer.style.setProperty("overflow-y", "visible", "important");
gridContainer.style.setProperty("height", "auto", "important");
gridContainer.style.setProperty("max-height", "none", "important");
gridContainer.style.setProperty("overflow-y", "visible", "important");
gridContainer.style.setProperty("flex", "none", "important");
setTimeout(() => {
const contentH = gridContainer.scrollHeight || 0;
const neededHeight = contentH + 160;
node.setSize([node.size[0], Math.max(360, neededHeight)]);
if (app.graph) app.graph.setDirtyCanvas(true, true);
}, 50);
} else {
viewContainer.style.removeProperty("height");
viewContainer.style.removeProperty("max-height");
viewContainer.style.setProperty("overflow-y", "hidden", "important");
gridContainer.style.removeProperty("height");
gridContainer.style.removeProperty("max-height");
gridContainer.style.setProperty("overflow-y", "auto", "important");
gridContainer.style.setProperty("flex", "1 1 0px", "important");
const restoredH = node.userCustomHeight || userSavedHeight || 420;
if (viewContainer.style) {
viewContainer.style.height = `${restoredH - 80}px`;
}
node.setSize([node.size[0], Math.max(360, restoredH)]);
if (app.graph) app.graph.setDirtyCanvas(true, true);
}
};
node.computeSize = function() {
const displayModeWidget = node.widgets ? node.widgets.find(w => w.name === "display_mode") : null;
if (displayModeWidget && displayModeWidget.value === "Show All" && viewContainer) {
return [node.size[0], Math.max(360, viewContainer.scrollHeight + 190)];
if (displayModeWidget && displayModeWidget.value === "Show All" && gridContainer) {
return [node.size[0], Math.max(360, (gridContainer.scrollHeight || 0) + 160)];
}
return [node.size[0], Math.max(360, node.size[1])];
};
const updateNodeSize = () => {
const displayModeWidget = node.widgets ? node.widgets.find(w => w.name === "display_mode") : null;
if (displayModeWidget && displayModeWidget.value === "Show All") {
const neededHeight = viewContainer.scrollHeight + 190;
node.setSize([node.size[0], Math.max(360, neededHeight)]);
if (app.graph) app.graph.setDirtyCanvas(true, true);
}
const mode = displayModeWidget ? displayModeWidget.value : "Scrollable";
applyDisplayMode(mode);
};
// --- Control Bar for Mode Selector ---
@@ -960,6 +1006,7 @@ app.registerExtension({
controlBar.appendChild(scrapeToggleContainer);
viewContainer.appendChild(controlBar);
viewContainer.appendChild(gridContainer);
const domWidget = node.addDOMWidget("lora_visual_picker", "HTML", viewContainer, {
getValue() { return getHiddenWidget().value; },
@@ -971,6 +1018,20 @@ app.registerExtension({
return [node.size[0] - 30, node.size[1] - 190];
};
const onResize = node.onResize;
node.onResize = function(size) {
if (onResize) onResize.apply(this, arguments);
const displayModeWidget = node.widgets ? node.widgets.find(w => w.name === "display_mode") : null;
const isShowAll = displayModeWidget && displayModeWidget.value === "Show All";
if (!isShowAll && size) {
node.userCustomHeight = size[1];
userSavedHeight = size[1];
if (viewContainer && viewContainer.style) {
viewContainer.style.height = `${size[1] - 80}px`;
}
}
};
const zoomWidget = node.addWidget(
"slider",
"tile_size",
@@ -1072,11 +1133,7 @@ app.registerExtension({
if (activeRequest !== currentRequest) return;
const data = await response.json();
// Keep the control bar and clear the rest
const noneContainerExist = viewContainer.querySelector(".lora-none-container");
if (noneContainerExist) noneContainerExist.remove();
const foldersExist = viewContainer.querySelectorAll(".lora-folder-container");
foldersExist.forEach(f => f.remove());
gridContainer.innerHTML = "";
foldersMap = {};
foldersMap["Root"] = [];
@@ -1218,7 +1275,7 @@ app.registerExtension({
observer.unobserve(img);
}
});
}, { root: viewContainer, rootMargin: "100px" });
}, { root: gridContainer, rootMargin: "150px" });
// --- Render folder groups ---
sortedFolders.forEach(subFolder => {
@@ -1380,8 +1437,8 @@ app.registerExtension({
fragment.appendChild(folderContainer);
});
// Append entire DocumentFragment in a single high-performance DOM pass
viewContainer.appendChild(fragment);
// Append entire DocumentFragment to gridContainer
gridContainer.appendChild(fragment);
// Set initial checkboxes state
updateFolderCheckboxes();
@@ -1446,18 +1503,9 @@ app.registerExtension({
const origCallback = displayModeWidget.callback;
displayModeWidget.callback = function(val) {
if (origCallback) origCallback.apply(this, arguments);
if (val === "Show All") {
viewContainer.style.overflowY = "hidden";
} else {
viewContainer.style.overflowY = "auto";
}
setTimeout(() => updateNodeSize(), 50);
applyDisplayMode(val || displayModeWidget.value);
};
if (displayModeWidget.value === "Show All") {
viewContainer.style.overflowY = "hidden";
} else {
viewContainer.style.overflowY = "auto";
}
applyDisplayMode(displayModeWidget.value || "Scrollable");
}
updateVisualGrid();
}, 100);
+60 -39
View File
@@ -14,17 +14,10 @@ visualStyles.textContent = `
border: 1px solid #222;
border-radius: 6px;
box-sizing: border-box;
overflow-y: hidden;
}
.img-visual-container.vue-mode {
height: 400px;
min-height: 200px;
resize: vertical;
}
.img-visual-container.litegraph-mode {
height: 100%;
min-height: 0;
max-height: 100%;
overflow-y: hidden;
}
.img-visual-container::-webkit-scrollbar {
width: 6px;
@@ -299,19 +292,57 @@ app.registerExtension({
node.computeSize = function() {
const displayModeWidget = node.widgets ? node.widgets.find(w => w.name === "display_mode") : null;
if (displayModeWidget && displayModeWidget.value === "Show All" && gridContainer) {
return [node.size[0], Math.max(480, gridContainer.scrollHeight + 140)];
let userSavedHeight = 480;
const applyDisplayMode = (mode) => {
const isShowAll = mode === "Show All";
if (isShowAll) {
viewContainer.style.setProperty("height", "auto", "important");
viewContainer.style.setProperty("max-height", "none", "important");
viewContainer.style.setProperty("overflow-y", "visible", "important");
gridContainer.style.setProperty("height", "auto", "important");
gridContainer.style.setProperty("max-height", "none", "important");
gridContainer.style.setProperty("overflow-y", "visible", "important");
gridContainer.style.setProperty("flex", "none", "important");
setTimeout(() => {
const contentH = gridContainer.scrollHeight || 0;
const neededHeight = contentH + 160;
node.setSize([node.size[0], Math.max(420, neededHeight)]);
if (app.graph) app.graph.setDirtyCanvas(true, true);
}, 50);
} else {
viewContainer.style.removeProperty("height");
viewContainer.style.removeProperty("max-height");
viewContainer.style.setProperty("overflow-y", "hidden", "important");
gridContainer.style.removeProperty("height");
gridContainer.style.removeProperty("max-height");
gridContainer.style.setProperty("overflow-y", "auto", "important");
gridContainer.style.setProperty("flex", "1 1 0px", "important");
const restoredH = node.userCustomHeight || userSavedHeight || 480;
if (viewContainer.style) {
viewContainer.style.height = `${restoredH - 80}px`;
}
node.setSize([node.size[0], Math.max(420, restoredH)]);
if (app.graph) app.graph.setDirtyCanvas(true, true);
}
return [node.size[0], Math.max(480, node.size[1])];
};
node.computeSize = function() {
const displayModeWidget = node.widgets ? node.widgets.find(w => w.name === "display_mode") : null;
if (displayModeWidget && displayModeWidget.value === "Show All" && gridContainer) {
return [node.size[0], Math.max(420, (gridContainer.scrollHeight || 0) + 160)];
}
return [node.size[0], Math.max(420, node.size[1])];
};
const updateNodeSize = () => {
const displayModeWidget = node.widgets ? node.widgets.find(w => w.name === "display_mode") : null;
if (displayModeWidget && displayModeWidget.value === "Show All") {
const neededHeight = gridContainer.scrollHeight + 140;
node.setSize([node.size[0], Math.max(480, neededHeight)]);
if (app.graph) app.graph.setDirtyCanvas(true, true);
}
const mode = displayModeWidget ? displayModeWidget.value : "Scrollable";
applyDisplayMode(mode);
};
const initialZoom = localStorage.getItem("comfy_img_picker_zoom") || "80";
@@ -324,20 +355,19 @@ app.registerExtension({
serialize: false
});
// Detect Vue UI vs LiteGraph
const isVueUI = !!document.querySelector(".lg-node");
if (isVueUI) {
viewContainer.classList.add("vue-mode");
} else {
viewContainer.classList.add("litegraph-mode");
const onResize = node.onResize;
node.onResize = function(size) {
if (onResize) onResize.apply(this, arguments);
if (viewContainer.style) {
const onResize = node.onResize;
node.onResize = function(size) {
if (onResize) onResize.apply(this, arguments);
const displayModeWidget = node.widgets ? node.widgets.find(w => w.name === "display_mode") : null;
const isShowAll = displayModeWidget && displayModeWidget.value === "Show All";
if (!isShowAll && size) {
node.userCustomHeight = size[1];
userSavedHeight = size[1];
if (viewContainer && viewContainer.style) {
viewContainer.style.height = `${size[1] - 80}px`;
}
};
}
}
};
domWidget.computeSize = function() {
return [node.size[0] - 30, node.size[1] - 140];
@@ -594,18 +624,9 @@ app.registerExtension({
const origCallback = displayModeWidget.callback;
displayModeWidget.callback = function(val) {
if (origCallback) origCallback.apply(this, arguments);
if (val === "Show All") {
gridContainer.style.overflowY = "hidden";
} else {
gridContainer.style.overflowY = "auto";
}
setTimeout(() => updateNodeSize(), 50);
applyDisplayMode(val || displayModeWidget.value);
};
if (displayModeWidget.value === "Show All") {
gridContainer.style.overflowY = "hidden";
} else {
gridContainer.style.overflowY = "auto";
}
applyDisplayMode(displayModeWidget.value || "Scrollable");
}
updateImagesList();
}, 100);