fix(upload): fix upload progress counter showing done count always as 0
Replace the fragile global DOM query approach in `updateOverallStatus`
with per-batch closure-based counters inside `processFiles`.
- Add `total`, `done`, `updateStatus()`, and `markDone()` as closure
variables/functions within each `processFiles` invocation
- Change `_uploadSingleFile` to accept an `onTerminal` callback instead
of `statusMessage`, called when a file reaches a terminal state
- Pass `markDone` as the `onTerminal` callback from `scheduleNext`
- Remove the now-unused global `updateOverallStatus` function
The previous implementation queried `document.querySelectorAll('.file-status')`
globally and relied on text `startsWith` checks to count completed files.
This was fragile and could produce a stale done=0 count in practice.
The new approach uses deterministic closure counters, so the displayed
"Uploading files (X/N)" count correctly increments as files complete.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -274,6 +274,25 @@ function processFiles(files, progressContainer, statusMessage) {
|
|||||||
statusMessage.textContent = `Queued ${fileArray.length} file(s) for upload…`;
|
statusMessage.textContent = `Queued ${fileArray.length} file(s) for upload…`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-batch counters tracked in closure variables (avoids fragile DOM queries).
|
||||||
|
const total = fileArray.length;
|
||||||
|
let done = 0;
|
||||||
|
|
||||||
|
function updateStatus() {
|
||||||
|
if (!statusMessage) return;
|
||||||
|
if (done === total) {
|
||||||
|
statusMessage.textContent = `All uploads completed (${done}/${total})`;
|
||||||
|
window.dispatchEvent(new CustomEvent('allUploadsComplete', { detail: { total, completed: done } }));
|
||||||
|
} else {
|
||||||
|
statusMessage.textContent = `Uploading files (${done}/${total})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function markDone() {
|
||||||
|
done++;
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-create one progress row per file.
|
// Pre-create one progress row per file.
|
||||||
const queueItems = fileArray.map((file) => {
|
const queueItems = fileArray.map((file) => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
@@ -319,7 +338,7 @@ function processFiles(files, progressContainer, statusMessage) {
|
|||||||
item.statusEl.textContent = 'Unsupported file type';
|
item.statusEl.textContent = 'Unsupported file type';
|
||||||
item.statusEl.className = 'text-xs text-red-500 mt-1';
|
item.statusEl.className = 'text-xs text-red-500 mt-1';
|
||||||
active--;
|
active--;
|
||||||
updateOverallStatus(statusMessage);
|
markDone();
|
||||||
// No HTTP request – skip straight to next without adding delay.
|
// No HTTP request – skip straight to next without adding delay.
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
continue;
|
continue;
|
||||||
@@ -330,12 +349,12 @@ function processFiles(files, progressContainer, statusMessage) {
|
|||||||
item.statusEl.textContent = 'Exceeds 500 MB limit';
|
item.statusEl.textContent = 'Exceeds 500 MB limit';
|
||||||
item.statusEl.className = 'text-xs text-red-500 mt-1';
|
item.statusEl.className = 'text-xs text-red-500 mt-1';
|
||||||
active--;
|
active--;
|
||||||
updateOverallStatus(statusMessage);
|
markDone();
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_uploadSingleFile(item.file, item.progressBar, item.statusEl, statusMessage)
|
_uploadSingleFile(item.file, item.progressBar, item.statusEl, markDone)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
active--;
|
active--;
|
||||||
if (result.rateLimited) {
|
if (result.rateLimited) {
|
||||||
@@ -348,7 +367,7 @@ function processFiles(files, progressContainer, statusMessage) {
|
|||||||
item.progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full';
|
item.progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full';
|
||||||
item.statusEl.textContent = 'Failed: rate limit retries exhausted';
|
item.statusEl.textContent = 'Failed: rate limit retries exhausted';
|
||||||
item.statusEl.className = 'text-xs text-red-500 mt-1';
|
item.statusEl.className = 'text-xs text-red-500 mt-1';
|
||||||
updateOverallStatus(statusMessage);
|
markDone();
|
||||||
}
|
}
|
||||||
// Resume after the backoff window.
|
// Resume after the backoff window.
|
||||||
const wait = Math.max(_adaptiveState.pauseUntil - Date.now() + 50, 0);
|
const wait = Math.max(_adaptiveState.pauseUntil - Date.now() + 50, 0);
|
||||||
@@ -383,10 +402,10 @@ function _isAcceptedFile(file) {
|
|||||||
* @param {File} file
|
* @param {File} file
|
||||||
* @param {HTMLElement} progressBar
|
* @param {HTMLElement} progressBar
|
||||||
* @param {HTMLElement} statusEl
|
* @param {HTMLElement} statusEl
|
||||||
* @param {HTMLElement} statusMessage
|
* @param {Function} onTerminal - Called when the file reaches a terminal state (success or permanent error).
|
||||||
* @returns {Promise<{rateLimited: boolean, retryAfterSeconds: number}>}
|
* @returns {Promise<{rateLimited: boolean, retryAfterSeconds: number}>}
|
||||||
*/
|
*/
|
||||||
function _uploadSingleFile(file, progressBar, statusEl, statusMessage) {
|
function _uploadSingleFile(file, progressBar, statusEl, onTerminal) {
|
||||||
statusEl.textContent = 'Uploading…';
|
statusEl.textContent = 'Uploading…';
|
||||||
statusEl.className = 'text-xs text-gray-600 mt-1';
|
statusEl.className = 'text-xs text-gray-600 mt-1';
|
||||||
progressBar.style.width = '0%';
|
progressBar.style.width = '0%';
|
||||||
@@ -418,7 +437,7 @@ function _uploadSingleFile(file, progressBar, statusEl, statusMessage) {
|
|||||||
statusEl.textContent = `Success: Task ID: ${result.task_id}`;
|
statusEl.textContent = `Success: Task ID: ${result.task_id}`;
|
||||||
statusEl.className = 'text-xs text-green-600 mt-1';
|
statusEl.className = 'text-xs text-green-600 mt-1';
|
||||||
_onUploadSuccess();
|
_onUploadSuccess();
|
||||||
updateOverallStatus(statusMessage);
|
onTerminal();
|
||||||
resolve({ rateLimited: false, retryAfterSeconds: 0 });
|
resolve({ rateLimited: false, retryAfterSeconds: 0 });
|
||||||
|
|
||||||
} else if (xhr.status === 429) {
|
} else if (xhr.status === 429) {
|
||||||
@@ -438,7 +457,7 @@ function _uploadSingleFile(file, progressBar, statusEl, statusMessage) {
|
|||||||
progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full';
|
progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full';
|
||||||
statusEl.textContent = `Error: HTTP ${xhr.status}`;
|
statusEl.textContent = `Error: HTTP ${xhr.status}`;
|
||||||
statusEl.className = 'text-xs text-red-500 mt-1';
|
statusEl.className = 'text-xs text-red-500 mt-1';
|
||||||
updateOverallStatus(statusMessage);
|
onTerminal();
|
||||||
resolve({ rateLimited: false, retryAfterSeconds: 0 });
|
resolve({ rateLimited: false, retryAfterSeconds: 0 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -447,7 +466,7 @@ function _uploadSingleFile(file, progressBar, statusEl, statusMessage) {
|
|||||||
progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full';
|
progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full';
|
||||||
statusEl.textContent = 'Error: Network error';
|
statusEl.textContent = 'Error: Network error';
|
||||||
statusEl.className = 'text-xs text-red-500 mt-1';
|
statusEl.className = 'text-xs text-red-500 mt-1';
|
||||||
updateOverallStatus(statusMessage);
|
onTerminal();
|
||||||
resolve({ rateLimited: false, retryAfterSeconds: 0 });
|
resolve({ rateLimited: false, retryAfterSeconds: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -467,39 +486,6 @@ function validateAndUpload(file, progressContainer, statusMessage) {
|
|||||||
processFiles([file], progressContainer, statusMessage);
|
processFiles([file], progressContainer, statusMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Status helpers ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-calculate and display the overall upload status.
|
|
||||||
* Fires 'allUploadsComplete' when every item has a terminal status.
|
|
||||||
* @param {HTMLElement} statusMessage
|
|
||||||
*/
|
|
||||||
function updateOverallStatus(statusMessage) {
|
|
||||||
if (!statusMessage) return;
|
|
||||||
|
|
||||||
const fileStatuses = document.querySelectorAll('.file-status');
|
|
||||||
let done = 0;
|
|
||||||
const total = fileStatuses.length;
|
|
||||||
|
|
||||||
fileStatuses.forEach((s) => {
|
|
||||||
const t = s.textContent;
|
|
||||||
if (
|
|
||||||
t.startsWith('Success') ||
|
|
||||||
t.startsWith('Error') ||
|
|
||||||
t.startsWith('Unsupported') ||
|
|
||||||
t.startsWith('Exceeds') ||
|
|
||||||
t.startsWith('Failed:')
|
|
||||||
) done++;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (done === total && total > 0) {
|
|
||||||
statusMessage.textContent = `All uploads completed (${done}/${total})`;
|
|
||||||
window.dispatchEvent(new CustomEvent('allUploadsComplete', { detail: { total, completed: done } }));
|
|
||||||
} else {
|
|
||||||
statusMessage.textContent = `Uploading files (${done}/${total})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a byte count for human-readable display.
|
* Format a byte count for human-readable display.
|
||||||
* @param {number} bytes
|
* @param {number} bytes
|
||||||
|
|||||||
Reference in New Issue
Block a user