From d2f0ca39de7c7f2e4b5ecb65003095a996a2cfea Mon Sep 17 00:00:00 2001
From: awsr <43862868+awsr@users.noreply.github.com>
Date: Fri, 30 Jan 2026 22:53:15 -0800
Subject: [PATCH 1/2] Update custom elements
- Update known element when setting selected folder.
- Prevent re-trigger of `connectedCallback` when node is just being moved.
---
javascript/gallery.js | 48 ++++++++++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 17 deletions(-)
diff --git a/javascript/gallery.js b/javascript/gallery.js
index 5791990c3..bc8d6e59c 100644
--- a/javascript/gallery.js
+++ b/javascript/gallery.js
@@ -311,6 +311,8 @@ class SimpleFunctionQueue {
// HTML Elements
class GalleryFolder extends HTMLElement {
+ static folders = new Set();
+
constructor(folder) {
super();
// Support both old format (string) and new format (object with path and label)
@@ -324,21 +326,35 @@ class GalleryFolder extends HTMLElement {
this.style.overflowX = 'hidden';
this.shadow = this.attachShadow({ mode: 'open' });
this.shadow.adoptedStyleSheets = [folderStylesheet];
+
+ this.div = document.createElement('div');
}
connectedCallback() {
- const div = document.createElement('div');
- div.className = 'gallery-folder';
- div.innerHTML = `\uf03e ${this.label}`;
- div.title = this.name; // Show full path on hover
- div.addEventListener('click', () => {
- for (const folder of el.folders.children) {
- if (folder.name === this.name) folder.shadow.firstElementChild.classList.add('gallery-folder-selected');
- else folder.shadow.firstElementChild.classList.remove('gallery-folder-selected');
+ if (GalleryFolder.folders.has(this)) return; // Element is just being moved
+
+ this.div.className = 'gallery-folder';
+ this.div.innerHTML = `\uf03e ${this.label}`;
+ this.div.title = this.name; // Show full path on hover
+ this.div.addEventListener('click', () => { this.updateSelected(); }); // Ensures 'this' isn't the div in the called method
+ this.div.addEventListener('click', fetchFilesWS); // eslint-disable-line no-use-before-define
+ this.shadow.appendChild(this.div);
+ GalleryFolder.folders.add(this);
+ }
+
+ async disconnectedCallback() {
+ await Promise.resolve(); // Wait for other microtasks (such as element moving)
+ if (this.isConnected) return;
+ GalleryFolder.folders.delete(this);
+ }
+
+ updateSelected() {
+ this.div.classList.add('gallery-folder-selected');
+ for (const folder of GalleryFolder.folders) {
+ if (folder !== this) {
+ folder.div.classList.remove('gallery-folder-selected');
}
- });
- div.addEventListener('click', fetchFilesWS); // eslint-disable-line no-use-before-define
- this.shadow.appendChild(div);
+ }
}
}
@@ -508,12 +524,13 @@ class GalleryFile extends HTMLElement {
this.src = `${this.folder}/${this.name}`;
this.shadow = this.attachShadow({ mode: 'open' });
this.shadow.adoptedStyleSheets = [fileStylesheet];
+
+ this.firstRun = true;
}
async connectedCallback() {
- if (this.shadow.children.length > 0) {
- return;
- }
+ if (!this.firstRun) return; // Element is just being moved
+ this.firstRun = false;
// Check separator state early to hide the element immediately
const dir = this.name.match(/(.*)[/\\]/);
@@ -596,9 +613,6 @@ class GalleryFile extends HTMLElement {
setGallerySelectionByElement(this, { send: true });
};
img.title = `Folder: ${this.folder}\nFile: ${this.name}\nSize: ${this.size.toLocaleString()} bytes\nModified: ${this.mtime.toLocaleString()}`;
- if (this.shadow.children.length > 0) {
- return; // avoid double-adding
- }
this.title = img.title;
// Final visibility check based on search term.
From a3ad11ef9324019c08e60e4958f13b03d176653c Mon Sep 17 00:00:00 2001
From: awsr <43862868+awsr@users.noreply.github.com>
Date: Sat, 31 Jan 2026 22:01:11 -0800
Subject: [PATCH 2/2] Reorganize functions
---
javascript/gallery.js | 254 +++++++++++++++++++++---------------------
1 file changed, 127 insertions(+), 127 deletions(-)
diff --git a/javascript/gallery.js b/javascript/gallery.js
index bc8d6e59c..9b2514cdc 100644
--- a/javascript/gallery.js
+++ b/javascript/gallery.js
@@ -358,133 +358,6 @@ class GalleryFolder extends HTMLElement {
}
}
-async function createThumb(img) {
- const height = opts.extra_networks_card_size;
- const width = opts.browser_fixed_width ? opts.extra_networks_card_size : 0;
- const canvas = document.createElement('canvas');
- const scaleY = height / img.height;
- const scaleX = width > 0 ? width / img.width : scaleY;
- const scale = Math.min(scaleX, scaleY);
- const scaledWidth = img.width * scale;
- const scaledHeight = img.height * scale;
- canvas.width = scaledWidth;
- canvas.height = scaledHeight;
- const ctx = canvas.getContext('2d');
- ctx.drawImage(img, 0, 0, scaledWidth, scaledHeight);
- const dataURL = canvas.toDataURL('image/jpeg', 0.5);
- return dataURL;
-}
-
-async function handleSeparator(separator) {
- separator.classList.toggle('gallery-separator-hidden');
- const nowHidden = separator.classList.contains('gallery-separator-hidden');
-
- // Store the state (true = open, false = closed)
- separatorStates.set(separator.title, !nowHidden);
-
- // Update arrow and count
- const arrow = separator.querySelector('.gallery-separator-arrow');
- arrow.style.transform = nowHidden ? 'rotate(0deg)' : 'rotate(90deg)';
-
- const all = Array.from(el.files.children);
- for (const f of all) {
- if (!f.name) continue; // Skip separators
-
- // Check if file belongs to this exact directory
- const fileDir = f.name.match(/(.*)[/\\]/);
- const fileDirPath = fileDir ? fileDir[1] : '';
-
- if (separator.title.length > 0 && fileDirPath === separator.title) {
- f.style.display = nowHidden ? 'none' : 'unset';
- }
- }
- // Note: Count is not updated here on manual toggle, as it reflects the total.
- // If I end up implementing it, the search function will handle dynamic count updates.
-}
-
-async function addSeparators() {
- document.querySelectorAll('.gallery-separator').forEach((node) => { el.files.removeChild(node); });
- const all = Array.from(el.files.children);
- let lastDir;
-
- // Count root files (files without a directory path)
- const hasRootFiles = all.some((f) => f.name && !f.name.match(/[/\\]/));
- // Only auto-open first separator if there are no root files to display
- let isFirstSeparator = !hasRootFiles;
-
- // First pass: create separators
- for (const f of all) {
- let dir = f.name?.match(/(.*)[/\\]/);
- if (!dir) dir = '';
- else dir = dir[1];
- if (dir !== lastDir) {
- lastDir = dir;
- if (dir.length > 0) {
- // Count files in this directory
- let fileCount = 0;
- for (const file of all) {
- if (!file.name) continue;
- const fileDir = file.name.match(/(.*)[/\\]/);
- const fileDirPath = fileDir ? fileDir[1] : '';
- if (fileDirPath === dir) fileCount++;
- }
-
- const sep = document.createElement('div');
- sep.className = 'gallery-separator';
- sep.title = dir;
-
- // Default to open for the first separator if no state is saved, otherwise closed.
- const isOpen = separatorStates.has(dir) ? separatorStates.get(dir) : isFirstSeparator;
- separatorStates.set(dir, isOpen); // Ensure it's in the map
- if (isFirstSeparator) isFirstSeparator = false; // Subsequent separators will default to closed
-
- if (!isOpen) {
- sep.classList.add('gallery-separator-hidden');
- }
-
- // Create arrow span
- const arrow = document.createElement('span');
- arrow.className = 'gallery-separator-arrow';
- arrow.textContent = '▶';
- arrow.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
-
- // Create directory name span
- const dirName = document.createElement('span');
- dirName.className = 'gallery-separator-name';
- dirName.textContent = dir;
- dirName.title = dir; // Show full path on hover
-
- // Create count span
- const count = document.createElement('span');
- count.className = 'gallery-separator-count';
- count.textContent = `${fileCount} files`;
- sep.dataset.totalFiles = fileCount; // Store total count for search filtering
-
- sep.appendChild(arrow);
- sep.appendChild(dirName);
- sep.appendChild(count);
-
- sep.onclick = () => handleSeparator(sep);
- el.files.insertBefore(sep, f);
- }
- }
- }
-
- // Second pass: hide files in closed directories
- for (const f of all) {
- if (!f.name) continue; // Skip separators
-
- const dir = f.name.match(/(.*)[/\\]/);
- if (dir && dir[1]) {
- const dirPath = dir[1];
- const isOpen = separatorStates.get(dirPath);
- if (isOpen === false) {
- f.style.display = 'none';
- }
- }
- }
-}
-
async function delayFetchThumb(fn, signal) {
await awaitForOutstanding(16, signal);
try {
@@ -625,6 +498,133 @@ class GalleryFile extends HTMLElement {
}
}
+async function createThumb(img) {
+ const height = opts.extra_networks_card_size;
+ const width = opts.browser_fixed_width ? opts.extra_networks_card_size : 0;
+ const canvas = document.createElement('canvas');
+ const scaleY = height / img.height;
+ const scaleX = width > 0 ? width / img.width : scaleY;
+ const scale = Math.min(scaleX, scaleY);
+ const scaledWidth = img.width * scale;
+ const scaledHeight = img.height * scale;
+ canvas.width = scaledWidth;
+ canvas.height = scaledHeight;
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0, scaledWidth, scaledHeight);
+ const dataURL = canvas.toDataURL('image/jpeg', 0.5);
+ return dataURL;
+}
+
+async function handleSeparator(separator) {
+ separator.classList.toggle('gallery-separator-hidden');
+ const nowHidden = separator.classList.contains('gallery-separator-hidden');
+
+ // Store the state (true = open, false = closed)
+ separatorStates.set(separator.title, !nowHidden);
+
+ // Update arrow and count
+ const arrow = separator.querySelector('.gallery-separator-arrow');
+ arrow.style.transform = nowHidden ? 'rotate(0deg)' : 'rotate(90deg)';
+
+ const all = Array.from(el.files.children);
+ for (const f of all) {
+ if (!f.name) continue; // Skip separators
+
+ // Check if file belongs to this exact directory
+ const fileDir = f.name.match(/(.*)[/\\]/);
+ const fileDirPath = fileDir ? fileDir[1] : '';
+
+ if (separator.title.length > 0 && fileDirPath === separator.title) {
+ f.style.display = nowHidden ? 'none' : 'unset';
+ }
+ }
+ // Note: Count is not updated here on manual toggle, as it reflects the total.
+ // If I end up implementing it, the search function will handle dynamic count updates.
+}
+
+async function addSeparators() {
+ document.querySelectorAll('.gallery-separator').forEach((node) => { el.files.removeChild(node); });
+ const all = Array.from(el.files.children);
+ let lastDir;
+
+ // Count root files (files without a directory path)
+ const hasRootFiles = all.some((f) => f.name && !f.name.match(/[/\\]/));
+ // Only auto-open first separator if there are no root files to display
+ let isFirstSeparator = !hasRootFiles;
+
+ // First pass: create separators
+ for (const f of all) {
+ let dir = f.name?.match(/(.*)[/\\]/);
+ if (!dir) dir = '';
+ else dir = dir[1];
+ if (dir !== lastDir) {
+ lastDir = dir;
+ if (dir.length > 0) {
+ // Count files in this directory
+ let fileCount = 0;
+ for (const file of all) {
+ if (!file.name) continue;
+ const fileDir = file.name.match(/(.*)[/\\]/);
+ const fileDirPath = fileDir ? fileDir[1] : '';
+ if (fileDirPath === dir) fileCount++;
+ }
+
+ const sep = document.createElement('div');
+ sep.className = 'gallery-separator';
+ sep.title = dir;
+
+ // Default to open for the first separator if no state is saved, otherwise closed.
+ const isOpen = separatorStates.has(dir) ? separatorStates.get(dir) : isFirstSeparator;
+ separatorStates.set(dir, isOpen); // Ensure it's in the map
+ if (isFirstSeparator) isFirstSeparator = false; // Subsequent separators will default to closed
+
+ if (!isOpen) {
+ sep.classList.add('gallery-separator-hidden');
+ }
+
+ // Create arrow span
+ const arrow = document.createElement('span');
+ arrow.className = 'gallery-separator-arrow';
+ arrow.textContent = '▶';
+ arrow.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
+
+ // Create directory name span
+ const dirName = document.createElement('span');
+ dirName.className = 'gallery-separator-name';
+ dirName.textContent = dir;
+ dirName.title = dir; // Show full path on hover
+
+ // Create count span
+ const count = document.createElement('span');
+ count.className = 'gallery-separator-count';
+ count.textContent = `${fileCount} files`;
+ sep.dataset.totalFiles = fileCount; // Store total count for search filtering
+
+ sep.appendChild(arrow);
+ sep.appendChild(dirName);
+ sep.appendChild(count);
+
+ sep.onclick = () => handleSeparator(sep);
+ el.files.insertBefore(sep, f);
+ }
+ }
+ }
+
+ // Second pass: hide files in closed directories
+ for (const f of all) {
+ if (!f.name) continue; // Skip separators
+
+ const dir = f.name.match(/(.*)[/\\]/);
+ if (dir && dir[1]) {
+ const dirPath = dir[1];
+ const isOpen = separatorStates.get(dirPath);
+ if (isOpen === false) {
+ f.style.display = 'none';
+ }
+ }
+ }
+}
+
// methods
const gallerySendImage = (_images) => [currentImage]; // invoked by gradio button