소스 검색

Improve multi-file upload UX with batch progress and summary toast

- Extract uploadFiles(files) helper used by both file picker and drop zone
- Shows "Uploading N/total: filename" progress during batch uploads
- Per-file errors show briefly (1.2 s) without interrupting the batch
- Single summary toast at the end: "Uploaded 5 files" or "3/5 — 2 failed"
- Single-file behaviour unchanged (no counter shown)

No backend restart required (frontend-only change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
k4be 10 시간 전
부모
커밋
2f0769b6b3
1개의 변경된 파일32개의 추가작업 그리고 21개의 파일을 삭제
  1. 32 21
      gpx-vis-frontend/js/browser.js

+ 32 - 21
gpx-vis-frontend/js/browser.js

@@ -420,24 +420,46 @@ const Browser = (() => {
 
   // ===== Upload =====
 
-  async function handleFileUpload(e) {
-    const files = Array.from(e.target.files);
+  async function uploadFiles(files) {
     if (files.length === 0) return;
-    e.target.value = '';
-
-    for (const file of files) {
-      showUploadToast(`Uploading ${file.name}...`);
+    const total = files.length;
+    let failed = 0;
+
+    for (let i = 0; i < files.length; i++) {
+      const file = files[i];
+      showUploadToast(total > 1
+        ? `Uploading ${i + 1}/${total}: ${file.name}`
+        : `Uploading ${file.name}…`);
       try {
         await API.uploadTrack(file, selectedDirId, null);
-        showToast(`Uploaded: ${file.name}`, 'success');
       } catch (err) {
-        showToast(`Error uploading ${file.name}: ${err.message}`, 'error');
+        failed++;
+        showToast(`Failed: ${file.name} — ${err.message}`, 'error');
+        // Let the error toast show briefly before moving on
+        await new Promise(r => setTimeout(r, 1200));
       }
     }
-    hideUploadToast();
+
+    const ok = total - failed;
+    if (total === 1) {
+      failed ? hideUploadToast() : showToast('Uploaded successfully', 'success');
+    } else {
+      showToast(
+        failed === 0
+          ? `Uploaded ${ok} file${ok !== 1 ? 's' : ''}`
+          : `Uploaded ${ok}/${total} — ${failed} failed`,
+        failed ? 'error' : 'success'
+      );
+    }
     await reload();
   }
 
+  async function handleFileUpload(e) {
+    const files = Array.from(e.target.files);
+    e.target.value = '';
+    await uploadFiles(files);
+  }
+
   function setupDropZone() {
     const mapContainer = document.getElementById('map-container');
     mapContainer.addEventListener('dragover', (e) => {
@@ -456,18 +478,7 @@ const Browser = (() => {
       e.preventDefault();
       mapContainer.classList.remove('drag-over');
       const files = Array.from(e.dataTransfer.files).filter(f => f.name.toLowerCase().endsWith('.gpx'));
-      if (files.length === 0) return;
-      for (const file of files) {
-        showUploadToast(`Uploading ${file.name}...`);
-        try {
-          await API.uploadTrack(file, selectedDirId, null);
-          showToast(`Uploaded: ${file.name}`, 'success');
-        } catch (err) {
-          showToast(`Error: ${err.message}`, 'error');
-        }
-      }
-      hideUploadToast();
-      await reload();
+      await uploadFiles(files);
     });
   }