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