Spaces:
Paused
Paused
| // ========================================== | |
| // PIXELDRIVE v3.2 - APP.JS (Frontend) | |
| // ========================================== | |
| const app = { | |
| user: null, | |
| currentFolder: null, | |
| currentSection: 'files', | |
| files: [], | |
| folders: [], | |
| uploadController: null, | |
| isMobile: window.innerWidth <= 768, | |
| suppressClick: false, | |
| // ---------- INIT ---------- | |
| async init() { | |
| try { | |
| const res = await fetch('/api/me'); | |
| if (res.ok) { | |
| this.user = await res.json(); | |
| this.showApp(); | |
| if (!this.user.tosAccepted) this.showTos(true); | |
| this.loadContent(); | |
| } | |
| } catch (e) { console.error(e); } | |
| document.addEventListener('click', (e) => { | |
| const menu = document.getElementById('context-menu'); | |
| if (!menu.contains(e.target)) menu.style.display = 'none'; | |
| }); | |
| document.getElementById('file-area').addEventListener('contextmenu', (e) => e.preventDefault()); | |
| window.addEventListener('resize', () => { | |
| this.isMobile = window.innerWidth <= 768; | |
| if (!this.isMobile) { | |
| document.getElementById('sidebar').classList.remove('open'); | |
| document.querySelector('.sidebar-overlay').classList.remove('active'); | |
| } | |
| }); | |
| document.getElementById('login-pass').addEventListener('keydown', (e) => { if (e.key === 'Enter') this.login(); }); | |
| document.getElementById('reg-pass2').addEventListener('keydown', (e) => { if (e.key === 'Enter') this.register(); }); | |
| }, | |
| // ---------- UTILS ---------- | |
| esc(s) { | |
| return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); | |
| }, | |
| formatSize(bytes) { | |
| if (bytes < 1024) return bytes + ' B'; | |
| if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + ' KB'; | |
| if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + ' MB'; | |
| return (bytes / 1024 / 1024 / 1024).toFixed(2) + ' GB'; | |
| }, | |
| formatDate(ts) { | |
| if (!ts) return ''; | |
| return new Date(ts).toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); | |
| }, | |
| toast(msg, type) { | |
| type = type || 'info'; | |
| const container = document.getElementById('toast-container'); | |
| const el = document.createElement('div'); | |
| el.className = 'toast ' + type; | |
| el.textContent = msg; | |
| container.appendChild(el); | |
| setTimeout(() => { | |
| el.style.opacity = '0'; | |
| el.style.transition = 'opacity 0.3s'; | |
| setTimeout(() => el.remove(), 300); | |
| }, 3000); | |
| }, | |
| // ---------- AUTH ---------- | |
| toggleAuth(mode) { | |
| document.getElementById('login-form').style.display = mode === 'login' ? 'block' : 'none'; | |
| document.getElementById('register-form').style.display = mode === 'register' ? 'block' : 'none'; | |
| }, | |
| async login() { | |
| const u = document.getElementById('login-user').value.trim(); | |
| const p = document.getElementById('login-pass').value; | |
| if (!u || !p) return this.toast('Champs requis', 'error'); | |
| try { | |
| const res = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ u, p }) }); | |
| const data = await res.json(); | |
| if (data.ok) { | |
| this.user = data.user; | |
| this.showApp(); | |
| if (!this.user.tosAccepted) this.showTos(true); | |
| this.navTo('files'); | |
| } else { | |
| this.toast(data.error || 'Erreur connexion', 'error'); | |
| } | |
| } catch (e) { this.toast('Erreur réseau', 'error'); } | |
| }, | |
| async register() { | |
| const u = document.getElementById('reg-user').value.trim(); | |
| const p = document.getElementById('reg-pass').value; | |
| const p2 = document.getElementById('reg-pass2').value; | |
| const tos = document.getElementById('reg-tos').checked; | |
| if (!u || !p) return this.toast('Champs requis', 'error'); | |
| if (p !== p2) return this.toast('Mots de passe différents', 'error'); | |
| if (!tos) return this.toast('Acceptez les CGU', 'error'); | |
| try { | |
| const res = await fetch('/api/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ u, p, t: true }) }); | |
| const data = await res.json(); | |
| if (data.ok) { | |
| this.toast('Compte créé !', 'success'); | |
| this.toggleAuth('login'); | |
| } else { | |
| this.toast(data.error, 'error'); | |
| } | |
| } catch (e) { this.toast('Erreur', 'error'); } | |
| }, | |
| async logout() { | |
| if (!confirm('Se déconnecter ?')) return; | |
| await fetch('/api/logout', { method: 'POST' }); | |
| location.reload(); | |
| }, | |
| showApp() { | |
| document.getElementById('auth-screen').style.display = 'none'; | |
| document.getElementById('app-screen').style.display = 'flex'; | |
| document.getElementById('user-display').textContent = this.user.username; | |
| }, | |
| // ---------- TOS ---------- | |
| async showTos(force) { | |
| const modal = document.getElementById('tos-modal'); | |
| const text = document.getElementById('tos-text'); | |
| const btn = document.getElementById('tos-accept-btn'); | |
| modal.style.display = 'flex'; | |
| try { | |
| const res = await fetch('/api/tos'); | |
| text.textContent = await res.text(); | |
| } catch (e) { text.textContent = 'Erreur chargement CGU'; } | |
| btn.style.display = force ? 'flex' : 'none'; | |
| }, | |
| hideTos() { | |
| document.getElementById('tos-modal').style.display = 'none'; | |
| }, | |
| async acceptTos() { | |
| await fetch('/api/tos/accept', { method: 'POST' }); | |
| this.user.tosAccepted = true; | |
| this.hideTos(); | |
| this.toast('CGU acceptées', 'success'); | |
| }, | |
| // ---------- NAVIGATION ---------- | |
| toggleSidebar() { | |
| document.getElementById('sidebar').classList.toggle('open'); | |
| document.querySelector('.sidebar-overlay').classList.toggle('active'); | |
| }, | |
| navTo(section) { | |
| this.currentSection = section; | |
| document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active')); | |
| document.getElementById('nav-' + section).classList.add('active'); | |
| if (this.isMobile) this.toggleSidebar(); | |
| const actions = document.getElementById('toolbar-actions'); | |
| actions.style.display = (section === 'files') ? 'flex' : 'none'; | |
| if (section === 'files') { | |
| this.currentFolder = null; | |
| this.loadContent(); | |
| } else if (section === 'shared') { | |
| this.loadSharedLinks(); | |
| } else if (section === 'recent') { | |
| this.loadRecent(); | |
| } | |
| }, | |
| goRoot() { | |
| if (this.currentSection !== 'files') return this.navTo('files'); | |
| this.currentFolder = null; | |
| this.loadContent(); | |
| }, | |
| refresh() { | |
| if (this.currentSection === 'files') this.loadContent(); | |
| else if (this.currentSection === 'shared') this.loadSharedLinks(); | |
| else if (this.currentSection === 'recent') this.loadRecent(); | |
| }, | |
| // ---------- FILES ---------- | |
| async loadContent() { | |
| const area = document.getElementById('file-area'); | |
| area.innerHTML = '<div style="padding: 2rem; text-align: center; color: var(--text-muted);">Chargement...</div>'; | |
| const bc = document.getElementById('breadcrumb'); | |
| bc.innerHTML = '<span onclick="app.goRoot()">Mes Fichiers</span>'; | |
| if (this.currentFolder) { | |
| bc.innerHTML += ' <span class="sep">/</span> <span>' + this.esc(this.currentFolder.name) + '</span>'; | |
| } | |
| try { | |
| const [fRes, dRes] = await Promise.all([ | |
| fetch('/api/files?folder_id=' + (this.currentFolder ? this.currentFolder.id : '')), | |
| fetch('/api/folders?parent_id=' + (this.currentFolder ? this.currentFolder.id : '')) | |
| ]); | |
| this.files = await fRes.json(); | |
| this.folders = await dRes.json(); | |
| this.renderGrid(this.folders, this.files); | |
| } catch (e) { | |
| area.innerHTML = '<div style="padding: 2rem; color: var(--danger); text-align: center;">Erreur de chargement</div>'; | |
| } | |
| }, | |
| async loadRecent() { | |
| const area = document.getElementById('file-area'); | |
| area.innerHTML = '<div style="padding: 2rem; text-align: center; color: var(--text-muted);">Chargement...</div>'; | |
| document.getElementById('breadcrumb').innerHTML = '<span>Récents</span>'; | |
| try { | |
| const all = []; | |
| const walk = async (parentId) => { | |
| const fRes = await fetch('/api/files?folder_id=' + (parentId || '')); | |
| const files = await fRes.json(); | |
| files.forEach(f => all.push(f)); | |
| const dRes = await fetch('/api/folders?parent_id=' + (parentId || '')); | |
| const folders = await dRes.json(); | |
| for (const d of folders) await walk(d.id); | |
| }; | |
| await walk(null); | |
| all.sort((a, b) => (b.updated_at || b.created_at || 0) - (a.updated_at || a.created_at || 0)); | |
| this.renderGrid([], all.slice(0, 20)); | |
| } catch (e) { | |
| area.innerHTML = '<div style="padding: 2rem; color: var(--danger); text-align: center;">Erreur de chargement</div>'; | |
| } | |
| }, | |
| renderGrid(folders, files) { | |
| const area = document.getElementById('file-area'); | |
| area.innerHTML = ''; | |
| if (folders.length === 0 && files.length === 0) { | |
| area.innerHTML = '<div class="empty-state"><svg class="icon icon-lg"><use href="#icon-folder"></use></svg><p>Dossier vide</p></div>'; | |
| return; | |
| } | |
| const grid = document.createElement('div'); | |
| grid.className = 'grid'; | |
| folders.forEach(f => { | |
| const el = document.createElement('div'); | |
| el.className = 'folder-card'; | |
| el.innerHTML = '<svg class="icon"><use href="#icon-folder"></use></svg><span title="' + this.esc(f.name) + '">' + this.esc(f.name) + '</span>'; | |
| el.onclick = () => { this.currentFolder = f; this.loadContent(); }; | |
| el.oncontextmenu = (e) => this.showContextMenu(e, 'folder', f); | |
| grid.appendChild(el); | |
| }); | |
| files.forEach(f => { | |
| const el = document.createElement('div'); | |
| el.className = 'file-card'; | |
| let thumbHtml = '<svg class="icon icon-placeholder"><use href="#icon-file"></use></svg>'; | |
| if (f.mime.startsWith('image/') || f.mime.startsWith('video/')) { | |
| thumbHtml = '<img src="/api/files/' + f.id + '/thumb" alt="" loading="lazy">'; | |
| } | |
| el.innerHTML = | |
| '<div class="file-thumb">' + thumbHtml + '</div>' + | |
| '<div class="file-info">' + | |
| '<div class="file-name" title="' + this.esc(f.name) + '">' + this.esc(f.name) + '</div>' + | |
| '<div class="file-meta"><span>' + this.formatSize(f.size) + '</span><span>' + (f.status === 'ready' ? 'Prêt' : this.esc(f.status)) + '</span></div>' + | |
| '</div>'; | |
| el.onclick = () => { | |
| if (this.suppressClick) { this.suppressClick = false; return; } | |
| if (f.status === 'ready') this.previewFile(f); | |
| }; | |
| el.oncontextmenu = (e) => this.showContextMenu(e, 'file', f); | |
| let pressTimer = null; | |
| el.addEventListener('touchstart', (e) => { | |
| pressTimer = setTimeout(() => { | |
| this.suppressClick = true; | |
| this.showContextMenu(e, 'file', f); | |
| }, 500); | |
| }, { passive: true }); | |
| el.addEventListener('touchend', () => clearTimeout(pressTimer)); | |
| el.addEventListener('touchmove', () => clearTimeout(pressTimer)); | |
| grid.appendChild(el); | |
| }); | |
| area.appendChild(grid); | |
| }, | |
| // ---------- UPLOAD ---------- | |
| async handleUpload(input) { | |
| if (!input.files.length) return; | |
| const file = input.files[0]; | |
| const overlay = document.getElementById('upload-overlay'); | |
| const bar = document.getElementById('upload-progress'); | |
| const fname = document.getElementById('upload-filename'); | |
| const pct = document.getElementById('upload-percent'); | |
| overlay.style.display = 'block'; | |
| fname.textContent = file.name; | |
| bar.style.width = '0%'; | |
| pct.textContent = '0%'; | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| if (this.currentFolder) formData.append('folder_id', this.currentFolder.id); | |
| this.uploadController = new AbortController(); | |
| try { | |
| const res = await fetch('/api/files/upload', { method: 'POST', body: formData, signal: this.uploadController.signal }); | |
| if (!res.ok) throw new Error('Upload failed'); | |
| const data = await res.json(); | |
| this.listenProgress(data.fileId); | |
| } catch (e) { | |
| if (e.name !== 'AbortError') this.toast('Erreur upload', 'error'); | |
| overlay.style.display = 'none'; | |
| } | |
| input.value = ''; | |
| }, | |
| listenProgress(fileId) { | |
| const es = new EventSource('/api/files/' + fileId + '/progress'); | |
| const bar = document.getElementById('upload-progress'); | |
| const pct = document.getElementById('upload-percent'); | |
| const overlay = document.getElementById('upload-overlay'); | |
| es.onmessage = (e) => { | |
| const data = JSON.parse(e.data); | |
| if (data.type === 'progress') { | |
| const p = Math.round(data.progress); | |
| bar.style.width = p + '%'; | |
| pct.textContent = p + '%'; | |
| } | |
| if (data.type === 'done' || data.type === 'error') { | |
| es.close(); | |
| setTimeout(() => { | |
| overlay.style.display = 'none'; | |
| if (this.currentSection === 'files') this.loadContent(); | |
| if (data.type === 'done') this.toast('Upload terminé', 'success'); | |
| else this.toast('Erreur upload', 'error'); | |
| }, 1000); | |
| } | |
| }; | |
| es.onerror = () => { es.close(); overlay.style.display = 'none'; }; | |
| }, | |
| cancelUpload() { | |
| if (this.uploadController) this.uploadController.abort(); | |
| document.getElementById('upload-overlay').style.display = 'none'; | |
| }, | |
| async createFolder() { | |
| const name = prompt('Nom du dossier :'); | |
| if (!name) return; | |
| try { | |
| const res = await fetch('/api/folders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ n: name, p: this.currentFolder ? this.currentFolder.id : null }) }); | |
| if (res.ok) this.loadContent(); | |
| else this.toast('Erreur création', 'error'); | |
| } catch (e) { this.toast('Erreur réseau', 'error'); } | |
| }, | |
| // ---------- PREVIEW ---------- | |
| previewFile(file) { | |
| const modal = document.getElementById('preview-modal'); | |
| const container = document.getElementById('preview-container'); | |
| const dlBtn = document.getElementById('preview-download-btn'); | |
| container.innerHTML = ''; | |
| modal.style.display = 'flex'; | |
| const url = '/api/files/' + file.id + '/download'; | |
| dlBtn.onclick = () => { | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = file.name; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| }; | |
| if (file.mime.startsWith('image/')) { | |
| const img = document.createElement('img'); | |
| img.src = url; | |
| img.id = 'preview-content'; | |
| container.appendChild(img); | |
| } else if (file.mime.startsWith('video/')) { | |
| const vid = document.createElement('video'); | |
| vid.src = url; | |
| vid.controls = true; | |
| vid.autoplay = true; | |
| vid.playsInline = true; | |
| vid.id = 'preview-content'; | |
| container.appendChild(vid); | |
| } else if (file.mime.startsWith('audio/')) { | |
| const aud = document.createElement('audio'); | |
| aud.src = url; | |
| aud.controls = true; | |
| container.appendChild(aud); | |
| } else { | |
| container.innerHTML = '<div style="text-align:center; color:var(--text-main); padding: 2rem;"><svg class="icon" style="width:64px;height:64px;margin-bottom:1rem;"><use href="#icon-file"></use></svg><p style="margin-bottom:1rem; color: var(--text-muted);">Aperçu non disponible</p><p style="font-weight:500;">' + this.esc(file.name) + '</p></div>'; | |
| } | |
| }, | |
| closePreview() { | |
| const container = document.getElementById('preview-container'); | |
| const vid = container.querySelector('video'); | |
| const aud = container.querySelector('audio'); | |
| if (vid) vid.pause(); | |
| if (aud) aud.pause(); | |
| container.innerHTML = ''; | |
| document.getElementById('preview-modal').style.display = 'none'; | |
| }, | |
| // ---------- SHARE MODAL ---------- | |
| async showShareModal(file) { | |
| const modal = document.getElementById('share-modal'); | |
| modal.style.display = 'flex'; | |
| const previewInput = document.getElementById('share-preview-url'); | |
| const downloadInput = document.getElementById('share-download-url'); | |
| const embedInput = document.getElementById('share-embed-code'); | |
| previewInput.value = 'Génération...'; | |
| downloadInput.value = 'Génération...'; | |
| embedInput.value = 'Génération...'; | |
| try { | |
| const res = await fetch('/api/files/' + file.id + '/share', { method: 'POST' }); | |
| if (!res.ok) throw new Error('Erreur serveur'); | |
| const data = await res.json(); | |
| const origin = window.location.origin; | |
| previewInput.value = origin + data.preview; | |
| downloadInput.value = origin + data.download; | |
| let embedCode = ''; | |
| if (file.mime.startsWith('image/')) { | |
| embedCode = '<img src="' + origin + data.download + '" alt="' + this.esc(file.name) + '">'; | |
| } else if (file.mime.startsWith('video/')) { | |
| embedCode = '<video controls src="' + origin + data.download + '" style="max-width:100%"></video>'; | |
| } else if (file.mime.startsWith('audio/')) { | |
| embedCode = '<audio controls src="' + origin + data.download + '"></audio>'; | |
| } else { | |
| embedCode = '<a href="' + origin + data.download + '" download="' + this.esc(file.name) + '">Télécharger ' + this.esc(file.name) + '</a>'; | |
| } | |
| embedInput.value = embedCode; | |
| document.querySelectorAll('.share-copy-btn').forEach(btn => { | |
| btn.onclick = () => this.copyShare(btn.dataset.type); | |
| }); | |
| } catch (e) { | |
| this.toast('Erreur génération des liens', 'error'); | |
| modal.style.display = 'none'; | |
| } | |
| }, | |
| closeShare() { | |
| document.getElementById('share-modal').style.display = 'none'; | |
| }, | |
| async copyShare(type) { | |
| let inputId = ''; | |
| if (type === 'preview') inputId = 'share-preview-url'; | |
| else if (type === 'download') inputId = 'share-download-url'; | |
| else inputId = 'share-embed-code'; | |
| const input = document.getElementById(inputId); | |
| const text = input.value; | |
| if (!text || text === 'Génération...') return; | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| } catch (e) { | |
| input.select(); | |
| document.execCommand('copy'); | |
| } | |
| const btn = document.querySelector('.share-copy-btn[data-type="' + type + '"]'); | |
| if (btn) { | |
| btn.classList.add('copied'); | |
| setTimeout(() => btn.classList.remove('copied'), 1500); | |
| } | |
| this.toast('Lien copié !', 'success'); | |
| }, | |
| // ---------- SECTION PARTAGÉS ---------- | |
| async loadSharedLinks() { | |
| const area = document.getElementById('file-area'); | |
| area.innerHTML = '<div style="padding: 2rem; text-align: center; color: var(--text-muted);">Chargement...</div>'; | |
| document.getElementById('breadcrumb').innerHTML = '<span>Liens Partagés</span>'; | |
| try { | |
| const res = await fetch('/api/share-links'); | |
| const links = await res.json(); | |
| if (links.length === 0) { | |
| area.innerHTML = '<div class="empty-state"><svg class="icon icon-lg"><use href="#icon-share"></use></svg><p>Aucun lien partagé</p><p style="font-size: 0.875rem; margin-top: 0.5rem;">Clic droit sur un fichier → Partager</p></div>'; | |
| return; | |
| } | |
| const grouped = {}; | |
| links.forEach(l => { | |
| if (!grouped[l.file_id]) grouped[l.file_id] = { file_name: l.file_name, mime: l.mime, size: l.size, links: [] }; | |
| grouped[l.file_id].links.push(l); | |
| }); | |
| const list = document.createElement('div'); | |
| list.className = 'share-list'; | |
| Object.entries(grouped).forEach(([fileId, data]) => { | |
| const embedLink = data.links.find(l => l.type === 'embed'); | |
| const downloadLink = data.links.find(l => l.type === 'download'); | |
| const expires = this.formatDate(Math.max(...data.links.map(l => l.expires_at || 0))); | |
| const origin = window.location.origin; | |
| const item = document.createElement('div'); | |
| item.className = 'share-item'; | |
| item.innerHTML = | |
| '<div class="share-item-header">' + | |
| '<div class="share-item-info">' + | |
| '<svg class="icon icon-sm"><use href="#icon-file"></use></svg>' + | |
| '<span class="share-item-name">' + this.esc(data.file_name || 'Fichier supprimé') + '</span>' + | |
| '</div>' + | |
| '<button class="btn-icon share-delete-btn" data-file-id="' + fileId + '" title="Supprimer tous les liens">' + | |
| '<svg class="icon icon-sm"><use href="#icon-trash"></use></svg>' + | |
| '</button>' + | |
| '</div>' + | |
| '<div class="share-item-links">' + | |
| (embedLink ? | |
| '<div class="share-link-row"><span class="share-link-label">Aperçu</span><input type="text" readonly class="form-input share-link-input" value="' + origin + '/s/' + embedLink.token + '"><button class="btn-icon share-copy-btn" data-url="' + origin + '/s/' + embedLink.token + '"><svg class="icon icon-sm"><use href="#icon-link"></use></svg></button></div>' | |
| : '') + | |
| (downloadLink ? | |
| '<div class="share-link-row"><span class="share-link-label">Download</span><input type="text" readonly class="form-input share-link-input" value="' + origin + '/d/' + downloadLink.token + '"><button class="btn-icon share-copy-btn" data-url="' + origin + '/d/' + downloadLink.token + '"><svg class="icon icon-sm"><use href="#icon-link"></use></svg></button></div>' | |
| : '') + | |
| '</div>' + | |
| '<div class="share-item-footer">Expire le ' + expires + '</div>'; | |
| list.appendChild(item); | |
| }); | |
| area.innerHTML = ''; | |
| area.appendChild(list); | |
| area.querySelectorAll('.share-copy-btn').forEach(btn => { | |
| btn.onclick = async () => { | |
| try { | |
| await navigator.clipboard.writeText(btn.dataset.url); | |
| } catch (e) { | |
| const input = btn.previousElementSibling; | |
| input.select(); | |
| document.execCommand('copy'); | |
| } | |
| btn.classList.add('copied'); | |
| setTimeout(() => btn.classList.remove('copied'), 1500); | |
| this.toast('Lien copié !', 'success'); | |
| }; | |
| }); | |
| area.querySelectorAll('.share-delete-btn').forEach(btn => { | |
| btn.onclick = async () => { | |
| if (!confirm('Supprimer tous les liens de ce fichier ?')) return; | |
| const fileId = btn.dataset.fileId; | |
| const tokens = grouped[fileId].links.map(l => l.token); | |
| for (const token of tokens) { | |
| await fetch('/api/share-links/' + token, { method: 'DELETE' }); | |
| } | |
| this.toast('Liens supprimés', 'success'); | |
| this.loadSharedLinks(); | |
| }; | |
| }); | |
| } catch (e) { | |
| area.innerHTML = '<div style="padding: 2rem; color: var(--danger); text-align: center;">Erreur de chargement</div>'; | |
| } | |
| }, | |
| // ---------- CONTEXT MENU ---------- | |
| showContextMenu(e, type, item) { | |
| try { e.preventDefault(); } catch (err) {} | |
| e.stopPropagation(); | |
| const menu = document.getElementById('context-menu'); | |
| menu.style.display = 'block'; | |
| if (this.isMobile) { | |
| menu.style.left = '0'; | |
| menu.style.top = 'auto'; | |
| menu.style.bottom = '0'; | |
| menu.style.right = '0'; | |
| menu.style.width = '100%'; | |
| } else { | |
| const cx = e.pageX || 0; | |
| const cy = e.pageY || 0; | |
| menu.style.left = cx + 'px'; | |
| menu.style.top = cy + 'px'; | |
| menu.style.bottom = 'auto'; | |
| menu.style.right = 'auto'; | |
| menu.style.width = 'auto'; | |
| const rect = menu.getBoundingClientRect(); | |
| if (rect.right > window.innerWidth) menu.style.left = (cx - rect.width) + 'px'; | |
| if (rect.bottom > window.innerHeight) menu.style.top = (cy - rect.height) + 'px'; | |
| } | |
| const isFile = type === 'file'; | |
| document.getElementById('ctx-preview').style.display = isFile ? 'flex' : 'none'; | |
| document.getElementById('ctx-download').style.display = isFile ? 'flex' : 'none'; | |
| document.getElementById('ctx-share').style.display = isFile ? 'flex' : 'none'; | |
| document.getElementById('ctx-preview').onclick = () => { this.closeMenu(); this.previewFile(item); }; | |
| document.getElementById('ctx-download').onclick = () => { | |
| this.closeMenu(); | |
| const a = document.createElement('a'); | |
| a.href = '/api/files/' + item.id + '/download'; | |
| a.download = item.name; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| }; | |
| document.getElementById('ctx-share').onclick = () => { this.closeMenu(); this.showShareModal(item); }; | |
| document.getElementById('ctx-delete').onclick = async () => { | |
| this.closeMenu(); | |
| if (confirm('Supprimer ' + item.name + ' ?')) { | |
| await fetch('/api/' + type + 's/' + item.id, { method: 'DELETE' }); | |
| this.refresh(); | |
| this.toast('Supprimé', 'success'); | |
| } | |
| }; | |
| document.getElementById('ctx-rename').onclick = () => { this.closeMenu(); this.toast('Bientôt disponible', 'info'); }; | |
| document.getElementById('ctx-move').onclick = () => { this.closeMenu(); this.toast('Bientôt disponible', 'info'); }; | |
| }, | |
| closeMenu() { | |
| document.getElementById('context-menu').style.display = 'none'; | |
| } | |
| }; | |
| // ---------- START ---------- | |
| app.init(); |