NathMen12 commited on
Commit
7267898
·
verified ·
1 Parent(s): 8385c0f

Update public/app.js

Browse files
Files changed (1) hide show
  1. public/app.js +124 -42
public/app.js CHANGED
@@ -1,6 +1,19 @@
1
  // ==========================================
2
- // PIXELDRIVE v4.1 - APP.JS (i18n + avatars + settings)
3
  // ==========================================
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  const app = {
5
  user: null, currentFolder: null, currentSection: 'files', sortMode: 'name-asc',
6
  files: [], folders: [], uploadController: null, _uploadCancelled: false,
@@ -8,7 +21,6 @@ const app = {
8
  _dialogResolver: null, _dialogOpts: null,
9
 
10
  async init() {
11
- // ✅ Préférences locales (thème, tri) + i18n
12
  document.body.classList.toggle('light', (localStorage.getItem('pd_theme') || 'dark') === 'light');
13
  this.sortMode = localStorage.getItem('pd_sort') || 'name-asc';
14
  const ss = document.getElementById('sort-select'); if (ss) ss.value = this.sortMode;
@@ -39,6 +51,11 @@ const app = {
39
  formatSize(b) { if (b === null || b === undefined) return '—'; if (b < 1024) return b + ' B'; if (b < 1048576) return (b/1024).toFixed(0) + ' KB'; if (b < 1073741824) return (b/1048576).toFixed(1) + ' MB'; return (b/1073741824).toFixed(2) + ' GB'; },
40
  statusLabel(s) { return ({pending:t('st_pending'), uploading:t('st_uploading'), processing:t('st_processing'), ready:t('st_ready'), error:t('st_error')})[s] || s; },
41
  formatDate(ts) { return ts ? new Date(ts).toLocaleString('fr-FR', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) : ''; },
 
 
 
 
 
42
  toast(msg, type) {
43
  const el = document.createElement('div');
44
  el.className = 'toast ' + (type || 'info');
@@ -47,7 +64,6 @@ const app = {
47
  setTimeout(() => { el.style.opacity = '0'; el.style.transition = 'opacity 0.3s'; setTimeout(() => el.remove(), 300); }, 3500);
48
  },
49
 
50
- // ✅ Lignes composites (texte + lien) traduites
51
  _switchHTML(key, onclick) {
52
  const s = t(key);
53
  const i = s.indexOf('?');
@@ -61,7 +77,7 @@ const app = {
61
  if (sw2) sw2.innerHTML = this._switchHTML('have_account', "app.toggleAuth('login')");
62
  },
63
 
64
- // DIALOGUES CUSTOM
65
  dialog(opts) {
66
  return new Promise(resolve => {
67
  this._dialogResolver = resolve;
@@ -141,7 +157,7 @@ const app = {
141
  hideTos() { document.getElementById('tos-modal').style.display = 'none'; },
142
  async acceptTos() { await fetch('/api/tos/accept', {method:'POST'}); this.user.tosAccepted = true; this.hideTos(); this.toast('CGU ✓', 'success'); },
143
 
144
- // AVATARS
145
  loadAvatar() {
146
  if (!this.user) return;
147
  const url = '/api/users/' + this.user.id + '/avatar?' + Date.now();
@@ -173,7 +189,7 @@ const app = {
173
  this.loadAvatar();
174
  },
175
 
176
- // SETTINGS
177
  openSettings() {
178
  document.getElementById('settings-modal').style.display = 'flex';
179
  document.getElementById('set-lang').value = getLang();
@@ -299,7 +315,7 @@ const app = {
299
  el.addEventListener('touchmove', () => clearTimeout(t2));
300
  },
301
 
302
- // ONGLET PARTAGÉS (3 sections)
303
  async loadSharedPage() {
304
  const area = document.getElementById('file-area');
305
  area.innerHTML = '<div style="padding:2rem;text-align:center;color:var(--text-muted);">' + t('loading') + '</div>';
@@ -345,7 +361,6 @@ const app = {
345
  const list = document.createElement('div'); list.className = 'share-list';
346
  myShares.forEach(sh => {
347
  const name = sh.file_name || sh.folder_name || '—';
348
- const kind = sh.file_id ? t('folder').replace(t('folder'), sh.file_id ? 'Fichier' : 'Dossier') : 'Dossier';
349
  const item = document.createElement('div'); item.className = 'share-item';
350
  item.innerHTML =
351
  '<div class="share-item-header"><div class="share-item-info"><svg class="icon icon-sm"><use href="#' + (sh.file_id ? 'icon-file' : 'icon-folder') + '"></use></svg><div style="min-width:0;"><div class="share-item-name">' + this.esc(name) + '</div><div class="share-item-sub">' + (sh.file_id ? 'Fichier' : 'Dossier') + ' · ' + t('shared_with') + ' <strong style="color:var(--primary);">' + this.esc(sh.shared_with_name) + '</strong></div></div></div><button class="btn-icon myshare-revoke" data-id="' + sh.id + '"><svg class="icon icon-sm"><use href="#icon-trash"></use></svg></button></div>' +
@@ -406,7 +421,9 @@ const app = {
406
  } catch (e) { area.innerHTML = '<div style="padding:2rem;color:var(--danger);text-align:center;">Erreur</div>'; }
407
  },
408
 
409
- // ✅ MULTI-UPLOAD + vitesse
 
 
410
  async handleUpload(input) {
411
  const files = Array.from(input.files);
412
  input.value = '';
@@ -424,44 +441,109 @@ const app = {
424
  },
425
 
426
  uploadOne(file, idx, total) {
427
- return new Promise(async (resolve) => {
428
  const t0 = performance.now();
429
- document.getElementById('upload-count').textContent = '(' + idx + '/' + total + ')';
430
- document.getElementById('upload-filename').textContent = file.name;
431
- document.getElementById('upload-progress').style.width = '0%';
432
- document.getElementById('upload-percent').textContent = '0%';
433
- document.getElementById('upload-speed').textContent = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  const fd = new FormData();
435
  fd.append('file', file);
436
  if (this.currentFolder) fd.append('folder_id', this.currentFolder.id);
437
- this.uploadController = new AbortController();
438
- const finish = (ok) => {
439
- const secs = (performance.now() - t0) / 1000;
440
- const mbps = (file.size / 1048576) / secs;
441
- if (ok && isFinite(mbps)) document.getElementById('upload-speed').textContent = mbps.toFixed(2) + ' MB/s';
442
- setTimeout(() => resolve(ok), 600);
 
 
 
443
  };
444
- try {
445
- const res = await fetch('/api/files/upload', {method:'POST', body: fd, signal: this.uploadController.signal});
446
- if (!res.ok) throw new Error('fail');
447
- const d = await res.json();
448
- const es = new EventSource('/api/files/' + d.fileId + '/progress');
449
- es.onmessage = (e) => {
450
- const data = JSON.parse(e.data);
451
- if (data.type === 'progress') {
452
- document.getElementById('upload-progress').style.width = Math.round(data.progress) + '%';
453
- document.getElementById('upload-percent').textContent = Math.round(data.progress) + '%';
454
- const secs = (performance.now() - t0) / 1000;
455
- if (secs > 0.5) document.getElementById('upload-speed').textContent = ((file.size / 1048576) / secs).toFixed(2) + ' MB/s';
456
- }
457
- if (data.type === 'done') { es.close(); finish(true); }
458
- if (data.type === 'error') { es.close(); this.toast('Erreur: ' + file.name, 'error'); finish(false); }
459
- };
460
- es.onerror = () => { es.close(); finish(false); };
461
- } catch (e) {
462
- if (e.name === 'AbortError') { this._uploadCancelled = true; resolve(false); }
463
- else { this.toast('Erreur upload: ' + file.name, 'error'); finish(false); }
464
- }
 
 
 
 
 
 
 
465
  });
466
  },
467
 
 
1
  // ==========================================
2
+ // PIXELDRIVE v4.2 - APP.JS (progression upload réelle + stats)
3
  // ==========================================
4
+
5
+ // ✅ Nouvelles clés de traduction (injectées dans le dictionnaire existant)
6
+ if (window.I18N) {
7
+ Object.assign(window.I18N.fr, {
8
+ up_elapsed: 'Temps', up_speed: 'Vitesse', up_eta: 'Restant',
9
+ up_done_in: 'Terminé en', up_phase_upload: 'Envoi au serveur', up_phase_process: 'Traitement'
10
+ });
11
+ Object.assign(window.I18N.en, {
12
+ up_elapsed: 'Time', up_speed: 'Speed', up_eta: 'Left',
13
+ up_done_in: 'Done in', up_phase_upload: 'Uploading to server', up_phase_process: 'Processing'
14
+ });
15
+ }
16
+
17
  const app = {
18
  user: null, currentFolder: null, currentSection: 'files', sortMode: 'name-asc',
19
  files: [], folders: [], uploadController: null, _uploadCancelled: false,
 
21
  _dialogResolver: null, _dialogOpts: null,
22
 
23
  async init() {
 
24
  document.body.classList.toggle('light', (localStorage.getItem('pd_theme') || 'dark') === 'light');
25
  this.sortMode = localStorage.getItem('pd_sort') || 'name-asc';
26
  const ss = document.getElementById('sort-select'); if (ss) ss.value = this.sortMode;
 
51
  formatSize(b) { if (b === null || b === undefined) return '—'; if (b < 1024) return b + ' B'; if (b < 1048576) return (b/1024).toFixed(0) + ' KB'; if (b < 1073741824) return (b/1048576).toFixed(1) + ' MB'; return (b/1073741824).toFixed(2) + ' GB'; },
52
  statusLabel(s) { return ({pending:t('st_pending'), uploading:t('st_uploading'), processing:t('st_processing'), ready:t('st_ready'), error:t('st_error')})[s] || s; },
53
  formatDate(ts) { return ts ? new Date(ts).toLocaleString('fr-FR', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) : ''; },
54
+ fmtDur(s) {
55
+ s = Math.max(0, Math.round(s));
56
+ const m = Math.floor(s / 60), r = s % 60;
57
+ return m > 0 ? m + 'min ' + String(r).padStart(2, '0') + 's' : r + 's';
58
+ },
59
  toast(msg, type) {
60
  const el = document.createElement('div');
61
  el.className = 'toast ' + (type || 'info');
 
64
  setTimeout(() => { el.style.opacity = '0'; el.style.transition = 'opacity 0.3s'; setTimeout(() => el.remove(), 300); }, 3500);
65
  },
66
 
 
67
  _switchHTML(key, onclick) {
68
  const s = t(key);
69
  const i = s.indexOf('?');
 
77
  if (sw2) sw2.innerHTML = this._switchHTML('have_account', "app.toggleAuth('login')");
78
  },
79
 
80
+ // ---------- DIALOGUES ----------
81
  dialog(opts) {
82
  return new Promise(resolve => {
83
  this._dialogResolver = resolve;
 
157
  hideTos() { document.getElementById('tos-modal').style.display = 'none'; },
158
  async acceptTos() { await fetch('/api/tos/accept', {method:'POST'}); this.user.tosAccepted = true; this.hideTos(); this.toast('CGU ✓', 'success'); },
159
 
160
+ // ---------- AVATARS ----------
161
  loadAvatar() {
162
  if (!this.user) return;
163
  const url = '/api/users/' + this.user.id + '/avatar?' + Date.now();
 
189
  this.loadAvatar();
190
  },
191
 
192
+ // ---------- SETTINGS ----------
193
  openSettings() {
194
  document.getElementById('settings-modal').style.display = 'flex';
195
  document.getElementById('set-lang').value = getLang();
 
315
  el.addEventListener('touchmove', () => clearTimeout(t2));
316
  },
317
 
318
+ // ---------- PARTAGÉS ----------
319
  async loadSharedPage() {
320
  const area = document.getElementById('file-area');
321
  area.innerHTML = '<div style="padding:2rem;text-align:center;color:var(--text-muted);">' + t('loading') + '</div>';
 
361
  const list = document.createElement('div'); list.className = 'share-list';
362
  myShares.forEach(sh => {
363
  const name = sh.file_name || sh.folder_name || '—';
 
364
  const item = document.createElement('div'); item.className = 'share-item';
365
  item.innerHTML =
366
  '<div class="share-item-header"><div class="share-item-info"><svg class="icon icon-sm"><use href="#' + (sh.file_id ? 'icon-file' : 'icon-folder') + '"></use></svg><div style="min-width:0;"><div class="share-item-name">' + this.esc(name) + '</div><div class="share-item-sub">' + (sh.file_id ? 'Fichier' : 'Dossier') + ' · ' + t('shared_with') + ' <strong style="color:var(--primary);">' + this.esc(sh.shared_with_name) + '</strong></div></div></div><button class="btn-icon myshare-revoke" data-id="' + sh.id + '"><svg class="icon icon-sm"><use href="#icon-trash"></use></svg></button></div>' +
 
421
  } catch (e) { area.innerHTML = '<div style="padding:2rem;color:var(--danger);text-align:center;">Erreur</div>'; }
422
  },
423
 
424
+ // ==========================================
425
+ // ✅ MULTI-UPLOAD AVEC PROGRESSION RÉELLE (XHR)
426
+ // ==========================================
427
  async handleUpload(input) {
428
  const files = Array.from(input.files);
429
  input.value = '';
 
441
  },
442
 
443
  uploadOne(file, idx, total) {
444
+ return new Promise((resolve) => {
445
  const t0 = performance.now();
446
+ let uploadedBytes = 0; // octets envoyés navigateur → serveur
447
+ let phase = 'upload'; // 'upload' puis 'process'
448
+ let overallPct = 0; // % global (0-50 envoi, 50-100 traitement)
449
+
450
+ const countEl = document.getElementById('upload-count');
451
+ const nameEl = document.getElementById('upload-filename');
452
+ const barEl = document.getElementById('upload-progress');
453
+ const pctEl = document.getElementById('upload-percent');
454
+ const speedEl = document.getElementById('upload-speed');
455
+
456
+ countEl.textContent = '(' + idx + '/' + total + ')';
457
+ nameEl.textContent = file.name;
458
+ barEl.style.width = '0%';
459
+ pctEl.textContent = '0%';
460
+ speedEl.textContent = '';
461
+
462
+ // ✅ Ligne de stats injectée (temps / vitesse / restant)
463
+ let statsEl = document.getElementById('upload-stats');
464
+ if (!statsEl) {
465
+ statsEl = document.createElement('div');
466
+ statsEl.id = 'upload-stats';
467
+ statsEl.className = 'upload-status';
468
+ statsEl.style.fontSize = '0.75rem';
469
+ statsEl.style.marginTop = '0.4rem';
470
+ document.getElementById('upload-overlay').appendChild(statsEl);
471
+ }
472
+ statsEl.textContent = '';
473
+
474
+ // ✅ Chronomètre + stats live (2x par seconde)
475
+ const ticker = setInterval(() => {
476
+ const elapsed = (performance.now() - t0) / 1000;
477
+ let speed = 0, eta = '—';
478
+ if (phase === 'upload') {
479
+ if (elapsed > 0.3 && uploadedBytes > 0) {
480
+ speed = (uploadedBytes / 1048576) / elapsed;
481
+ if (speed > 0.01) eta = '~' + this.fmtDur((file.size - uploadedBytes) / (uploadedBytes / elapsed));
482
+ }
483
+ } else {
484
+ speed = (file.size / 1048576) / Math.max(elapsed, 0.1);
485
+ if (overallPct > 52) eta = '~' + this.fmtDur(elapsed * (100 - overallPct) / overallPct);
486
+ }
487
+ const speedTxt = speed > 0 ? speed.toFixed(2) + ' MB/s' : '…';
488
+ speedEl.textContent = speedTxt;
489
+ statsEl.textContent = t('up_elapsed') + ': ' + this.fmtDur(elapsed) +
490
+ ' · ' + t('up_speed') + ': ' + speedTxt +
491
+ ' · ' + t('up_eta') + ': ' + eta +
492
+ ' · ' + (phase === 'upload' ? t('up_phase_upload') : t('up_phase_process'));
493
+ }, 500);
494
+
495
+ const finish = (ok) => {
496
+ clearInterval(ticker);
497
+ const elapsed = (performance.now() - t0) / 1000;
498
+ const speed = (file.size / 1048576) / Math.max(elapsed, 0.1);
499
+ statsEl.textContent = t('up_done_in') + ' ' + this.fmtDur(elapsed) + ' · ' + speed.toFixed(2) + ' MB/s';
500
+ speedEl.textContent = speed.toFixed(2) + ' MB/s';
501
+ setTimeout(() => resolve(ok), 900);
502
+ };
503
+
504
+ // ✅ XHR = vraie progression d'upload (fetch ne la donne pas !)
505
+ const xhr = new XMLHttpRequest();
506
  const fd = new FormData();
507
  fd.append('file', file);
508
  if (this.currentFolder) fd.append('folder_id', this.currentFolder.id);
509
+
510
+ xhr.open('POST', '/api/files/upload');
511
+ xhr.upload.onprogress = (e) => {
512
+ if (e.lengthComputable) {
513
+ uploadedBytes = e.loaded;
514
+ overallPct = (e.loaded / e.total) * 50; // phase 1 = 0→50%
515
+ barEl.style.width = overallPct + '%';
516
+ pctEl.textContent = Math.round(overallPct) + '%';
517
+ }
518
  };
519
+ xhr.onload = () => {
520
+ if (xhr.status === 200) {
521
+ let d;
522
+ try { d = JSON.parse(xhr.responseText); } catch (e) { return finish(false); }
523
+ phase = 'process';
524
+ // Phase 2 = 50→100% (SSE serveur)
525
+ const es = new EventSource('/api/files/' + d.fileId + '/progress');
526
+ es.onmessage = (ev) => {
527
+ const data = JSON.parse(ev.data);
528
+ if (data.type === 'progress') {
529
+ overallPct = 50 + (data.progress / 2);
530
+ barEl.style.width = overallPct + '%';
531
+ pctEl.textContent = Math.round(overallPct) + '%';
532
+ }
533
+ if (data.type === 'done') { es.close(); barEl.style.width = '100%'; pctEl.textContent = '100%'; finish(true); }
534
+ if (data.type === 'error') { es.close(); this.toast('Erreur: ' + file.name, 'error'); finish(false); }
535
+ };
536
+ es.onerror = () => { es.close(); finish(false); };
537
+ } else {
538
+ this.toast('Erreur upload: ' + file.name, 'error');
539
+ finish(false);
540
+ }
541
+ };
542
+ xhr.onerror = () => { this.toast('Erreur réseau: ' + file.name, 'error'); finish(false); };
543
+ xhr.onabort = () => { this._uploadCancelled = true; finish(false); };
544
+
545
+ this.uploadController = { abort: () => xhr.abort() };
546
+ xhr.send(fd);
547
  });
548
  },
549