Spaces:
Paused
Paused
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>PixelDrive</title> | |
| <style> | |
| :root{--bg:#0d1117;--fg:#c9d1d9;--accent:#58a6ff;--card:#161b22;--border:#30363d;--danger:#f85149;--success:#3fb950;--warn:#d29922;--menu-bg:#21262d;--menu-border:#30363d;--img:#58a6ff;--vid:#a371f7;--aud:#f78166;--pdf:#d29922;--arc:#8b949e;--code:#3fb950;} | |
| *{box-sizing:border-box;font-family:system-ui,sans-serif;margin:0;padding:0} | |
| body{background:var(--bg);color:var(--fg);min-height:100vh;display:flex;flex-direction:column} | |
| header{background:var(--card);border-bottom:1px solid var(--border);padding:1rem 1.5rem;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:10} | |
| h1{font-size:1.25rem;font-weight:600;color:var(--accent)} | |
| header div{display:flex;gap:0.5rem;align-items:center} | |
| button,input,select{background:var(--bg);color:var(--fg);border:1px solid var(--border);padding:0.5rem 1rem;border-radius:6px;cursor:pointer;font-size:0.9rem} | |
| button.primary{background:var(--accent);color:#fff;border-color:var(--accent)} | |
| button.danger{background:var(--danger);color:#fff;border-color:var(--danger)} | |
| button.warn{background:var(--warn);color:#000;border-color:var(--warn)} | |
| button.ghost{background:transparent;border-color:transparent;color:var(--fg);padding:0.4rem} | |
| button.ghost:hover{background:var(--border);border-radius:4px} | |
| button:disabled{opacity:0.5;cursor:not-allowed} | |
| input[type=file]{padding:0.3rem} | |
| main{flex:1;padding:1.5rem;max-width:900px;width:100%;margin:0 auto} | |
| .card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1.5rem;margin-bottom:1rem} | |
| .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:1rem} | |
| .item{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:1rem;display:flex;flex-direction:column;gap:0.5rem;position:relative;min-height:120px;transition:border 0.2s} | |
| .item:hover{border-color:var(--accent)} | |
| .item.folder{border-color:var(--accent)} | |
| .item-header{display:flex;justify-content:space-between;align-items:flex-start} | |
| .file-icon{width:32px;height:32px;flex-shrink:0;margin-right:0.5rem;display:flex;align-items:center;justify-content:center} | |
| .item-name{font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;font-size:0.95rem;display:flex;align-items:center} | |
| .item-meta{font-size:0.75rem;color:#8b949e;line-height:1.4;margin-left:2.2rem} /* Align with name */ | |
| .item-actions{display:flex;gap:0.25rem;margin-top:auto;padding-top:0.5rem;border-top:1px solid var(--border);justify-content:flex-end} | |
| /* Dropdown Menu */ | |
| .dropdown{position:relative;display:inline-block} | |
| .dropdown-btn{background:none;border:none;color:#8b949e;font-size:1.2rem;line-height:1;padding:0.25rem 0.5rem;cursor:pointer;border-radius:4px} | |
| .dropdown-btn:hover{color:var(--fg);background:var(--border)} | |
| .dropdown-menu{display:none;position:absolute;right:0;top:100%;z-index:20;min-width:180px;background:var(--menu-bg);border:1px solid var(--menu-border);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,0.4);overflow:hidden} | |
| .dropdown-menu.show{display:block} | |
| .dropdown-item{display:flex;align-items:center;gap:0.5rem;width:100%;padding:0.6rem 1rem;background:none;border:none;color:var(--fg);font-size:0.9rem;text-align:left;cursor:pointer} | |
| .dropdown-item:hover{background:var(--border)} | |
| .dropdown-item.danger{color:var(--danger)} | |
| .dropdown-item.warn{color:var(--warn)} | |
| .dropdown-divider{height:1px;background:var(--menu-border);margin:0.25rem 0} | |
| .modal{position:fixed;inset:0;background:#0008;display:flex;align-items:center;justify-content:center;z-index:100} | |
| .modal.hidden{display:none} | |
| .modal-content{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1.5rem;width:100%;max-width:500px;display:flex;flex-direction:column;gap:1rem;max-height:90vh;overflow:auto} | |
| .toast{position:fixed;bottom:1rem;right:1rem;padding:1rem 1.5rem;background:var(--card);border:1px solid var(--border);border-radius:6px;z-index:200;animation:slide 0.3s} | |
| .toast.success{border-left:4px solid var(--success)} | |
| .toast.error{border-left:4px solid var(--danger)} | |
| .toast.info{border-left:4px solid var(--accent)} | |
| @keyframes slide{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}} | |
| .progress-bar{height:8px;background:var(--border);border-radius:4px;overflow:hidden;margin:0.5rem 0} | |
| .progress-bar>div{height:100%;background:linear-gradient(90deg, var(--accent), #79c0ff);width:0%;transition:width 0.2s} | |
| .progress-info{display:flex;justify-content:space-between;font-size:0.8rem;color:#8b949e;margin-bottom:0.25rem;font-family:monospace} | |
| .hidden{display:none} | |
| code{background:var(--bg);padding:0.1rem 0.3rem;border-radius:4px;font-size:0.85rem;word-break:break-all} | |
| .share-row{display:flex;gap:0.5rem;align-items:center;margin:0.5rem 0;padding:0.5rem;background:var(--bg);border-radius:4px;border:1px solid var(--border)} | |
| .share-row input{flex:1;font-size:0.8rem;padding:0.3rem} | |
| .share-row button{padding:0.3rem 0.6rem;font-size:0.8rem} | |
| .stage-badge{display:inline-block;padding:0.1rem 0.5rem;border-radius:4px;font-size:0.7rem;font-weight:bold;background:var(--border);color:var(--fg);margin-left:0.5rem;vertical-align:middle} | |
| .stage-badge.recv{background:#238636;color:#fff} | |
| .stage-badge.crypt{background:#d29922;color:#000} | |
| .stage-badge.png{background:#58a6ff;color:#fff} | |
| .stage-badge.imgbb{background:#a371f7;color:#fff} | |
| .stage-badge.done{background:#3fb950;color:#fff} | |
| .stage-badge.error{background:#f85149;color:#fff} | |
| </style> | |
| </head><body> | |
| <header><h1>💾 PixelDrive</h1><div><span id="userInfo" style="margin-right:1rem;font-size:0.9rem;"></span><button id="btnLogout" class="danger hidden">Déconnexion</button><button id="btnLogin" class="primary">Connexion</button></div></header> | |
| <main id="app"> | |
| <div id="viewAuth" class="card" style="max-width:400px;margin:2rem auto;"><h2 style="margin-bottom:1rem;">Connexion / Inscription</h2><input id="authUser" placeholder="Utilisateur" style="width:100%;margin-bottom:0.5rem;"><input id="authPass" type="password" placeholder="Mot de passe" style="width:100%;margin-bottom:0.5rem;"><button id="btnDoLogin" class="primary" style="width:100%;">Se connecter</button><button id="btnDoRegister" style="width:100%;margin-top:0.5rem;">Créer compte</button></div> | |
| <div id="viewDrive" class="hidden"> | |
| <div class="card" style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:1rem;align-items:center;"><input type="file" id="fileInput" multiple style="flex:1;min-width:200px;"><button id="btnUpload" class="primary">Uploader</button><input type="text" id="mkdirName" placeholder="Nom dossier" style="flex:1;min-width:150px;"><button id="btnMkdir">Créer dossier</button><button id="btnUpFolder" title="Dossier parent" style="padding:0.5rem 1rem;">⬆</button><span id="pathDisplay" style="flex:1;text-align:right;color:#8b949e;font-size:0.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">🏠 Racine</span></div> | |
| <div id="progressContainer" class="card hidden"><div class="progress-info"><span id="progressStage"><span class="stage-badge recv">Réception</span></span><span id="progressSpeed">0 Mo/s</span><span id="progressETA">--:--</span></div><div class="progress-bar"><div id="progressBar"></div></div><div class="progress-info"><span id="progressCurrentFile"></span><span id="progressChunks">Chunks: 0/0</span></div></div> | |
| <div id="grid" class="grid"></div> | |
| </div></main> | |
| <div id="modalShare" class="modal hidden"><div class="modal-content"><h3>🔗 Lien de partage</h3><p style="color:#8b949e;font-size:0.9rem;">Accès sans connexion.</p><div id="shareList"></div><div style="display:flex;gap:0.5rem;justify-content:flex-end;margin-top:1rem;"><button id="modalShareClose">Fermer</button></div></div></div> | |
| <div id="modalConfirm" class="modal hidden"><div class="modal-content"><h3>Confirmer suppression</h3><p id="modalText"></p><div style="display:flex;justify-content:flex-end;gap:0.5rem;"><button id="modalCancel">Annuler</button><button id="modalOk" class="danger">Supprimer</button></div></div></div> | |
| <div id="modalPreview" class="modal hidden"><div class="modal-content" style="max-width:90vw;max-height:90vh;padding:0;overflow:hidden;"><button style="align-self:flex-end;background:none;border:none;color:var(--fg);font-size:1.5rem;padding:0.5rem 1rem;cursor:pointer;">✕</button><div id="previewContent" style="max-height:80vh;overflow:auto;background:var(--bg);"></div></div></div> | |
| <div id="toastContainer"></div> | |
| <script> | |
| const $=(s)=>document.querySelector(s); const $$=(s)=>document.querySelectorAll(s); | |
| const show=(el)=>el.classList.remove('hidden'); const hide=(el)=>el.classList.add('hidden'); | |
| const toast=(msg,type='')=>{const t=document.createElement('div');t.className='toast '+type;t.textContent=msg;$('#toastContainer').append(t);setTimeout(()=>t.remove(),3000);}; | |
| const fmtBytes=(b)=>{if(!b)return'0o';const k=1024,u=['o','Ko','Mo','Go','To'];const i=Math.floor(Math.log(b)/Math.log(k));return(b/Math.pow(k,i)).toFixed(1)+' '+u[i];}; | |
| const fmtTime=(s)=>{if(!s||s<0||isNaN(s))return'--:--';const m=Math.floor(s/60),h=Math.floor(m/60);return(h>0?h+'h ':'')+(m%60)+'m '+(s%60)+'s';}; | |
| const api=(url,opts={})=>fetch(url,{credentials:'include',headers:{'Content-Type':'application/json',...(opts.headers||{})},...opts}).then(r=>{if(!r.ok)return r.json().then(e=>Promise.reject(e.error||r.statusText));return r.json();}); | |
| // --- ICÔNES SVG PAR TYPE MIME --- | |
| function getFileIcon(mime) { | |
| const sz = 'width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"'; | |
| if(mime.startsWith('image/')) return `<svg class="file-icon" style="color:var(--img)" ${sz}><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>`; | |
| if(mime.startsWith('video/')) return `<svg class="file-icon" style="color:var(--vid)" ${sz}><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg>`; | |
| if(mime.startsWith('audio/')) return `<svg class="file-icon" style="color:var(--aud)" ${sz}><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>`; | |
| if(mime === 'application/pdf') return `<svg class="file-icon" style="color:var(--pdf)" ${sz}><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><text x="12" y="17" font-size="6" text-anchor="middle" fill="currentColor">PDF</text></svg>`; | |
| if(mime.startsWith('application/zip') || mime.startsWith('application/x-rar') || mime.startsWith('application/x-7z') || mime.startsWith('application/gzip')) return `<svg class="file-icon" style="color:var(--arc)" ${sz}><polyline points="21 8 21 21 3 21 3 8"></polyline><rect x="1" y="3" width="22" height="5" rx="1"></rect></svg>`; | |
| if(mime.startsWith('text/') || mime === 'application/json' || mime === 'application/javascript' || mime === 'application/typescript') return `<svg class="file-icon" style="color:var(--code)" ${sz}><polyline points="4 7 4 4 20 4 20 17"></polyline><line x1="14" y1="1" x2="14" y2="7"></line><line x1="4" y1="17" x2="20" y2="17"></line></svg>`; | |
| return `<svg class="file-icon" style="color:var(--fg)" ${sz}><path d="M13 2H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>`; | |
| } | |
| let currentParentId=null; const pathStack=[{id:null,name:'Racine'}]; | |
| let shareTargetFileId=null; let uploadPollInterval=null; | |
| // Dropdown Logic | |
| function setupDropdowns(){$$('.dropdown-btn').forEach(btn=>btn.onclick=(e)=>{e.stopPropagation();const m=btn.nextElementSibling;$$('.dropdown-menu.show').forEach(o=>{if(o!==m)o.classList.remove('show');});m.classList.toggle('show');});document.onclick=()=>$$('.dropdown-menu.show').forEach(m=>m.classList.remove('show'));} | |
| // Auth | |
| $('#btnLogin').onclick=()=>{hide($('#viewDrive'));show($('#viewAuth'))}; | |
| $('#btnLogout').onclick=async()=>{await api('/api/auth/logout',{method:'POST'});location.reload();}; | |
| $('#btnDoLogin').onclick=async()=>{try{await api('/api/auth/login',{method:'POST',body:JSON.stringify({username:$('#authUser').value,password:$('#authPass').value})});toast('Connecté','success');initDrive();}catch(e){toast(e,'error');}}; | |
| $('#btnDoRegister').onclick=async()=>{try{await api('/api/auth/register',{method:'POST',body:JSON.stringify({username:$('#authUser').value,password:$('#authPass').value})});toast('Compte créé','success');}catch(e){toast(e,'error');}}; | |
| async function initDrive(){const me=await api('/api/auth/me');$('#userInfo').textContent=me.user.username+(me.user.isAdmin?' 👑':'');hide($('#viewAuth'));hide($('#btnLogin'));show($('#btnLogout'));show($('#viewDrive'));loadFolder(null);} | |
| function updatePath(){$('#pathDisplay').textContent='🏠 '+pathStack.map(p=>p.name).join(' / ');} | |
| $('#btnUpFolder').onclick=()=>{if(pathStack.length>1){pathStack.pop();loadFolder(pathStack[pathStack.length-1].id);}}; | |
| async function loadFolder(pid){currentParentId=pid;updatePath();const{items}=await api('/api/fs/list?parent_id='+(pid||''));renderGrid(items);} | |
| function renderGrid(items){ | |
| const grid=$('#grid');grid.innerHTML=''; | |
| items.sort((a,b)=>{const af=a.mime==='application/vnd.pixeldrive.folder',bf=b.mime==='application/vnd.pixeldrive.folder';if(af!==bf)return bf-af;return a.name.localeCompare(b.name,undefined,{numeric:true,sensitivity:'base'});}); | |
| items.forEach(item=>{ | |
| const div=document.createElement('div');div.className='item'+(item.mime==='application/vnd.pixeldrive.folder'?' folder':'');const isFile=item.mime!=='application/vnd.pixeldrive.folder'; | |
| let menu=''; | |
| if(isFile){ | |
| menu+=`<button class="dropdown-item btn-preview" data-id="${item.id}" data-mime="${item.mime}" data-name="${item.name}">👁 Aperçu</button>`; | |
| menu+=`<button class="dropdown-item btn-download" data-id="${item.id}">⬇ Télécharger</button>`; | |
| menu+=`<button class="dropdown-item btn-share warn" data-id="${item.id}" data-name="${item.name}">🔗 Partager</button>`; | |
| menu+=`<div class="dropdown-divider"></div>`; | |
| }else{menu+=`<button class="dropdown-item btn-open-folder" data-id="${item.id}">📂 Ouvrir</button><div class="dropdown-divider"></div>`;} | |
| menu+=`<button class="dropdown-item danger btn-delete" data-id="${item.id}" data-name="${item.name}" data-folder="${!isFile}">🗑 Supprimer</button>`; | |
| const iconHtml = isFile ? getFileIcon(item.mime) : `<div class="file-icon" style="color:var(--accent)"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg></div>`; | |
| div.innerHTML=`<div class="item-header">${iconHtml}<div class="item-name" title="${item.name}" data-id="${item.id}" data-folder="${!isFile}">${item.name}</div><div class="dropdown"><button class="dropdown-btn" title="Actions">⋮</button><div class="dropdown-menu">${menu}</div></div></div><div class="item-meta">${isFile?fmtBytes(item.size)+' • '+item.chunk_count+' chunks<br>':''}${new Date(item.created_at*1000).toLocaleString()}</div>`; | |
| grid.appendChild(div); | |
| }); | |
| $$('.item-name[data-folder="true"], .btn-open-folder').forEach(el=>el.onclick=(e)=>{e.stopPropagation();const id=el.dataset.id;const it=items.find(i=>i.id===id);if(it){pathStack.push(it);loadFolder(id);}}); | |
| $$('.btn-preview').forEach(el=>el.onclick=(e)=>{e.stopPropagation();openPreview(el.dataset.id,el.dataset.mime,el.dataset.name);}); | |
| $$('.btn-download').forEach(el=>el.onclick=(e)=>{e.stopPropagation();window.open('/api/fs/download/'+el.dataset.id,'_blank');}); | |
| $$('.btn-share').forEach(el=>el.onclick=(e)=>{e.stopPropagation();openShareModal(el.dataset.id,el.dataset.name);}); | |
| $$('.btn-delete').forEach(el=>el.onclick=(e)=>{e.stopPropagation();confirmDelete(el.dataset.id,el.dataset.name,el.dataset.folder==='true');}); | |
| setupDropdowns(); | |
| } | |
| // Mkdir | |
| $('#btnMkdir').onclick=async()=>{const n=$('#mkdirName').value.trim();if(!n)return;try{await api('/api/fs/mkdir',{method:'POST',body:JSON.stringify({name:n,parent_id:currentParentId})});toast('Dossier créé','success');$('#mkdirName').value='';loadFolder(currentParentId);}catch(e){toast(e,'error');}}; | |
| // UPLOAD XHR + POLLING | |
| $('#btnUpload').onclick=()=>$('#fileInput').click(); | |
| $('#fileInput').onchange=async(e)=>{ | |
| const files=Array.from(e.target.files);if(!files.length)return; | |
| hide($('#btnUpload'));show($('#progressContainer')); | |
| for(const file of files){ | |
| $('#progressCurrentFile').textContent=file.name;$('#progressBar').style.width='0%';$('#progressSpeed').textContent='0 Mo/s';$('#progressETA').textContent='--:--';$('#progressChunks').textContent='Chunks: 0/0';setStageBadge('recv'); | |
| const form=new FormData();form.append('file',file);form.append('parent_id',currentParentId||'');let fileId=null; | |
| try{ | |
| await new Promise((resolve,reject)=>{const xhr=new XMLHttpRequest();xhr.open('POST','/api/fs/upload',true);xhr.withCredentials=true;xhr.upload.onprogress=(ev)=>{if(ev.lengthComputable){const p=Math.round(ev.loaded/ev.total*100);$('#progressBar').style.width=p+'%';const elapsed=(Date.now()-xhr._start)/1000;const sp=elapsed>0?ev.loaded/elapsed/1024/1024:0;$('#progressSpeed').textContent=sp.toFixed(2)+' Mo/s';$('#progressETA').textContent=fmtTime(sp>0?(ev.total-ev.loaded)/sp/1024/1024:0);}};xhr.onload=()=>{if(xhr.status>=200&&xhr.status<300){const r=JSON.parse(xhr.response);fileId=r.fileId;resolve(r);}else reject(new Error(xhr.responseText));};xhr.onerror=()=>reject(new Error('Network'));xhr._start=Date.now();xhr.send(form);}); | |
| if(fileId){$('#progressBar').style.width='100%';setStageBadge('crypt');await pollUploadStatus(fileId);} | |
| toast(file.name+' uploadé ✓','success'); | |
| }catch(err){toast('Erreur '+file.name+': '+err.message,'error');console.error(err);} | |
| } | |
| $('#fileInput').value='';show($('#btnUpload'));hide($('#progressContainer'));loadFolder(currentParentId); | |
| }; | |
| function setStageBadge(stage){const el=$('#progressStage');el.innerHTML='';const labels={recv:'Réception',crypt:'Chiffrement',png:'Encodage PNG',imgbb:'Envoi IMGBB',finalizing:'Finalisation',done:'Terminé',error:'Erreur'};const badge=document.createElement('span');badge.className='stage-badge '+stage;badge.textContent=labels[stage]||stage;el.appendChild(badge);} | |
| async function pollUploadStatus(fid){ | |
| return new Promise((resolve,reject)=>{const intv=setInterval(async()=>{try{const s=await api('/api/fs/upload/status/'+fid);if(!s||s.stage==='waiting')return;if(s.stage==='chunking'||s.stage==='receiving')setStageBadge('recv');else if(s.stage==='encrypting')setStageBadge('crypt');else if(s.stage==='png_encoding')setStageBadge('png');else if(s.stage==='uploading_imgbb')setStageBadge('imgbb');else if(s.stage==='finalizing')setStageBadge('finalizing');else if(s.stage==='done'){setStageBadge('done');clearInterval(intv);resolve();return;}else if(s.stage==='error'){setStageBadge('error');clearInterval(intv);reject(new Error(s.error));return;}if(s.speed)$('#progressSpeed').textContent=s.speed.toFixed(2)+' Mo/s';if(s.eta)$('#progressETA').textContent=fmtTime(s.eta);if(s.chunksTotal)$('#progressChunks').textContent=`Chunks: ${s.chunksDone||0}/${s.chunksTotal}`;if(s.currentChunk)$('#progressCurrentFile').textContent=`Chunk ${s.currentChunk}/${s.chunksTotal}...`;}catch(e){clearInterval(intv);reject(e);}},1000);uploadPollInterval=intv;}); | |
| } | |
| // Delete | |
| let delTarget=null;function confirmDelete(id,name,isFolder){delTarget={id,isFolder};$('#modalText').textContent='Supprimer '+(isFolder?'le dossier':'le fichier')+' "'+name+'" ?';show($('#modalConfirm'));} | |
| $('#modalCancel').onclick=()=>hide($('#modalConfirm')); | |
| $('#modalOk').onclick=async()=>{if(!delTarget)return;try{await api('/api/fs/'+delTarget.id,{method:'DELETE'});toast('Supprimé','success');loadFolder(currentParentId);}catch(e){toast(e,'error');}hide($('#modalConfirm'));delTarget=null;}; | |
| // Preview | |
| function openPreview(id,mime,name){const c=$('#previewContent');c.innerHTML=''; | |
| if(mime.startsWith('image/'))c.innerHTML='<img src="/api/fs/preview/'+id+'" style="max-width:100%;max-height:80vh;display:block;margin:auto;" alt="'+name+'">'; | |
| else if(mime.startsWith('video/'))c.innerHTML='<video controls style="max-width:100%;max-height:80vh;display:block;margin:auto;"><source src="/api/fs/preview/'+id+'" type="'+mime+'"></video>'; | |
| else if(mime.startsWith('audio/'))c.innerHTML='<audio controls style="width:100%;"><source src="/api/fs/preview/'+id+'" type="'+mime+'"></audio>'; | |
| else if(mime==='application/pdf')c.innerHTML='<iframe src="/api/fs/preview/'+id+'" style="width:100%;height:80vh;border:none;"></iframe>'; | |
| else c.innerHTML='<p style="padding:2rem;text-align:center;">Aperçu non dispo pour '+mime+'</p>';show($('#modalPreview'));} | |
| $('#modalPreview').onclick=(e)=>{if(e.target===$('#modalPreview')||e.target.tagName==='BUTTON')hide($('#modalPreview'));}; | |
| // Share Modal | |
| async function openShareModal(fid,name){shareTargetFileId=fid;$('#modalShare').querySelector('h3').textContent='🔗 Partage : '+name;const list=$('#shareList');list.innerHTML='<div style="color:#8b949e;">Chargement...</div>';show($('#modalShare')); | |
| try{const {shares}=await api('/api/fs/shares/'+fid);renderShareList(shares);}catch(e){list.innerHTML='<div class="toast error">Erreur</div>';}} | |
| function renderShareList(shares){const list=$('#shareList');list.innerHTML=''; | |
| ['download','embed'].forEach(type=>{const ex=shares.find(s=>s.type===type);const lb=type==='download'?'⬇ Téléchargement':'👁 Embed';const row=document.createElement('div');row.className='share-row'; | |
| if(ex){row.innerHTML='<span style="min-width:140px;font-weight:bold;">'+lb+'</span><input type="text" value="'+ex.url+'" readonly><button class="btn-copy" data-url="'+ex.url+'">📋 Copier</button><button class="danger btn-revoke" data-type="'+type+'">⛔ Révoquer</button>';} | |
| else{row.innerHTML='<span style="min-width:140px;font-weight:bold;">'+lb+'</span><button class="primary btn-create" data-type="'+type+'">Créer</button>';} | |
| list.appendChild(row);}); | |
| list.querySelectorAll('.btn-create').forEach(b=>b.onclick=()=>createShare(b.dataset.type)); | |
| list.querySelectorAll('.btn-revoke').forEach(b=>b.onclick=()=>revokeShare(b.dataset.type)); | |
| list.querySelectorAll('.btn-copy').forEach(b=>b.onclick=()=>{navigator.clipboard.writeText(b.dataset.url);toast('Copié !','success');});} | |
| async function createShare(type){try{await api('/api/fs/share/'+shareTargetFileId,{method:'POST',body:JSON.stringify({type})});toast('Lien créé','success');openShareModal(shareTargetFileId,$('#modalShare').querySelector('h3').textContent.replace('🔗 Partage : ',''));}catch(e){toast(e,'error');}} | |
| async function revokeShare(type){if(!confirm('Révoquer ?'))return;try{await api('/api/fs/share/'+shareTargetFileId+'/'+type,{method:'DELETE'});toast('Révoqué','success');openShareModal(shareTargetFileId,$('#modalShare').querySelector('h3').textContent.replace('🔗 Partage : ',''));}catch(e){toast(e,'error');}} | |
| $('#modalShareClose').onclick=()=>hide($('#modalShare')); | |
| // Start | |
| initDrive().catch(()=>{show($('#viewAuth'));hide($('#viewDrive'));}); | |
| </script> | |
| </body></html> |