From 2f9cf121bc86342ca0c7e7f4c1bc17b18a771a07 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Fri, 21 Nov 2025 04:58:21 -0800 Subject: [PATCH 01/10] Remove old entries from gallery cache Removes excess thumbnail data when the IndexedDB has at least 200 more entries than is loaded by the gallery. --- javascript/gallery.js | 45 +++++++++++++++++++++++++++++++++++++++++-- javascript/indexdb.js | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index da4da5fd3..5fd20e58a 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -6,6 +6,7 @@ let pruneImagesTimer; let outstanding = 0; let lastSort = 0; let lastSortName = 'None'; +let idbIsCleaning = false; // Store separator states for the session const separatorStates = new Map(); const el = { @@ -15,6 +16,7 @@ const el = { status: undefined, btnSend: undefined, }; +const thumbHashes = new Set(); const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; @@ -182,7 +184,7 @@ async function addSeparators() { } async function delayFetchThumb(fn) { - while (outstanding > 16) await new Promise((resolve) => setTimeout(resolve, 50)); // eslint-disable-line no-promise-executor-return + await awaitForIDB(16); outstanding++; const ts = Date.now().toString(); const res = await authFetch(`${window.api}/browser/thumb?file=${encodeURI(fn)}&ts=${ts}`, { priority: 'low' }); @@ -231,6 +233,7 @@ class GalleryFile extends HTMLElement { } this.hash = await getHash(`${this.folder}/${this.name}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define + thumbHashes.add(this.hash); const style = document.createElement('style'); const width = opts.browser_fixed_width ? `${opts.extra_networks_card_size}px` : 'unset'; style.textContent = ` @@ -324,7 +327,11 @@ class GalleryFile extends HTMLElement { // methods -const gallerySendImage = (_images) => [currentImage]; // invoked by gadio button +const gallerySendImage = (_images) => [currentImage]; // invoked by gradio button + +async function awaitForIDB(num = 0) { + while (outstanding > num || idbIsCleaning) await new Promise((resolve) => setTimeout(resolve, 50)); +} async function getHash(str, algo = 'SHA-256') { try { @@ -546,6 +553,36 @@ async function gallerySort(btn) { updateStatusWithSort(`${arr.length.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`); } +async function thumbCacheCleanup() { + if (idbIsCleaning) return; + await awaitForIDB(); + idbIsCleaning = true; + + const t0 = performance.now(); + + const idbSize = await idbGetAllKeys() + .then(keys => keys.length) + .catch(() => 0); + + if (idbSize < thumbHashes.size + 200) { + // Don't run when there aren't many excess entries + idbIsCleaning = false; + return; + } + + idbClean(thumbHashes) + .then(delcount => { + const t1 = performance.now(); + log(`Thumbnail DB cleanup: kept=${thumbHashes.size} deleted=${delcount} time=${Math.floor(t1 - t0)}ms`); + }) + .catch(() => { + log("Thumbnail DB cleanup: Cleanup failed"); + }) + .finally(() => { + idbIsCleaning = false; + }); +} + async function fetchFilesHT(evt) { const t0 = performance.now(); const fragment = document.createDocumentFragment(); @@ -575,9 +612,12 @@ async function fetchFilesHT(evt) { log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(`Folder: ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`); addSeparators(); + thumbCacheCleanup(); } async function fetchFilesWS(evt) { // fetch file-by-file list over websockets + if (idbIsCleaning) return; + thumbHashes.clear(); // Only called here because fetchFilesHT isn't called directly el.files.innerHTML = ''; if (!url) return; if (ws && ws.readyState === WebSocket.OPEN) ws.close(); // abort previous request @@ -626,6 +666,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(`Folder: ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`); addSeparators(); + thumbCacheCleanup(); }; ws.onerror = (event) => { log('gallery ws error', event); diff --git a/javascript/indexdb.js b/javascript/indexdb.js index e6a1c6786..65a18f601 100644 --- a/javascript/indexdb.js +++ b/javascript/indexdb.js @@ -75,6 +75,46 @@ async function put(record) { }); } +async function idbGetAllKeys() { + if (!db) return null; + return new Promise((resolve, reject) => { + const request = db + .transaction("thumbs") + .objectStore("thumbs") + .getAllKeys(); + request.onsuccess = () => resolve(request.result); + request.onerror = (evt) => reject(evt); + }); +} + +async function idbClean(keepSet) { + if (!db) return null; + if (!keepSet instanceof Set) { + console.error("IndexedDB cleaning function must be given a Set() of hashes to keep"); + }; + return new Promise((resolve, reject) => { + let counter = 0; + const request = db + .transaction("thumbs", "readwrite") + .objectStore("thumbs") + .openCursor(); + request.onsuccess = (evt) => { + const cursor = evt.target.result; + if (cursor) { + if (!keepSet.has(cursor.key)) { + cursor.delete(); + counter++; + } + cursor.continue(); + } + else { + resolve(counter); + } + }; + request.onerror = (evt) => reject(evt); + }); +} + window.idbAdd = add; window.idbDel = del; window.idbGet = get; From f2eddd7f7d32d9b2ee5e98cd2c12501e81273199 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Fri, 21 Nov 2025 05:00:19 -0800 Subject: [PATCH 02/10] Changed threshold to 100 entries before cleanup --- javascript/gallery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 5fd20e58a..ffe2697ec 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -564,7 +564,7 @@ async function thumbCacheCleanup() { .then(keys => keys.length) .catch(() => 0); - if (idbSize < thumbHashes.size + 200) { + if (idbSize < thumbHashes.size + 100) { // Don't run when there aren't many excess entries idbIsCleaning = false; return; From 6f15a04fe04e923f075a2876a5607740737779e1 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Fri, 21 Nov 2025 05:29:48 -0800 Subject: [PATCH 03/10] Simpler method for getting key count --- javascript/gallery.js | 3 +-- javascript/indexdb.js | 14 +++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index ffe2697ec..f493e68d9 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -560,8 +560,7 @@ async function thumbCacheCleanup() { const t0 = performance.now(); - const idbSize = await idbGetAllKeys() - .then(keys => keys.length) + const idbSize = await idbCount() .catch(() => 0); if (idbSize < thumbHashes.size + 100) { diff --git a/javascript/indexdb.js b/javascript/indexdb.js index 65a18f601..841e41748 100644 --- a/javascript/indexdb.js +++ b/javascript/indexdb.js @@ -79,7 +79,7 @@ async function idbGetAllKeys() { if (!db) return null; return new Promise((resolve, reject) => { const request = db - .transaction("thumbs") + .transaction("thumbs", "readonly") .objectStore("thumbs") .getAllKeys(); request.onsuccess = () => resolve(request.result); @@ -87,6 +87,18 @@ async function idbGetAllKeys() { }); } +async function idbCount() { + if (!db) return null; + return new Promise((resolve, reject) => { + const request = db + .transaction("thumbs", "readonly") + .objectStore("thumbs") + .count(); + request.onsuccess = () => resolve(request.result); + request.onerror = (evt) => reject(evt); + }); +} + async function idbClean(keepSet) { if (!db) return null; if (!keepSet instanceof Set) { From cfae4a5f1fa671501c301b483abdd4793e016586 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 22 Nov 2025 05:19:15 -0800 Subject: [PATCH 04/10] Show blocking overlay when running cleanup --- javascript/gallery.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/javascript/gallery.js b/javascript/gallery.js index f493e68d9..d502b3635 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -15,7 +15,9 @@ const el = { search: undefined, status: undefined, btnSend: undefined, + overlay: document.createElement("div"), }; +let cleaningOverlayIsReady = false; const thumbHashes = new Set(); const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; @@ -569,6 +571,7 @@ async function thumbCacheCleanup() { return; } + showCleaningMsg(true); idbClean(thumbHashes) .then(delcount => { const t1 = performance.now(); @@ -579,6 +582,7 @@ async function thumbCacheCleanup() { }) .finally(() => { idbIsCleaning = false; + showCleaningMsg(false); }); } @@ -615,6 +619,10 @@ async function fetchFilesHT(evt) { } async function fetchFilesWS(evt) { // fetch file-by-file list over websockets + if (!cleaningOverlayIsReady) { + initCleaningOverlay() // Can't call during initGallery because it'll attach to the wrong component for some reason + .then(() => {cleaningOverlayIsReady = true}); + } if (idbIsCleaning) return; thumbHashes.clear(); // Only called here because fetchFilesHT isn't called directly el.files.innerHTML = ''; @@ -716,6 +724,33 @@ async function monitorGalleries() { } } +async function initCleaningOverlay() { + if (!el.folders) { + return; + } + const busyAnimation = document.createElement("style"); + busyAnimation.textContent = ".idbBusyAnim{width:16px;height:16px;border-radius:50%;display:block;margin:16px;position:relative;background:#ff3d00;color:#fff;box-shadow:-24px 0,24px 0;box-sizing:border-box;animation:2s ease-in-out infinite rotation}@keyframes rotation{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}" + document.head.append(busyAnimation); + + el.folders.parentElement.style.position = "relative"; + + el.overlay = document.createElement("div"); + el.overlay.style.cssText = "position: absolute; height: 100%; width: 100%; background-color: hsl(210 50 20 / 0.8); display: none; align-items: center; justify-content: center;"; + const msg = document.createElement("span"); + msg.style.cssText = "display: block; background-color: hsl(0 0 10); color: white; padding: 12px; border-radius: 8px; margin-left: -30px; margin-right: 30px;"; + msg.innerText = "Running thumbnail cleanup"; + + const anim = document.createElement("span"); + anim.classList.add("idbBusyAnim"); + + el.overlay.append(msg, anim); + el.folders.parentElement.append(el.overlay); +} + +function showCleaningMsg(state) { + el.overlay.style.display = state ? "flex" : "none"; +} + async function initGallery() { // triggered on gradio change to monitor when ui gets sufficiently constructed log('initGallery'); el.folders = gradioApp().getElementById('tab-gallery-folders'); From b476a31e7179fa188236ab02d5e66c28ce553008 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 22 Nov 2025 14:34:12 -0800 Subject: [PATCH 05/10] Update error handling and logging --- javascript/gallery.js | 4 ++-- javascript/indexdb.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index d502b3635..9f7e4c34c 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -577,8 +577,8 @@ async function thumbCacheCleanup() { const t1 = performance.now(); log(`Thumbnail DB cleanup: kept=${thumbHashes.size} deleted=${delcount} time=${Math.floor(t1 - t0)}ms`); }) - .catch(() => { - log("Thumbnail DB cleanup: Cleanup failed"); + .catch((err) => { + error("Thumbnail DB cleanup: Cleanup failed.", err.message); }) .finally(() => { idbIsCleaning = false; diff --git a/javascript/indexdb.js b/javascript/indexdb.js index 841e41748..8ba984903 100644 --- a/javascript/indexdb.js +++ b/javascript/indexdb.js @@ -102,7 +102,7 @@ async function idbCount() { async function idbClean(keepSet) { if (!db) return null; if (!keepSet instanceof Set) { - console.error("IndexedDB cleaning function must be given a Set() of hashes to keep"); + throw new TypeError("IndexedDB cleaning function must be given a Set() of hashes to keep"); }; return new Promise((resolve, reject) => { let counter = 0; From 43de9f98e3119b6e9aa44cbc9ed697de3ed0a10a Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:09:07 -0800 Subject: [PATCH 06/10] Improve overlay function - Rename variables to be more descriptive. - Change run threshold to 500. - Make overlay fully dynamic. --- javascript/gallery.js | 64 ++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 9f7e4c34c..49e6fe080 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -15,10 +15,9 @@ const el = { search: undefined, status: undefined, btnSend: undefined, - overlay: document.createElement("div"), }; let cleaningOverlayIsReady = false; -const thumbHashes = new Set(); +const galleryHashes = new Set(); const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; @@ -235,7 +234,7 @@ class GalleryFile extends HTMLElement { } this.hash = await getHash(`${this.folder}/${this.name}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define - thumbHashes.add(this.hash); + galleryHashes.add(this.hash); const style = document.createElement('style'); const width = opts.browser_fixed_width ? `${opts.extra_networks_card_size}px` : 'unset'; style.textContent = ` @@ -561,28 +560,26 @@ async function thumbCacheCleanup() { idbIsCleaning = true; const t0 = performance.now(); - - const idbSize = await idbCount() + const cachedHashesCount = await idbCount() .catch(() => 0); - - if (idbSize < thumbHashes.size + 100) { + if (cachedHashesCount < galleryHashes.size + 500) { // Don't run when there aren't many excess entries idbIsCleaning = false; return; } - showCleaningMsg(true); - idbClean(thumbHashes) + const removeOverlay = showCleaningMsg(); + idbClean(galleryHashes) .then(delcount => { const t1 = performance.now(); - log(`Thumbnail DB cleanup: kept=${thumbHashes.size} deleted=${delcount} time=${Math.floor(t1 - t0)}ms`); + log(`Thumbnail DB cleanup: kept=${galleryHashes.size} deleted=${delcount} time=${Math.floor(t1 - t0)}ms`); }) .catch((err) => { error("Thumbnail DB cleanup: Cleanup failed.", err.message); }) .finally(() => { idbIsCleaning = false; - showCleaningMsg(false); + removeOverlay(); }); } @@ -620,11 +617,11 @@ async function fetchFilesHT(evt) { async function fetchFilesWS(evt) { // fetch file-by-file list over websockets if (!cleaningOverlayIsReady) { - initCleaningOverlay() // Can't call during initGallery because it'll attach to the wrong component for some reason + setOverlayAnimation() // Can't call during initGallery because it'll attach to the wrong component for some reason .then(() => {cleaningOverlayIsReady = true}); } if (idbIsCleaning) return; - thumbHashes.clear(); // Only called here because fetchFilesHT isn't called directly + galleryHashes.clear(); // Only called here because fetchFilesHT isn't called directly el.files.innerHTML = ''; if (!url) return; if (ws && ws.readyState === WebSocket.OPEN) ws.close(); // abort previous request @@ -724,31 +721,30 @@ async function monitorGalleries() { } } -async function initCleaningOverlay() { - if (!el.folders) { - return; - } +async function setOverlayAnimation() { const busyAnimation = document.createElement("style"); - busyAnimation.textContent = ".idbBusyAnim{width:16px;height:16px;border-radius:50%;display:block;margin:16px;position:relative;background:#ff3d00;color:#fff;box-shadow:-24px 0,24px 0;box-sizing:border-box;animation:2s ease-in-out infinite rotation}@keyframes rotation{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}" + busyAnimation.textContent = ".idbBusyAnim{width:16px;height:16px;border-radius:50%;display:block;margin:16px;position:relative;background:#ff3d00;color:#fff;box-shadow:-24px 0,24px 0;box-sizing:border-box;animation:2s ease-in-out infinite overlayRotation}@keyframes overlayRotation{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}" document.head.append(busyAnimation); - - el.folders.parentElement.style.position = "relative"; - - el.overlay = document.createElement("div"); - el.overlay.style.cssText = "position: absolute; height: 100%; width: 100%; background-color: hsl(210 50 20 / 0.8); display: none; align-items: center; justify-content: center;"; - const msg = document.createElement("span"); - msg.style.cssText = "display: block; background-color: hsl(0 0 10); color: white; padding: 12px; border-radius: 8px; margin-left: -30px; margin-right: 30px;"; - msg.innerText = "Running thumbnail cleanup"; - - const anim = document.createElement("span"); - anim.classList.add("idbBusyAnim"); - - el.overlay.append(msg, anim); - el.folders.parentElement.append(el.overlay); } -function showCleaningMsg(state) { - el.overlay.style.display = state ? "flex" : "none"; +function showCleaningMsg() { + const parent = el.folders.parentElement; + const cleaningOverlay = document.createElement("div"); + const msg = document.createElement("span"); + const anim = document.createElement("span"); + + parent.style.position = "relative"; + cleaningOverlay.style.cssText = "position: absolute; height: 100%; width: 100%; background-color: hsl(210 50 20 / 0.8); display: flex; align-items: center; justify-content: center;"; + msg.style.cssText = "display: block; background-color: hsl(0 0 10); color: white; padding: 12px; border-radius: 8px; margin-left: -30px; margin-right: 30px;"; + msg.innerText = "Running thumbnail cleanup"; + anim.classList.add("idbBusyAnim"); + + cleaningOverlay.append(msg, anim); + parent.append(cleaningOverlay); + return () => { + parent.style.position = ""; + cleaningOverlay.remove(); + } } async function initGallery() { // triggered on gradio change to monitor when ui gets sufficiently constructed From 7caba3287fbe48f6ba7263fb35ebe40bae3c4fd0 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:11:46 -0800 Subject: [PATCH 07/10] Reorganize functions for readability --- javascript/gallery.js | 62 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 49e6fe080..86f167c0f 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -7,6 +7,8 @@ let outstanding = 0; let lastSort = 0; let lastSortName = 'None'; let idbIsCleaning = false; +let cleaningOverlayIsReady = false; +const galleryHashes = new Set(); // Store separator states for the session const separatorStates = new Map(); const el = { @@ -16,8 +18,6 @@ const el = { status: undefined, btnSend: undefined, }; -let cleaningOverlayIsReady = false; -const galleryHashes = new Set(); const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; @@ -554,35 +554,6 @@ async function gallerySort(btn) { updateStatusWithSort(`${arr.length.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`); } -async function thumbCacheCleanup() { - if (idbIsCleaning) return; - await awaitForIDB(); - idbIsCleaning = true; - - const t0 = performance.now(); - const cachedHashesCount = await idbCount() - .catch(() => 0); - if (cachedHashesCount < galleryHashes.size + 500) { - // Don't run when there aren't many excess entries - idbIsCleaning = false; - return; - } - - const removeOverlay = showCleaningMsg(); - idbClean(galleryHashes) - .then(delcount => { - const t1 = performance.now(); - log(`Thumbnail DB cleanup: kept=${galleryHashes.size} deleted=${delcount} time=${Math.floor(t1 - t0)}ms`); - }) - .catch((err) => { - error("Thumbnail DB cleanup: Cleanup failed.", err.message); - }) - .finally(() => { - idbIsCleaning = false; - removeOverlay(); - }); -} - async function fetchFilesHT(evt) { const t0 = performance.now(); const fragment = document.createDocumentFragment(); @@ -747,6 +718,35 @@ function showCleaningMsg() { } } +async function thumbCacheCleanup() { + if (idbIsCleaning) return; + await awaitForIDB(); + idbIsCleaning = true; + + const t0 = performance.now(); + const cachedHashesCount = await idbCount() + .catch(() => 0); + if (cachedHashesCount < galleryHashes.size + 500) { + // Don't run when there aren't many excess entries + idbIsCleaning = false; + return; + } + + const removeOverlayFunc = showCleaningMsg(); + idbClean(galleryHashes) + .then(delcount => { + const t1 = performance.now(); + log(`Thumbnail DB cleanup: kept=${galleryHashes.size} deleted=${delcount} time=${Math.floor(t1 - t0)}ms`); + }) + .catch((err) => { + error("Thumbnail DB cleanup: Cleanup failed.", err.message); + }) + .finally(() => { + removeOverlayFunc(); + idbIsCleaning = false; + }); +} + async function initGallery() { // triggered on gradio change to monitor when ui gets sufficiently constructed log('initGallery'); el.folders = gradioApp().getElementById('tab-gallery-folders'); From 538cad1aadfd9892eb23b622df1853f88b35d24a Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:15:41 -0800 Subject: [PATCH 08/10] Simplify init - Doesn't need advanced handling anymore because the overlay is now fully dynamic. --- javascript/gallery.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 86f167c0f..88a21c1a8 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -7,7 +7,6 @@ let outstanding = 0; let lastSort = 0; let lastSortName = 'None'; let idbIsCleaning = false; -let cleaningOverlayIsReady = false; const galleryHashes = new Set(); // Store separator states for the session const separatorStates = new Map(); @@ -587,10 +586,6 @@ async function fetchFilesHT(evt) { } async function fetchFilesWS(evt) { // fetch file-by-file list over websockets - if (!cleaningOverlayIsReady) { - setOverlayAnimation() // Can't call during initGallery because it'll attach to the wrong component for some reason - .then(() => {cleaningOverlayIsReady = true}); - } if (idbIsCleaning) return; galleryHashes.clear(); // Only called here because fetchFilesHT isn't called directly el.files.innerHTML = ''; @@ -715,7 +710,7 @@ function showCleaningMsg() { return () => { parent.style.position = ""; cleaningOverlay.remove(); - } + } } async function thumbCacheCleanup() { @@ -757,6 +752,7 @@ async function initGallery() { // triggered on gradio change to monitor when ui error('initGallery', 'Missing gallery elements'); return; } + setOverlayAnimation(); el.search.addEventListener('input', gallerySearch); el.btnSend = gradioApp().getElementById('tab-gallery-send-image'); document.getElementById('tab-gallery-files').style.height = opts.logmonitor_show ? '75vh' : '85vh'; From 1b4bdda3a229cd066cded00471915621a8b1cb14 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:53:13 -0800 Subject: [PATCH 09/10] Add function documentation --- javascript/gallery.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/javascript/gallery.js b/javascript/gallery.js index 88a21c1a8..1e70f09c3 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -693,6 +693,10 @@ async function setOverlayAnimation() { document.head.append(busyAnimation); } +/** + * Generate and display the overlay to announce cleanup is in progress. + * @returns {() => void} Function for clearing the overlay + */ function showCleaningMsg() { const parent = el.folders.parentElement; const cleaningOverlay = document.createElement("div"); From 412d857e735b2f103b36f43a31ad3dd2abce1a20 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 22 Nov 2025 16:18:37 -0800 Subject: [PATCH 10/10] Minor code layout adjustment --- javascript/gallery.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 1e70f09c3..28226a2b4 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -20,6 +20,10 @@ const el = { const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; +async function awaitForIDB(num = 0) { + while (outstanding > num || idbIsCleaning) await new Promise((resolve) => setTimeout(resolve, 50)); +} + // HTML Elements class GalleryFolder extends HTMLElement { @@ -329,10 +333,6 @@ class GalleryFile extends HTMLElement { const gallerySendImage = (_images) => [currentImage]; // invoked by gradio button -async function awaitForIDB(num = 0) { - while (outstanding > num || idbIsCleaning) await new Promise((resolve) => setTimeout(resolve, 50)); -} - async function getHash(str, algo = 'SHA-256') { try { let hex = '';