feat(ui): add overall upload progress and improve file icons
- Track per-file loaded bytes and compute an overall upload percentage - Add overall progress bar/percent styling and resize upload window to fit - Hide the upload result section until a share URL is available - Use a specific icon for .exe files and update the default fallback iconfeat(ui): add overall upload progress and improve file icons - Track per-file loaded bytes and compute an overall upload percentage - Add overall progress bar/percent styling and resize upload window to fit - Hide the upload result section until a share URL is available - Use a specific icon for .exe files and update the default fallback icon
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
.upload-window {
|
||||
width: 520px;
|
||||
height: 456px;
|
||||
height: 486px;
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
@@ -145,6 +145,10 @@
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.upload-result.is-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.upload-result-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -274,6 +278,48 @@
|
||||
padding: 0 8px 8px;
|
||||
}
|
||||
|
||||
.upload-overall {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 42px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px 8px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.upload-overall-track {
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
border-top: 2px solid #808080;
|
||||
border-left: 2px solid #808080;
|
||||
border-right: 2px solid #ffffff;
|
||||
border-bottom: 2px solid #ffffff;
|
||||
}
|
||||
|
||||
.upload-overall-bar {
|
||||
display: block;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
background-color: #000078;
|
||||
background-image: repeating-linear-gradient(
|
||||
to right,
|
||||
#000078 0,
|
||||
#000078 10px,
|
||||
#c0c0c0 10px,
|
||||
#c0c0c0 12px
|
||||
);
|
||||
}
|
||||
|
||||
.upload-overall-percent {
|
||||
min-width: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.upload-statusbar {
|
||||
grid-template-columns: 1fr 96px;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ const dropzone = document.querySelector(".upload-dropzone");
|
||||
const uploadForm = document.querySelector(".upload-form");
|
||||
const uploadStatus = document.querySelector(".upload-statusbar span:first-child");
|
||||
const boxStatus = document.querySelector(".upload-statusbar span:last-child");
|
||||
const uploadResult = document.querySelector(".upload-result");
|
||||
const boxLink = document.querySelector("#upload-box-link");
|
||||
const shareButton = document.querySelector("#upload-share-button");
|
||||
const overallProgressBar = document.querySelector(".upload-overall-bar");
|
||||
const overallProgressPercent = document.querySelector(".upload-overall-percent");
|
||||
|
||||
let selectedFiles = [];
|
||||
let statusTimer = null;
|
||||
@@ -62,6 +65,10 @@ function setBoxStatus(message) {
|
||||
function setBoxLink(path) {
|
||||
shareURL = path ? new URL(path, window.location.origin).toString() : "";
|
||||
|
||||
if (uploadResult) {
|
||||
uploadResult.classList.toggle("is-hidden", !shareURL);
|
||||
}
|
||||
|
||||
if (boxLink) {
|
||||
boxLink.href = shareURL || "#";
|
||||
boxLink.textContent = shareURL || "Waiting for upload";
|
||||
@@ -75,6 +82,25 @@ function setBoxLink(path) {
|
||||
}
|
||||
}
|
||||
|
||||
function setOverallProgress(percent) {
|
||||
const clampedPercent = Math.max(0, Math.min(100, percent));
|
||||
const displayPercent = `${Math.round(clampedPercent)}%`;
|
||||
|
||||
if (overallProgressBar) {
|
||||
overallProgressBar.style.width = displayPercent;
|
||||
}
|
||||
|
||||
if (overallProgressPercent) {
|
||||
overallProgressPercent.textContent = displayPercent;
|
||||
}
|
||||
}
|
||||
|
||||
function updateOverallProgress() {
|
||||
const totalBytes = selectedFiles.reduce((total, selectedFile) => total + selectedFile.file.size, 0);
|
||||
const loadedBytes = selectedFiles.reduce((total, selectedFile) => total + selectedFile.loaded, 0);
|
||||
setOverallProgress(totalBytes > 0 ? (loadedBytes / totalBytes) * 100 : 0);
|
||||
}
|
||||
|
||||
function updateFileCount() {
|
||||
if (fileCount) {
|
||||
fileCount.textContent = `${selectedFiles.length} ${selectedFiles.length === 1 ? "file" : "files"}`;
|
||||
@@ -121,6 +147,7 @@ function createFileRow(selectedFile) {
|
||||
function updateSelectedFiles(files) {
|
||||
selectedFiles = Array.from(files || []).map((file) => ({
|
||||
file,
|
||||
loaded: 0,
|
||||
row: null,
|
||||
uploaded: false,
|
||||
failed: false,
|
||||
@@ -142,6 +169,7 @@ function updateSelectedFiles(files) {
|
||||
updateStatus("Ready");
|
||||
setBoxStatus("WarpBox");
|
||||
setBoxLink("");
|
||||
setOverallProgress(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,6 +182,7 @@ function updateSelectedFiles(files) {
|
||||
updateStatus("Files selected");
|
||||
setBoxStatus("WarpBox");
|
||||
setBoxLink("");
|
||||
setOverallProgress(0);
|
||||
}
|
||||
|
||||
async function createBox() {
|
||||
@@ -174,6 +203,8 @@ function uploadFile(boxID, selectedFile, onComplete) {
|
||||
xhr.open("POST", `/box/${boxID}/upload`);
|
||||
|
||||
xhr.upload.addEventListener("loadstart", () => {
|
||||
selectedFile.loaded = 0;
|
||||
updateOverallProgress();
|
||||
setRowProgress(selectedFile.row, 2);
|
||||
});
|
||||
|
||||
@@ -182,6 +213,8 @@ function uploadFile(boxID, selectedFile, onComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedFile.loaded = Math.min(event.loaded, selectedFile.file.size);
|
||||
updateOverallProgress();
|
||||
setRowProgress(selectedFile.row, (event.loaded / event.total) * 100);
|
||||
});
|
||||
|
||||
@@ -194,7 +227,9 @@ function uploadFile(boxID, selectedFile, onComplete) {
|
||||
}
|
||||
|
||||
selectedFile.uploaded = true;
|
||||
selectedFile.loaded = selectedFile.file.size;
|
||||
selectedFile.row.classList.add("is-uploaded");
|
||||
updateOverallProgress();
|
||||
setRowProgress(selectedFile.row, 100);
|
||||
onComplete();
|
||||
resolve();
|
||||
@@ -257,17 +292,19 @@ if (uploadForm) {
|
||||
selectedFiles.forEach((selectedFile) => {
|
||||
selectedFile.uploaded = false;
|
||||
selectedFile.failed = false;
|
||||
selectedFile.loaded = 0;
|
||||
selectedFile.row.classList.remove("is-uploaded", "is-failed");
|
||||
setRowProgress(selectedFile.row, 0);
|
||||
});
|
||||
|
||||
setBoxLink("");
|
||||
setOverallProgress(0);
|
||||
updateStatus(`${statusPrefix()} Uploading.`);
|
||||
animateUploadStatus(statusPrefix);
|
||||
|
||||
try {
|
||||
const box = await createBox();
|
||||
setBoxStatus(box.box_url);
|
||||
setBoxLink(box.box_url);
|
||||
|
||||
await Promise.allSettled(selectedFiles.map((selectedFile) => {
|
||||
return uploadFile(box.box_id, selectedFile, () => {
|
||||
@@ -278,13 +315,19 @@ if (uploadForm) {
|
||||
stopStatusAnimation();
|
||||
const failedCount = selectedFiles.filter((selectedFile) => selectedFile.failed).length;
|
||||
if (failedCount > 0) {
|
||||
if (completedCount > 0) {
|
||||
setBoxLink(box.box_url);
|
||||
}
|
||||
updateStatus(`${completedCount}/${totalCount} Uploaded, ${failedCount} failed`);
|
||||
return;
|
||||
}
|
||||
|
||||
setBoxLink(box.box_url);
|
||||
setOverallProgress(100);
|
||||
updateStatus(`${completedCount}/${totalCount} Uploaded`);
|
||||
} catch (error) {
|
||||
stopStatusAnimation();
|
||||
setBoxLink("");
|
||||
updateStatus("Upload failed");
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user