From e0e5b76c8eceb9317d765d4df3469485bca7e506 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Wed, 31 Dec 2025 09:05:38 -0700 Subject: [PATCH] Improve Media Manager loading state with parallel fetching and overlay - Implemented simultaneous execution of getMediaDiagnostics and getMediaForSku in MediaManager.html to speed up initial load and refresh. - Added a translucent grid-loading-overlay that appears over existing tiles during refresh, preventing interaction while maintaining context. - Differentiated loading messages: 'Connecting to systems...' for initial load vs 'Refreshing media...' for updates. - Fixed a syntax error in the save handler. --- src/MediaManager.html | 292 ++++++++++++++++++++++++------------------ 1 file changed, 166 insertions(+), 126 deletions(-) diff --git a/src/MediaManager.html b/src/MediaManager.html index e415077..5b422e9 100644 --- a/src/MediaManager.html +++ b/src/MediaManager.html @@ -477,6 +477,25 @@ padding-top: 12px; border-top: 1px solid #f1f5f9; } + + /* Grid Overlay */ + .grid-loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.75); + backdrop-filter: blur(1px); + z-index: 50; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: var(--text-secondary); + font-weight: 500; + border-radius: 8px; + } @@ -560,36 +579,36 @@ - - @@ -865,81 +884,81 @@ this.saveBtn.innerText = enable ? "Save Changes" : "No Changes"; }; - UI.prototype.showTransferSession = function (mode, serviceName) { - var el = document.getElementById('transfer-session-ui'); - var desc = document.getElementById('transfer-desc'); - var statusText = document.getElementById('transfer-status-text'); - var bar = document.getElementById('transfer-progress-bar'); - var btnReopen = document.getElementById('btn-transfer-reopen'); - var btnCancel = document.getElementById('btn-transfer-cancel'); + UI.prototype.showTransferSession = function (mode, serviceName) { + var el = document.getElementById('transfer-session-ui'); + var desc = document.getElementById('transfer-desc'); + var statusText = document.getElementById('transfer-status-text'); + var bar = document.getElementById('transfer-progress-bar'); + var btnReopen = document.getElementById('btn-transfer-reopen'); + var btnCancel = document.getElementById('btn-transfer-cancel'); - el.style.display = 'block'; - bar.style.width = '0%'; - statusText.innerText = 'Initializing...'; - document.getElementById('transfer-count').innerText = ''; + el.style.display = 'block'; + bar.style.width = '0%'; + statusText.innerText = 'Initializing...'; + document.getElementById('transfer-count').innerText = ''; - // Reset Buttons - btnCancel.disabled = false; - btnReopen.disabled = true; // Default + // Reset Buttons + btnCancel.disabled = false; + btnReopen.disabled = true; // Default - if (mode === 'waiting') { - desc.innerText = "Select items from " + (serviceName || "the service") + " in the popup window. Click 'Done' when finished."; - statusText.innerText = "Waiting for selection..."; - } else { - desc.innerText = "Importing media from " + (serviceName || "source") + "..."; - } - }; - - UI.prototype.updateTransferProgress = function (current, total, statusMsg) { - var bar = document.getElementById('transfer-progress-bar'); - var statusText = document.getElementById('transfer-status-text'); - var countText = document.getElementById('transfer-count'); - - // Disable Reopen once transfer starts - const btnReopen = document.getElementById('btn-transfer-reopen'); - if (btnReopen) btnReopen.disabled = true; - - if (total > 0) { - var pct = Math.round((current / total) * 100); - bar.style.width = pct + '%'; - countText.innerText = current + ' / ' + total; + if (mode === 'waiting') { + desc.innerText = "Select items from " + (serviceName || "the service") + " in the popup window. Click 'Done' when finished."; + statusText.innerText = "Waiting for selection..."; } else { - // Indeterminate - bar.style.width = '100%'; - countText.innerText = ''; + desc.innerText = "Importing media from " + (serviceName || "source") + "..."; + } + }; + + UI.prototype.updateTransferProgress = function (current, total, statusMsg) { + var bar = document.getElementById('transfer-progress-bar'); + var statusText = document.getElementById('transfer-status-text'); + var countText = document.getElementById('transfer-count'); + + // Disable Reopen once transfer starts + const btnReopen = document.getElementById('btn-transfer-reopen'); + if (btnReopen) btnReopen.disabled = true; + + if (total > 0) { + var pct = Math.round((current / total) * 100); + bar.style.width = pct + '%'; + countText.innerText = current + ' / ' + total; + } else { + // Indeterminate + bar.style.width = '100%'; + countText.innerText = ''; } - if (statusMsg) statusText.innerText = statusMsg; + if (statusMsg) statusText.innerText = statusMsg; - // If done, disable cancel - if (current === total && total > 0) { - const btnCancel = document.getElementById('btn-transfer-cancel'); - if (btnCancel) btnCancel.disabled = true; - } + // If done, disable cancel + if (current === total && total > 0) { + const btnCancel = document.getElementById('btn-transfer-cancel'); + if (btnCancel) btnCancel.disabled = true; + } }; - UI.prototype.hideTransferSession = function () { - document.getElementById('transfer-session-ui').style.display = 'none'; + UI.prototype.hideTransferSession = function () { + document.getElementById('transfer-session-ui').style.display = 'none'; }; - UI.prototype.setupReopenButton = function (url) { - var btn = document.getElementById('btn-transfer-reopen'); - if (!url) { - btn.disabled = true; - return; - } + UI.prototype.setupReopenButton = function (url) { + var btn = document.getElementById('btn-transfer-reopen'); + if (!url) { + btn.disabled = true; + return; + } - const width = 1200; - const height = 800; - const left = (screen.width - width) / 2; - const top = (screen.height - height) / 2; - const params = `width=${width},height=${height},top=${top},left=${left}`; + const width = 1200; + const height = 800; + const left = (screen.width - width) / 2; + const top = (screen.height - height) / 2; + const params = `width=${width},height=${height},top=${top},left=${left}`; - btn.onclick = function (e) { - e.preventDefault(); - window.open(url, 'googlePhotos', params); - }; - btn.disabled = false; + btn.onclick = function (e) { + e.preventDefault(); + window.open(url, 'googlePhotos', params); + }; + btn.disabled = false; }; UI.prototype.render = function (items) { @@ -983,10 +1002,31 @@ }; UI.prototype.setLoadingState = function (isLoading) { + var overlay = document.getElementById('grid-loading-overlay'); + if (isLoading) { - this.grid.innerHTML = '
' + - '
' + - '
Connecting to systems...
'; + // Check if we have items + var hasItems = this.grid.children.length > 0 && !this.grid.querySelector('.empty-state'); + + if (hasItems) { + // Create overlay if not exists + if (!overlay) { + overlay = document.createElement('div'); + overlay.id = 'grid-loading-overlay'; + overlay.className = 'grid-loading-overlay'; + overlay.innerHTML = '
Refreshing media...
'; + this.grid.style.position = 'relative'; // Ensure positioning context + this.grid.appendChild(overlay); + } + } else { + // Standard empty state loading + this.grid.innerHTML = '
' + + '
' + + '
Loading media...
'; + } + } else { + // Clear overlay + if (overlay) overlay.remove(); } }; @@ -1281,9 +1321,9 @@ ui.logStatus('init', 'Initializing access...', 'info'); + // 1. Diagnostics (Parallel) google.script.run - .withSuccessHandler((diagnostics) => { // Use arrow - + .withSuccessHandler((diagnostics) => { // Check Resumption if (diagnostics.activeJobId) { ui.logStatus('resume', 'Resuming active background job...', 'info'); @@ -1304,7 +1344,6 @@ // Capture Token if (diagnostics.token) state.token = diagnostics.token; - // Shopify Status if (diagnostics.shopify.status === 'ok') { ui.logStatus('shopify', `Shopify Product: ok (${diagnostics.shopify.mediaCount} media) (ID: ${diagnostics.shopify.id}) Open Admin ↗`, 'success'); @@ -1314,43 +1353,44 @@ } else { ui.logStatus('shopify', `Shopify Check Failed: ${diagnostics.shopify.error}`, 'error'); } - - ui.logStatus('fetch', 'Fetching full media state (this may take a moment)...', 'info'); - - // 2. Load Full Media - google.script.run - .withSuccessHandler(function (items) { - // Normalize items - const normalized = items.map(i => ({ - ...i, - id: i.id || Math.random().toString(36).substr(2, 9), - status: i.source || 'drive_only', // Fix: Use source as status - source: i.source, - _deleted: false // Init soft delete flag - })); - - state.setItems(normalized); - - if (!controller.hasRunMatching) { - controller.hasRunMatching = true; - controller.checkMatches(normalized); - } else { - controller.showGallery(); - } - - }) - .withFailureHandler(function (err) { - ui.logStatus('fatal', `Failed to load media: ${err.message}`, 'error'); - }) - .getMediaForSku(sku); - }) .withFailureHandler(function (err) { ui.logStatus('fatal', `Diagnostics failed: ${err.message}`, 'error'); }) .getMediaDiagnostics(sku, ""); + + // 2. Load Full Media (Parallel) + ui.logStatus('fetch', 'Fetching full media state...', 'info'); + google.script.run + .withSuccessHandler(function (items) { + // Normalize items + const normalized = items.map(function (i) { + return { + ...i, + id: i.id || Math.random().toString(36).substr(2, 9), + status: i.source || 'drive_only', + source: i.source, + _deleted: false + }; + }); + + state.setItems(normalized); + + if (!controller.hasRunMatching) { + controller.hasRunMatching = true; + controller.checkMatches(normalized); + } else { + controller.showGallery(); + } + }) + .withFailureHandler(function (err) { + ui.logStatus('fatal', `Failed to load media: ${err.message}`, 'error'); + ui.setLoadingState(false); + }) + .getMediaForSku(sku); }, + saveChanges() { ui.toggleSave(false); ui.saveBtn.innerText = "Saving...";