Sanjana Vekhande commited on
Commit
d94bead
·
1 Parent(s): 3f0cc99

add download progress bar

Browse files
Files changed (1) hide show
  1. frontend/index.html +112 -4
frontend/index.html CHANGED
@@ -470,13 +470,61 @@ input[type="range"]::-webkit-slider-thumb:hover {
470
  transition: all 0.15s;
471
  }
472
 
473
- .btn-download:hover {
474
  background: var(--teal);
475
  color: white;
476
  transform: translateY(-1px);
477
  box-shadow: var(--shadow-md);
478
  }
479
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  /* ── Footer ──────────────────────────── */
481
  footer {
482
  margin-top: 4rem;
@@ -636,6 +684,12 @@ footer a:hover { color: var(--ink); }
636
  <button class="btn-download" id="downloadBtn" onclick="triggerDownload()">
637
  ⬇ Download Bootstrapped ZIP
638
  </button>
 
 
 
 
 
 
639
  </div>
640
  </div>
641
 
@@ -787,19 +841,73 @@ async function startJob() {
787
  async function triggerDownload() {
788
  const jobId = document.getElementById('downloadBtn').dataset.jobId;
789
  if (!jobId) return;
 
 
 
 
 
 
 
 
 
 
 
 
790
  try {
791
  const res = await fetch(`/api/download/${jobId}`);
792
  if (!res.ok) throw new Error('Download failed');
793
- const blob = await res.blob();
794
- const url = URL.createObjectURL(blob);
795
- const a = document.createElement('a');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  a.href = url;
797
  a.download = 'geneExpr_bootstrapped.zip';
798
  document.body.appendChild(a);
799
  a.click();
800
  document.body.removeChild(a);
801
  URL.revokeObjectURL(url);
 
 
 
 
 
 
 
 
802
  } catch (e) {
 
 
 
803
  alert('Download failed: ' + e.message);
804
  }
805
  }
 
470
  transition: all 0.15s;
471
  }
472
 
473
+ .btn-download:hover:not(:disabled) {
474
  background: var(--teal);
475
  color: white;
476
  transform: translateY(-1px);
477
  box-shadow: var(--shadow-md);
478
  }
479
 
480
+ .btn-download:disabled {
481
+ opacity: 0.7;
482
+ cursor: not-allowed;
483
+ transform: none;
484
+ }
485
+
486
+ /* Download progress bar */
487
+ .download-progress-wrap {
488
+ margin-top: 0.65rem;
489
+ display: none;
490
+ }
491
+
492
+ .download-progress-wrap.show { display: block; }
493
+
494
+ .download-progress-track {
495
+ height: 4px;
496
+ background: var(--border);
497
+ border-radius: 2px;
498
+ overflow: hidden;
499
+ }
500
+
501
+ .download-progress-fill {
502
+ height: 100%;
503
+ background: linear-gradient(90deg, var(--teal), #6B8FCF);
504
+ border-radius: 2px;
505
+ width: 0%;
506
+ transition: width 0.3s ease;
507
+ }
508
+
509
+ .download-progress-label {
510
+ font-size: 12px;
511
+ color: var(--ink2);
512
+ margin-top: 0.3rem;
513
+ text-align: center;
514
+ }
515
+
516
+ @keyframes indeterminate {
517
+ 0% { left: -40%; width: 40%; }
518
+ 60% { left: 100%; width: 40%; }
519
+ 100% { left: 100%; width: 40%; }
520
+ }
521
+
522
+ .download-progress-fill.indeterminate {
523
+ position: relative;
524
+ width: 40%;
525
+ animation: indeterminate 1.4s ease infinite;
526
+ }
527
+
528
  /* ── Footer ──────────────────────────── */
529
  footer {
530
  margin-top: 4rem;
 
684
  <button class="btn-download" id="downloadBtn" onclick="triggerDownload()">
685
  ⬇ Download Bootstrapped ZIP
686
  </button>
687
+ <div class="download-progress-wrap" id="dlProgressWrap">
688
+ <div class="download-progress-track">
689
+ <div class="download-progress-fill indeterminate" id="dlProgressFill"></div>
690
+ </div>
691
+ <div class="download-progress-label" id="dlProgressLabel">Preparing ZIP…</div>
692
+ </div>
693
  </div>
694
  </div>
695
 
 
841
  async function triggerDownload() {
842
  const jobId = document.getElementById('downloadBtn').dataset.jobId;
843
  if (!jobId) return;
844
+
845
+ const btn = document.getElementById('downloadBtn');
846
+ const wrap = document.getElementById('dlProgressWrap');
847
+ const fill = document.getElementById('dlProgressFill');
848
+ const label = document.getElementById('dlProgressLabel');
849
+
850
+ btn.disabled = true;
851
+ btn.textContent = 'Preparing download…';
852
+ wrap.classList.add('show');
853
+ fill.classList.add('indeterminate');
854
+ label.textContent = 'Fetching ZIP from server…';
855
+
856
  try {
857
  const res = await fetch(`/api/download/${jobId}`);
858
  if (!res.ok) throw new Error('Download failed');
859
+
860
+ const contentLength = res.headers.get('Content-Length');
861
+ let blob;
862
+
863
+ if (contentLength) {
864
+ // Real progress from Content-Length
865
+ const total = parseInt(contentLength);
866
+ const chunks = [];
867
+ let received = 0;
868
+ const reader = res.body.getReader();
869
+ fill.classList.remove('indeterminate');
870
+
871
+ while (true) {
872
+ const { done, value } = await reader.read();
873
+ if (done) break;
874
+ chunks.push(value);
875
+ received += value.length;
876
+ const pct = Math.min(Math.round(received / total * 100), 99);
877
+ fill.style.width = pct + '%';
878
+ label.textContent = `Downloading… ${pct}%`;
879
+ }
880
+ blob = new Blob(chunks, { type: 'application/zip' });
881
+ } else {
882
+ // No content-length — just show spinner until done
883
+ label.textContent = 'Downloading…';
884
+ blob = await res.blob();
885
+ }
886
+
887
+ fill.classList.remove('indeterminate');
888
+ fill.style.width = '100%';
889
+ label.textContent = 'Done!';
890
+
891
+ const url = URL.createObjectURL(blob);
892
+ const a = document.createElement('a');
893
  a.href = url;
894
  a.download = 'geneExpr_bootstrapped.zip';
895
  document.body.appendChild(a);
896
  a.click();
897
  document.body.removeChild(a);
898
  URL.revokeObjectURL(url);
899
+
900
+ setTimeout(() => {
901
+ btn.disabled = false;
902
+ btn.textContent = '⬇ Download Bootstrapped ZIP';
903
+ wrap.classList.remove('show');
904
+ fill.style.width = '0%';
905
+ }, 2000);
906
+
907
  } catch (e) {
908
+ btn.disabled = false;
909
+ btn.textContent = '⬇ Download Bootstrapped ZIP';
910
+ wrap.classList.remove('show');
911
  alert('Download failed: ' + e.message);
912
  }
913
  }