From 820b017dcf1d56bf22cadf83d522f50abd9e62a1 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:35:21 -0800 Subject: [PATCH 1/9] Additional AbortSignal checks --- javascript/gallery.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/gallery.js b/javascript/gallery.js index fc36f5152..8c1dc9f0f 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -856,6 +856,7 @@ async function fetchFilesHT(evt, controller) { } } + if (controller.signal.aborted) return; el.files.appendChild(fragment); const t1 = performance.now(); @@ -915,6 +916,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets } }; ws.onclose = (event) => { + if (controller.signal.aborted) return; el.files.appendChild(fragment); // gallerySort(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); From 6d940ef0b7a51666df3c132cebb4c8ad73772cec Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:42:00 -0800 Subject: [PATCH 2/9] Create SimpleProgressBar class --- javascript/simpleProgressBar.js | 82 +++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 javascript/simpleProgressBar.js diff --git a/javascript/simpleProgressBar.js b/javascript/simpleProgressBar.js new file mode 100644 index 000000000..75cbfe49c --- /dev/null +++ b/javascript/simpleProgressBar.js @@ -0,0 +1,82 @@ +class SimpleProgressBar { + #container = document.createElement("div"); + #progress = document.createElement("div"); + #textDiv = document.createElement("div"); + #text = document.createElement("span"); + #visible = false; + #hideTimeout = null; + #interval = null; + #max = 0; + /** @type {Set} */ + #monitoredSet; + + constructor() { + this.#container.style.cssText = "position:relative;width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;" + this.#progress.style.cssText = "position:absolute;left:0;height:100%;width:0;transition:width 200ms;" + this.#progress.style.backgroundColor = "hsla(110, 32%, 35%, 0.80)"; // alt: "#27911d" + this.#textDiv.style.cssText = "margin:auto;width:max-content;height:100%;"; + this.#text.style.cssText = "position:relative;user-select:none;color:white;" + + this.#textDiv.append(this.#text); + this.#container.append(this.#progress, this.#textDiv); + } + + setMax(max) { + this.clearProgress(); + this.#max = max; + this.#startUpdating(); + } + + attach(element) { + if (element.hasChildNodes) { + element.innerHTML = ''; + } + element.appendChild(this.#container); + } + + monitor(hashes) { + // This is required because incrementing a variable with a class method turned out to not be an atomic operation + this.#monitoredSet = hashes; + } + + clearProgress() { + this.#stopUpdating(); + clearTimeout(this.#hideTimeout); + this.#hideTimeout = null; + this.#container.style.display = "none"; + this.#visible = false; + this.#progress.style.width = "0"; + this.#text.textContent = ""; + } + + #setProgress(loaded, max) { + if (this.#hideTimeout) { + this.#hideTimeout = null; + } + + this.#progress.style.width = `${Math.floor((loaded / max) * 100)}%`; + this.#text.textContent = `${loaded}/${max}`; + + if (!this.#visible) { + this.#container.style.display = "block"; + this.#visible = true; + } + if (loaded >= max) { + this.#stopUpdating() + this.#hideTimeout = setTimeout(() => { + this.clearProgress(); + }, 1000); + } + } + + #startUpdating() { + this.#interval = setInterval(() => { + this.#setProgress(this.#monitoredSet.size, this.#max); + }, 250); + } + + #stopUpdating() { + clearInterval(this.#interval); + this.#interval = null; + } +} From 322cf8af391279c01a9ee7a50e3ccebf9ac11426 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:42:24 -0800 Subject: [PATCH 3/9] Implement SimpleProgressBar class --- javascript/gallery.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 8c1dc9f0f..93d3b870f 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -14,11 +14,13 @@ const fileStylesheet = new CSSStyleSheet(); const iconStopwatch = String.fromCodePoint(9201); // Store separator states for the session const separatorStates = new Map(); +const galleryProgressBar = new SimpleProgressBar(); const el = { folders: undefined, files: undefined, search: undefined, status: undefined, + progress: undefined, btnSend: undefined, }; @@ -862,6 +864,7 @@ async function fetchFilesHT(evt, controller) { const t1 = performance.now(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); + galleryProgressBar.setMax(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); } @@ -872,6 +875,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets maintenanceController.abort('Gallery update'); // Abort previous controller maintenanceController = controller; // Point to new controller for next time galleryHashes.clear(); // Must happen AFTER the AbortController steps + galleryProgressBar.clearProgress(); el.files.innerHTML = ''; updateGalleryStyles(); @@ -921,6 +925,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets // gallerySort(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); + galleryProgressBar.setMax(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); }; @@ -985,13 +990,16 @@ async function initGallery() { // triggered on gradio change to monitor when ui el.files = gradioApp().getElementById('tab-gallery-files'); el.status = gradioApp().getElementById('tab-gallery-status'); el.search = gradioApp().querySelector('#tab-gallery-search textarea'); - if (!el.folders || !el.files || !el.status || !el.search) { + el.progress = gradioApp().getElementById('tab-gallery-progress'); + if (!el.folders || !el.files || !el.status || !el.search || el.progress) { error('initGallery', 'Missing gallery elements'); return; } updateGalleryStyles(); injectGalleryStatusCSS(); setOverlayAnimation(); + galleryProgressBar.attachTo(el.progress); + galleryProgressBar.monitor(galleryHashes); 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 12d2bb8495a762c098c7f7a3a795b45a15a088ad Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:01:28 -0800 Subject: [PATCH 4/9] Ensure class loads first Temporary solution until ESM update. --- javascript/{simpleProgressBar.js => _simpleProgressBar.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename javascript/{simpleProgressBar.js => _simpleProgressBar.js} (100%) diff --git a/javascript/simpleProgressBar.js b/javascript/_simpleProgressBar.js similarity index 100% rename from javascript/simpleProgressBar.js rename to javascript/_simpleProgressBar.js From 87b77af13d1c193bff7ca52eb0c06308ece873f7 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:02:45 -0800 Subject: [PATCH 5/9] Simplify and lint --- javascript/_simpleProgressBar.js | 64 ++++++++++++++------------------ javascript/gallery.js | 20 +++++----- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/javascript/_simpleProgressBar.js b/javascript/_simpleProgressBar.js index 75cbfe49c..b900573cb 100644 --- a/javascript/_simpleProgressBar.js +++ b/javascript/_simpleProgressBar.js @@ -1,8 +1,8 @@ class SimpleProgressBar { - #container = document.createElement("div"); - #progress = document.createElement("div"); - #textDiv = document.createElement("div"); - #text = document.createElement("span"); + #container = document.createElement('div'); + #progress = document.createElement('div'); + #textDiv = document.createElement('div'); + #text = document.createElement('span'); #visible = false; #hideTimeout = null; #interval = null; @@ -10,46 +10,44 @@ class SimpleProgressBar { /** @type {Set} */ #monitoredSet; - constructor() { - this.#container.style.cssText = "position:relative;width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;" - this.#progress.style.cssText = "position:absolute;left:0;height:100%;width:0;transition:width 200ms;" - this.#progress.style.backgroundColor = "hsla(110, 32%, 35%, 0.80)"; // alt: "#27911d" - this.#textDiv.style.cssText = "margin:auto;width:max-content;height:100%;"; - this.#text.style.cssText = "position:relative;user-select:none;color:white;" + constructor(monitoredSet) { + this.#monitoredSet = monitoredSet; // This is required because incrementing a variable with a class method turned out to not be an atomic operation + this.#container.style.cssText = 'position:relative;width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;' + this.#progress.style.cssText = 'position:absolute;left:0;height:100%;width:0;transition:width 200ms;' + this.#progress.style.backgroundColor = 'hsla(110, 32%, 35%, 0.80)'; // alt: '#27911d' + this.#textDiv.style.cssText = 'position:relative;margin:auto;width:max-content;height:100%;'; + this.#text.style.cssText = 'user-select:none;color:white;' this.#textDiv.append(this.#text); this.#container.append(this.#progress, this.#textDiv); } - setMax(max) { - this.clearProgress(); - this.#max = max; - this.#startUpdating(); + start(total) { + this.clear(); + this.#max = total; + this.#interval = setInterval(() => { + this.#update(this.#monitoredSet.size, this.#max); + }, 250); } - attach(element) { + attachTo(element) { if (element.hasChildNodes) { element.innerHTML = ''; } element.appendChild(this.#container); } - monitor(hashes) { - // This is required because incrementing a variable with a class method turned out to not be an atomic operation - this.#monitoredSet = hashes; - } - - clearProgress() { - this.#stopUpdating(); + clear() { + this.#stop(); clearTimeout(this.#hideTimeout); this.#hideTimeout = null; - this.#container.style.display = "none"; + this.#container.style.display = 'none'; this.#visible = false; - this.#progress.style.width = "0"; - this.#text.textContent = ""; + this.#progress.style.width = '0'; + this.#text.textContent = ''; } - #setProgress(loaded, max) { + #update(loaded, max) { if (this.#hideTimeout) { this.#hideTimeout = null; } @@ -58,24 +56,18 @@ class SimpleProgressBar { this.#text.textContent = `${loaded}/${max}`; if (!this.#visible) { - this.#container.style.display = "block"; + this.#container.style.display = 'block'; this.#visible = true; } if (loaded >= max) { - this.#stopUpdating() + this.#stop() this.#hideTimeout = setTimeout(() => { - this.clearProgress(); + this.clear(); }, 1000); } } - #startUpdating() { - this.#interval = setInterval(() => { - this.#setProgress(this.#monitoredSet.size, this.#max); - }, 250); - } - - #stopUpdating() { + #stop() { clearInterval(this.#interval); this.#interval = null; } diff --git a/javascript/gallery.js b/javascript/gallery.js index 93d3b870f..55ae92ca4 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -14,13 +14,12 @@ const fileStylesheet = new CSSStyleSheet(); const iconStopwatch = String.fromCodePoint(9201); // Store separator states for the session const separatorStates = new Map(); -const galleryProgressBar = new SimpleProgressBar(); +const galleryProgressBar = new SimpleProgressBar(galleryHashes); const el = { folders: undefined, files: undefined, search: undefined, status: undefined, - progress: undefined, btnSend: undefined, }; @@ -864,7 +863,7 @@ async function fetchFilesHT(evt, controller) { const t1 = performance.now(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); - galleryProgressBar.setMax(numFiles); + galleryProgressBar.start(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); } @@ -875,7 +874,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets maintenanceController.abort('Gallery update'); // Abort previous controller maintenanceController = controller; // Point to new controller for next time galleryHashes.clear(); // Must happen AFTER the AbortController steps - galleryProgressBar.clearProgress(); + galleryProgressBar.clear(); el.files.innerHTML = ''; updateGalleryStyles(); @@ -925,7 +924,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets // gallerySort(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); - galleryProgressBar.setMax(numFiles); + galleryProgressBar.start(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); }; @@ -990,16 +989,19 @@ async function initGallery() { // triggered on gradio change to monitor when ui el.files = gradioApp().getElementById('tab-gallery-files'); el.status = gradioApp().getElementById('tab-gallery-status'); el.search = gradioApp().querySelector('#tab-gallery-search textarea'); - el.progress = gradioApp().getElementById('tab-gallery-progress'); - if (!el.folders || !el.files || !el.status || !el.search || el.progress) { + if (!el.folders || !el.files || !el.status || !el.search) { error('initGallery', 'Missing gallery elements'); return; } updateGalleryStyles(); injectGalleryStatusCSS(); setOverlayAnimation(); - galleryProgressBar.attachTo(el.progress); - galleryProgressBar.monitor(galleryHashes); + const progress = gradioApp().getElementById('tab-gallery-progress'); + if (progress) { + galleryProgressBar.attachTo(progress); + } else { + log('initGallery', 'Failed to attach loading progress bar'); + } 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 ba7fb3cd9dcc2e9c0708938782307b0e60de3a37 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:35:59 -0800 Subject: [PATCH 6/9] Minor style update --- javascript/_simpleProgressBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/_simpleProgressBar.js b/javascript/_simpleProgressBar.js index b900573cb..fc5b0c7e8 100644 --- a/javascript/_simpleProgressBar.js +++ b/javascript/_simpleProgressBar.js @@ -12,7 +12,7 @@ class SimpleProgressBar { constructor(monitoredSet) { this.#monitoredSet = monitoredSet; // This is required because incrementing a variable with a class method turned out to not be an atomic operation - this.#container.style.cssText = 'position:relative;width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;' + this.#container.style.cssText = 'position:relative;overflow:hidden;border-radius:var(--sd-border-radius);width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;' this.#progress.style.cssText = 'position:absolute;left:0;height:100%;width:0;transition:width 200ms;' this.#progress.style.backgroundColor = 'hsla(110, 32%, 35%, 0.80)'; // alt: '#27911d' this.#textDiv.style.cssText = 'position:relative;margin:auto;width:max-content;height:100%;'; From 75e858df2206e081465c1f12009e0e8f1441c0aa Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Fri, 2 Jan 2026 02:17:07 -0800 Subject: [PATCH 7/9] Add attachment point for non-ModernUI --- modules/ui_gallery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ui_gallery.py b/modules/ui_gallery.py index 283b720a8..f22bc1a1a 100644 --- a/modules/ui_gallery.py +++ b/modules/ui_gallery.py @@ -68,6 +68,7 @@ def create_ui(): sort_buttons.append(ToolButton(value=ui_symbols.sort_time_dsc, elem_classes=['gallery-sort'])) gr.Textbox(show_label=False, placeholder='Search', elem_id='tab-gallery-search') gr.HTML('', elem_id='tab-gallery-status') + gr.HTML('', elem_id='tab-gallery-progress') for btn in sort_buttons: btn.click(fn=None, _js='gallerySort', inputs=[btn], outputs=[]) with gr.Row(): From e55c20af5bc38ee8c78d2f0ec4a36e3c86c17423 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:26:58 -0800 Subject: [PATCH 8/9] Move SimpleProgressBar into `gallery.js` Converted to using static class elements since it's only being used here. --- javascript/_simpleProgressBar.js | 74 --------------------------- javascript/gallery.js | 85 ++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 79 deletions(-) delete mode 100644 javascript/_simpleProgressBar.js diff --git a/javascript/_simpleProgressBar.js b/javascript/_simpleProgressBar.js deleted file mode 100644 index fc5b0c7e8..000000000 --- a/javascript/_simpleProgressBar.js +++ /dev/null @@ -1,74 +0,0 @@ -class SimpleProgressBar { - #container = document.createElement('div'); - #progress = document.createElement('div'); - #textDiv = document.createElement('div'); - #text = document.createElement('span'); - #visible = false; - #hideTimeout = null; - #interval = null; - #max = 0; - /** @type {Set} */ - #monitoredSet; - - constructor(monitoredSet) { - this.#monitoredSet = monitoredSet; // This is required because incrementing a variable with a class method turned out to not be an atomic operation - this.#container.style.cssText = 'position:relative;overflow:hidden;border-radius:var(--sd-border-radius);width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;' - this.#progress.style.cssText = 'position:absolute;left:0;height:100%;width:0;transition:width 200ms;' - this.#progress.style.backgroundColor = 'hsla(110, 32%, 35%, 0.80)'; // alt: '#27911d' - this.#textDiv.style.cssText = 'position:relative;margin:auto;width:max-content;height:100%;'; - this.#text.style.cssText = 'user-select:none;color:white;' - - this.#textDiv.append(this.#text); - this.#container.append(this.#progress, this.#textDiv); - } - - start(total) { - this.clear(); - this.#max = total; - this.#interval = setInterval(() => { - this.#update(this.#monitoredSet.size, this.#max); - }, 250); - } - - attachTo(element) { - if (element.hasChildNodes) { - element.innerHTML = ''; - } - element.appendChild(this.#container); - } - - clear() { - this.#stop(); - clearTimeout(this.#hideTimeout); - this.#hideTimeout = null; - this.#container.style.display = 'none'; - this.#visible = false; - this.#progress.style.width = '0'; - this.#text.textContent = ''; - } - - #update(loaded, max) { - if (this.#hideTimeout) { - this.#hideTimeout = null; - } - - this.#progress.style.width = `${Math.floor((loaded / max) * 100)}%`; - this.#text.textContent = `${loaded}/${max}`; - - if (!this.#visible) { - this.#container.style.display = 'block'; - this.#visible = true; - } - if (loaded >= max) { - this.#stop() - this.#hideTimeout = setTimeout(() => { - this.clear(); - }, 1000); - } - } - - #stop() { - clearInterval(this.#interval); - this.#interval = null; - } -} diff --git a/javascript/gallery.js b/javascript/gallery.js index 55ae92ca4..f6965b650 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -14,7 +14,6 @@ const fileStylesheet = new CSSStyleSheet(); const iconStopwatch = String.fromCodePoint(9201); // Store separator states for the session const separatorStates = new Map(); -const galleryProgressBar = new SimpleProgressBar(galleryHashes); const el = { folders: undefined, files: undefined, @@ -109,6 +108,82 @@ function updateGalleryStyles() { // Classes +class SimpleProgressBar { + static #container = document.createElement('div'); + static #progress = document.createElement('div'); + static #textDiv = document.createElement('div'); + static #text = document.createElement('span'); + static #visible = false; + static #hideTimeout = null; + static #interval = null; + static #max = 0; + /** @type {Set} */ + static #monitoredSet; + + static { + this.#monitoredSet = galleryHashes; // This is required because incrementing a variable with a class method turned out to not be an atomic operation + this.#container.style.cssText = 'position:relative;overflow:hidden;border-radius:var(--sd-border-radius);width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;' + this.#progress.style.cssText = 'position:absolute;left:0;height:100%;width:0;transition:width 200ms;' + this.#progress.style.backgroundColor = 'hsla(110, 32%, 35%, 0.80)'; // alt: '#27911d' + this.#textDiv.style.cssText = 'position:relative;margin:auto;width:max-content;height:100%;'; + this.#text.style.cssText = 'user-select:none;color:white;' + + this.#textDiv.append(this.#text); + this.#container.append(this.#progress, this.#textDiv); + } + + static start(total) { + this.clear(); + this.#max = total; + this.#interval = setInterval(() => { + this.#update(this.#monitoredSet.size, this.#max); + }, 250); + } + + static attachTo(element) { + if (element.hasChildNodes) { + element.innerHTML = ''; + } + element.appendChild(this.#container); + } + + static clear() { + this.#stop(); + clearTimeout(this.#hideTimeout); + this.#hideTimeout = null; + this.#container.style.display = 'none'; + this.#visible = false; + this.#progress.style.width = '0'; + this.#text.textContent = ''; + } + + static #update(loaded, max) { + if (this.#hideTimeout) { + this.#hideTimeout = null; + } + + this.#progress.style.width = `${Math.floor((loaded / max) * 100)}%`; + this.#text.textContent = `${loaded}/${max}`; + + if (!this.#visible) { + this.#container.style.display = 'block'; + this.#visible = true; + } + if (loaded >= max) { + this.#stop() + this.#hideTimeout = setTimeout(() => { + this.clear(); + }, 1000); + } + } + + static #stop() { + clearInterval(this.#interval); + this.#interval = null; + } +} + + /* This isn't as robust as the Web Locks API, but it will at least work if accessing a remote machine without HTTPS */ class SimpleFunctionQueue { #id; @@ -863,7 +938,7 @@ async function fetchFilesHT(evt, controller) { const t1 = performance.now(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); - galleryProgressBar.start(numFiles); + SimpleProgressBar.start(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); } @@ -874,7 +949,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets maintenanceController.abort('Gallery update'); // Abort previous controller maintenanceController = controller; // Point to new controller for next time galleryHashes.clear(); // Must happen AFTER the AbortController steps - galleryProgressBar.clear(); + SimpleProgressBar.clear(); el.files.innerHTML = ''; updateGalleryStyles(); @@ -924,7 +999,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets // gallerySort(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); - galleryProgressBar.start(numFiles); + SimpleProgressBar.start(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); }; @@ -998,7 +1073,7 @@ async function initGallery() { // triggered on gradio change to monitor when ui setOverlayAnimation(); const progress = gradioApp().getElementById('tab-gallery-progress'); if (progress) { - galleryProgressBar.attachTo(progress); + SimpleProgressBar.attachTo(progress); } else { log('initGallery', 'Failed to attach loading progress bar'); } From 0b663882744f5d8dbb0021568a86161a5bcb4ae2 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sun, 4 Jan 2026 00:34:16 -0800 Subject: [PATCH 9/9] Revert conversion to static class It's probably fine if keeping the instance creation next to the class definition as long as it doesn't get moved to earlier in the file. --- javascript/gallery.js | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index f6965b650..f5bbc20b3 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -109,19 +109,19 @@ function updateGalleryStyles() { // Classes class SimpleProgressBar { - static #container = document.createElement('div'); - static #progress = document.createElement('div'); - static #textDiv = document.createElement('div'); - static #text = document.createElement('span'); - static #visible = false; - static #hideTimeout = null; - static #interval = null; - static #max = 0; + #container = document.createElement('div'); + #progress = document.createElement('div'); + #textDiv = document.createElement('div'); + #text = document.createElement('span'); + #visible = false; + #hideTimeout = null; + #interval = null; + #max = 0; /** @type {Set} */ - static #monitoredSet; + #monitoredSet; - static { - this.#monitoredSet = galleryHashes; // This is required because incrementing a variable with a class method turned out to not be an atomic operation + constructor(monitoredSet) { + this.#monitoredSet = monitoredSet; // This is required because incrementing a variable with a class method turned out to not be an atomic operation this.#container.style.cssText = 'position:relative;overflow:hidden;border-radius:var(--sd-border-radius);width:100%;background-color:hsla(0,0%,36%,0.3);height:1.2rem;margin:0;padding:0;display:none;' this.#progress.style.cssText = 'position:absolute;left:0;height:100%;width:0;transition:width 200ms;' this.#progress.style.backgroundColor = 'hsla(110, 32%, 35%, 0.80)'; // alt: '#27911d' @@ -132,7 +132,7 @@ class SimpleProgressBar { this.#container.append(this.#progress, this.#textDiv); } - static start(total) { + start(total) { this.clear(); this.#max = total; this.#interval = setInterval(() => { @@ -140,14 +140,14 @@ class SimpleProgressBar { }, 250); } - static attachTo(element) { + attachTo(element) { if (element.hasChildNodes) { element.innerHTML = ''; } element.appendChild(this.#container); } - static clear() { + clear() { this.#stop(); clearTimeout(this.#hideTimeout); this.#hideTimeout = null; @@ -157,7 +157,7 @@ class SimpleProgressBar { this.#text.textContent = ''; } - static #update(loaded, max) { + #update(loaded, max) { if (this.#hideTimeout) { this.#hideTimeout = null; } @@ -177,12 +177,13 @@ class SimpleProgressBar { } } - static #stop() { + #stop() { clearInterval(this.#interval); this.#interval = null; } } +const galleryProgressBar = new SimpleProgressBar(galleryHashes); /* This isn't as robust as the Web Locks API, but it will at least work if accessing a remote machine without HTTPS */ class SimpleFunctionQueue { @@ -938,7 +939,7 @@ async function fetchFilesHT(evt, controller) { const t1 = performance.now(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); - SimpleProgressBar.start(numFiles); + galleryProgressBar.start(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); } @@ -949,7 +950,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets maintenanceController.abort('Gallery update'); // Abort previous controller maintenanceController = controller; // Point to new controller for next time galleryHashes.clear(); // Must happen AFTER the AbortController steps - SimpleProgressBar.clear(); + galleryProgressBar.clear(); el.files.innerHTML = ''; updateGalleryStyles(); @@ -999,7 +1000,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets // gallerySort(); log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); updateStatusWithSort(['Folder', evt.target.name], ['Images', numFiles.toLocaleString()], `${iconStopwatch} ${Math.floor(t1 - t0).toLocaleString()}ms`); - SimpleProgressBar.start(numFiles); + galleryProgressBar.start(numFiles); addSeparators(); thumbCacheCleanup(evt.target.name, numFiles, controller); }; @@ -1073,7 +1074,7 @@ async function initGallery() { // triggered on gradio change to monitor when ui setOverlayAnimation(); const progress = gradioApp().getElementById('tab-gallery-progress'); if (progress) { - SimpleProgressBar.attachTo(progress); + galleryProgressBar.attachTo(progress); } else { log('initGallery', 'Failed to attach loading progress bar'); }