diff --git a/lib/server/server.go b/lib/server/server.go index 937e6d2..c4e1348 100644 --- a/lib/server/server.go +++ b/lib/server/server.go @@ -344,6 +344,8 @@ func iconForMimeType(mimeType string, filename string) string { extension := strings.ToLower(filepath.Ext(filename)) switch { + case extension == ".exe": + return "/static/img/icons/Program Files Icons - PNG/MSONSEXT.DLL_14_6-0.png" case strings.HasPrefix(mimeType, "image/"): return "/static/img/sprites/bitmap.png" case strings.HasPrefix(mimeType, "video/"): @@ -361,7 +363,7 @@ func iconForMimeType(mimeType string, filename string) string { case extension == ".html" || extension == ".css" || extension == ".js": return "/static/img/sprites/frame_web-0.png" default: - return "/static/img/sprites/freepad.png" + return "/static/img/icons/Windows Icons - PNG/ole2.dll_14_DEFICON.png" } } diff --git a/static/css/upload.css b/static/css/upload.css index 0681fd5..97e938e 100644 --- a/static/css/upload.css +++ b/static/css/upload.css @@ -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; } diff --git a/static/js/app.js b/static/js/app.js index 11f70c7..5803d22 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -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"); } }); diff --git a/templates/index.html b/templates/index.html index 5632bca..8e138d4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -48,7 +48,7 @@
No files selected
-