From 5eb7b317408c0ad2532c1952c3d96bbc5d9b2dee Mon Sep 17 00:00:00 2001 From: BillionClaw Date: Wed, 18 Mar 2026 11:37:42 +0800 Subject: [PATCH] fix(inline-viewer): add download progress bar for files >2GB Add download progress tracking to createBlobUrlViewer to fix incorrect progress display for large files. Changes: - Add xhr.onprogress handler to track download bytes - Use 64-bit float division to avoid 32-bit integer overflow - Show progress bar UI for files >10MB with percentage - Add CSS styles with dark theme support The progress calculation now correctly handles files larger than 2GB by using JavaScript's native double-precision floats instead of operations that might truncate to 32-bit integers. Fixes #82 --- static/css/views/inlineViewer.css | 50 ++++++++++++++++++++++++ static/js/features/files/inlineViewer.js | 37 ++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/static/css/views/inlineViewer.css b/static/css/views/inlineViewer.css index b22d1915..a1803b1d 100755 --- a/static/css/views/inlineViewer.css +++ b/static/css/views/inlineViewer.css @@ -171,6 +171,56 @@ color: #64748b; } +/* Download progress container */ +.inline-viewer-progress { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 20px; + background: rgba(255, 255, 255, 0.95); + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +/* Progress bar track */ +.inline-viewer-progress-bar { + width: 200px; + height: 8px; + background: #e2e8f0; + border-radius: 4px; + overflow: hidden; +} + +/* Progress bar fill */ +.inline-viewer-progress-fill { + height: 100%; + background: linear-gradient(90deg, #3b82f6, #60a5fa); + border-radius: 4px; + transition: width 0.2s ease; +} + +/* Progress percentage text */ +.inline-viewer-progress-text { + font-size: 14px; + font-weight: 600; + color: #475569; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +/* Dark theme support */ +[data-theme="dark"] .inline-viewer-progress { + background: rgba(30, 41, 59, 0.95); +} + +[data-theme="dark"] .inline-viewer-progress-bar { + background: #334155; +} + +[data-theme="dark"] .inline-viewer-progress-text { + color: #cbd5e1; +} + /* Error and unsupported message */ .inline-viewer-message { padding: 32px; diff --git a/static/js/features/files/inlineViewer.js b/static/js/features/files/inlineViewer.js index ad4a0187..9a451cc1 100755 --- a/static/js/features/files/inlineViewer.js +++ b/static/js/features/files/inlineViewer.js @@ -251,13 +251,42 @@ class InlineViewer { async createBlobUrlViewer(file, type, container, loader) { try { console.log('Creating blob URL viewer for:', file.name, 'type:', type); - + + // Update loader to show progress bar for large files + let progressBar = null; + let progressText = null; + if (loader && file.size > 10 * 1024 * 1024) { // Show progress for files > 10MB + loader.innerHTML = ` +
+ +
+
+
+
0%
+
+ `; + progressBar = loader.querySelector('.inline-viewer-progress-fill'); + progressText = loader.querySelector('.inline-viewer-progress-text'); + } + // Use XMLHttpRequest instead of fetch to get better control over the response const xhr = new XMLHttpRequest(); xhr.open('GET', `/api/files/${file.id}?inline=true`, true); xhr.responseType = 'blob'; xhr.withCredentials = true; - + + // Track download progress - use double precision for large file support (>2GB) + xhr.onprogress = (e) => { + if (e.lengthComputable && progressBar && progressText) { + // Use direct division to avoid 32-bit overflow issues + // e.loaded and e.total are JavaScript numbers (64-bit float) + const progress = e.loaded / e.total; + const pct = Math.round(progress * 100); + progressBar.style.width = pct + '%'; + progressText.textContent = pct + '%'; + } + }; + // Create a promise to handle the XHR const response = await new Promise((resolve, reject) => { xhr.onload = function() { @@ -267,11 +296,11 @@ class InlineViewer { reject(new Error(`Error fetching file: ${this.status} ${this.statusText}`)); } }; - + xhr.onerror = function() { reject(new Error('Network error')); }; - + xhr.send(); });