Dateien nach "frontend/src/tools" hochladen

This commit is contained in:
2026-05-28 07:54:56 +02:00
parent 53b66eaf96
commit 166b3be251

View File

@@ -29,13 +29,18 @@ const IconBtn = ({ onClick, color, title, children, stop }) => (
// ── Share Modal ─────────────────────────────────────────────────────────────── // ── Share Modal ───────────────────────────────────────────────────────────────
function ShareModal({ item, type, onClose, toast }) { function ShareModal({ item, type, onClose, toast }) {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [shares, setShares] = useState([]); const [shares, setShares] = useState([]);
const base = type==='folder' ? `/tools/dateien/folders/${item.id}` : `/tools/dateien/${item.id}`; const base = type==='folder' ? `/tools/dateien/folders/${item.id}` : `/tools/dateien/${item.id}`;
useEffect(() => { api(`${base}/shares`).then(setShares).catch(()=>{}); }, [base]); useEffect(() => { api(`${base}/shares`).then(setShares).catch(()=>{}); }, [base]);
const share = async () => { const share = async () => {
if (!username.trim()) return; if (!username.trim()) return;
try { await api(`${base}/share`,{body:{username:username.trim()}}); toast('Geteilt ✓'); setUsername(''); api(`${base}/shares`).then(setShares); } try {
catch(e) { toast(e.message,'error'); } await api(`${base}/share`, { body: { username: username.trim(), password: password.trim() || undefined } });
toast('Geteilt ✓'); setUsername(''); setPassword('');
api(`${base}/shares`).then(setShares);
} catch(e) { toast(e.message,'error'); }
}; };
const unshare = async uid => { const unshare = async uid => {
try { await api(`${base}/share/${uid}`,{method:'DELETE'}); setShares(p=>p.filter(s=>s.id!==uid)); toast('Freigabe entfernt'); } try { await api(`${base}/share/${uid}`,{method:'DELETE'}); setShares(p=>p.filter(s=>s.id!==uid)); toast('Freigabe entfernt'); }
@@ -55,27 +60,57 @@ function ShareModal({ item, type, onClose, toast }) {
</div> </div>
<button onClick={onClose} style={{background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18,padding:'0 4px'}}></button> <button onClick={onClose} style={{background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18,padding:'0 4px'}}></button>
</div> </div>
<div style={{display:'flex',gap:8,marginBottom:14}}>
<input value={username} onChange={e=>setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()} {/* Teilen-Formular */}
placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/> {type==='file' && (
<button onClick={share} style={S.btn('#4ecdc4')}>Teilen</button> <div style={{marginBottom:10}}>
</div> <div style={{display:'flex',gap:8,marginBottom:6}}>
<input value={username} onChange={e=>setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()}
placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/>
<button onClick={share} style={S.btn('#4ecdc4')}>Teilen</button>
</div>
<input value={password} onChange={e=>setPassword(e.target.value)}
placeholder="Passwort (optional)" type="text" autoCapitalize="none"
style={{...S.inp,fontSize:13,padding:'7px 10px'}}/>
{password && <div style={{color:'rgba(255,230,109,0.6)',fontSize:9,fontFamily:'monospace',marginTop:4}}>
Teile das Passwort separat mit dem Empfänger
</div>}
</div>
)}
{type==='folder' && (
<div style={{display:'flex',gap:8,marginBottom:14}}>
<input value={username} onChange={e=>setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()}
placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/>
<button onClick={share} style={S.btn('#4ecdc4')}>Teilen</button>
</div>
)}
{/* Bestehende Shares */}
{shares.length>0 && ( {shares.length>0 && (
<div> <div>
<div style={{...S.head,marginBottom:0,marginTop:0}}>GETEILT MIT</div> <div style={{...S.head,marginBottom:6,marginTop:4}}>GETEILT MIT</div>
{shares.map(s=>( {shares.map(s=>(
<div key={s.id} style={{display:'flex',justifyContent:'space-between',alignItems:'center', <div key={s.id} style={{display:'flex',justifyContent:'space-between',alignItems:'center',
padding:'8px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}> padding:'8px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
<div> <div>
<div style={{color:'rgba(255,255,255,0.8)',fontFamily:'monospace',fontSize:13}}>{s.username}</div> <div style={{display:'flex',alignItems:'center',gap:6}}>
{s.shared_at && <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10}}>seit {fmtDate(s.shared_at)}</div>} <span style={{color:'rgba(255,255,255,0.8)',fontFamily:'monospace',fontSize:13}}>{s.username}</span>
{s.has_password ? <span title="Passwortgeschützt" style={{fontSize:10}}>🔑</span> : null}
</div>
<div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10,marginTop:2}}>
{s.shared_at && `geteilt ${fmtDate(s.shared_at)}`}
{s.accessed_at
? <span style={{color:'rgba(78,205,196,0.7)',marginLeft:8}}> zugegriffen {fmtDate(s.accessed_at)}</span>
: <span style={{color:'rgba(255,255,255,0.25)',marginLeft:8}}> noch nicht geöffnet</span>
}
</div>
</div> </div>
<button onClick={()=>unshare(s.id)} style={S.btn('#ff6b9d',true)}> Entfernen</button> <button onClick={()=>unshare(s.id)} style={S.btn('#ff6b9d',true)}></button>
</div> </div>
))} ))}
</div> </div>
)} )}
{shares.length===0 && <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11}}>Noch mit niemandem geteilt.</div>} {shares.length===0 && <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11,marginTop:4}}>Noch mit niemandem geteilt.</div>}
</div> </div>
</div> </div>
); );
@@ -128,17 +163,26 @@ export default function Dateien({ toast, mobile }) {
const onFileInput = e => { doUpload(e.target.files?.[0]); e.target.value=''; }; const onFileInput = e => { doUpload(e.target.files?.[0]); e.target.value=''; };
const onDrop = e => { e.preventDefault(); setDragOver(false); doUpload(e.dataTransfer.files?.[0]); }; const onDrop = e => { e.preventDefault(); setDragOver(false); doUpload(e.dataTransfer.files?.[0]); };
const download = async file => { const download = async (file, pw) => {
try { try {
const res = await fetch(`/api/tools/dateien/${file.id}/download`,{ const url = `/api/tools/dateien/${file.id}/download${pw ? `?password=${encodeURIComponent(pw)}` : ''}`;
headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`} const res = await fetch(url, { headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`} });
}); if (!res.ok) {
if (!res.ok){const err=await res.json().catch(()=>({error:'Fehler'}));toast(err.error,'error');return;} const err = await res.json().catch(()=>({error:'Fehler'}));
if (err.passwordRequired) {
const entered = window.prompt(`🔑 Datei "${file.originalname}" ist passwortgeschützt.\nBitte Passwort eingeben:`);
if (entered !== null) await download(file, entered);
return;
}
if (pw && res.status===403) { toast('Falsches Passwort', 'error'); return; }
toast(err.error,'error'); return;
}
const blob = await res.blob(); const blob = await res.blob();
const url = URL.createObjectURL(blob); const a = Object.assign(document.createElement('a'), {
const a = document.createElement('a'); a.href=url; a.download=file.originalname; href: URL.createObjectURL(blob), download: file.originalname,
});
document.body.appendChild(a); a.click(); document.body.removeChild(a); document.body.appendChild(a); a.click(); document.body.removeChild(a);
setTimeout(()=>URL.revokeObjectURL(url),1000); setTimeout(()=>URL.revokeObjectURL(a.href),1000);
} catch(e){toast(e.message,'error');} } catch(e){toast(e.message,'error');}
}; };
@@ -345,7 +389,15 @@ export default function Dateien({ toast, mobile }) {
<div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10,marginTop:2}}> <div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10,marginTop:2}}>
{fmtSize(file.size)} · {fmtDate(file.created_at)} {fmtSize(file.size)} · {fmtDate(file.created_at)}
{tab==='shared'?` · von ${file.owner}`:''} {tab==='shared'?` · von ${file.owner}`:''}
{tab==='sharedByMe'&&file.shared_with_names?` · → ${file.shared_with_names}${file.shared_at?' seit '+fmtDate(file.shared_at):''}`:'' } {tab==='sharedByMe'&&file.shared_with_names?(
<span>
{` · → ${file.shared_with_names}`}
{file.accessed_at
? <span style={{color:'rgba(78,205,196,0.8)'}}> · {fmtDate(file.accessed_at)}</span>
: <span style={{color:'rgba(255,255,255,0.25)'}}> · nicht geöffnet</span>
}
</span>
):''}
</div> </div>
</div> </div>
{/* Aktionen gleiche Breite wie bei Ordnern */} {/* Aktionen gleiche Breite wie bei Ordnern */}