feat(upload): add transfer rate tracking and 6-hour expiry option
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m47s

- Implement real-time transfer rate tracking and display upload speed (e.g., Mb/s) in the progress status.
- Add a 6-hour (360 minutes) option to the upload expiry selection ladder.
- Fix an issue where the "new upload" button remained visible by explicitly toggling its display style and adding a CSS fallback for the `hidden` attribute.
This commit is contained in:
2026-06-02 22:41:59 +03:00
parent 17c31be8b4
commit f698ba516d
3 changed files with 62 additions and 11 deletions

View File

@@ -117,9 +117,7 @@
uploadQueue.hidden = true;
uploadQueue.replaceChildren();
}
if (newUpload) {
newUpload.hidden = true;
}
updateNewUploadVisibility();
if (fileSummary) {
fileSummary.textContent = "Upload complete.";
}
@@ -189,9 +187,16 @@
uploadQueue.hidden = true;
uploadQueue.replaceChildren();
}
if (newUpload) {
newUpload.hidden = !(resumeMode && recoveredDraft);
updateNewUploadVisibility();
}
function updateNewUploadVisibility() {
if (!newUpload) {
return;
}
const visible = Boolean(resumeMode && recoveredDraft);
newUpload.hidden = !visible;
newUpload.style.display = visible ? "" : "none";
}
function setLoading(isLoading, submit) {
@@ -216,6 +221,41 @@
}
}
function updateUploadProgress(percent, bytesPerSecond) {
const clamped = Math.max(0, Math.min(100, Math.round(percent || 0)));
const rate = formatTransferRate(bytesPerSecond);
updateStatus(rate ? `${clamped}% · ${rate}` : `${clamped}%`);
}
function createTransferRateTracker(initialBytes) {
const startedAt = performance.now();
const baseline = Math.max(0, initialBytes || 0);
let lastRate = 0;
return function track(currentBytes) {
const elapsedSeconds = (performance.now() - startedAt) / 1000;
const transferred = Math.max(0, (currentBytes || 0) - baseline);
if (elapsedSeconds < 0.25 || transferred <= 0) {
return lastRate;
}
lastRate = transferred / elapsedSeconds;
return lastRate;
};
}
function formatTransferRate(bytesPerSecond) {
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) {
return "";
}
const units = ["b/s", "Kb/s", "Mb/s", "Gb/s"];
let value = bytesPerSecond * 8;
let unit = 0;
while (value >= 1000 && unit < units.length - 1) {
value /= 1000;
unit += 1;
}
return `${value >= 10 || unit === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unit]}`;
}
function renderResult(payload) {
if (!result || !resultList || !resultMeta || !openBox) {
return;
@@ -248,16 +288,18 @@
function uploadWithProgress(url, formData, files) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
const rateTracker = createTransferRateTracker(0);
request.open("POST", url);
request.setRequestHeader("Accept", "application/json");
request.upload.addEventListener("progress", (event) => {
const rate = rateTracker(event.loaded || 0);
if (!event.lengthComputable) {
updateStatus("Uploading...");
updateStatus(rate > 0 ? `Uploading · ${formatTransferRate(rate)}` : "Uploading...");
return;
}
const percent = Math.round((event.loaded / event.total) * 100);
updateStatus(`${percent}%`);
updateUploadProgress(percent, rate);
setTotalProgress(percent);
setFileProgress(files, percent);
});
@@ -348,7 +390,9 @@
completedByFile[index] = uploadedBytesForSessionFile(sessionFile, session.chunkSize);
setSingleFileProgress(index, files[index], percentForBytes(completedByFile[index], files[index].size));
});
setTotalProgress(percentForBytes(completedByFile.reduce((sum, bytes) => sum + bytes, 0), totalBytes));
const initiallyUploadedBytes = completedByFile.reduce((sum, bytes) => sum + bytes, 0);
const rateTracker = createTransferRateTracker(initiallyUploadedBytes);
setTotalProgress(percentForBytes(initiallyUploadedBytes, totalBytes));
for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
const file = files[fileIndex];
@@ -362,9 +406,11 @@
const end = Math.min(file.size, start + session.chunkSize);
await uploadChunkWithRetry(session, sessionFile, chunkIndex, file.slice(start, end), (loaded) => {
const currentTotal = completedByFile.reduce((sum, bytes) => sum + bytes, 0) + loaded;
setTotalProgress(percentForBytes(currentTotal, totalBytes));
const percent = percentForBytes(currentTotal, totalBytes);
const rate = rateTracker(currentTotal);
setTotalProgress(percent);
setSingleFileProgress(fileIndex, file, percentForBytes(completedByFile[fileIndex] + loaded, file.size));
updateStatus(`${percentForBytes(currentTotal, totalBytes)}%`);
updateUploadProgress(percent, rate);
});
completedByFile[fileIndex] += end - start;
uploaded.add(chunkIndex);
@@ -749,6 +795,7 @@
selectedFiles = [];
renderResumeQueue(recoveredDraft.session, selectedFiles);
updateSelectedState();
updateNewUploadVisibility();
updateStatus("Drop or reselect missing files to continue. Extra files will be added to this upload.");
}