Dateien nach "frontend/src/tools" hochladen
This commit is contained in:
@@ -29,13 +29,18 @@ const IconBtn = ({ onClick, color, title, children, stop }) => (
|
||||
// ── Share Modal ───────────────────────────────────────────────────────────────
|
||||
function ShareModal({ item, type, onClose, toast }) {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [shares, setShares] = useState([]);
|
||||
const base = type==='folder' ? `/tools/dateien/folders/${item.id}` : `/tools/dateien/${item.id}`;
|
||||
useEffect(() => { api(`${base}/shares`).then(setShares).catch(()=>{}); }, [base]);
|
||||
|
||||
const share = async () => {
|
||||
if (!username.trim()) return;
|
||||
try { await api(`${base}/share`,{body:{username:username.trim()}}); toast('Geteilt ✓'); setUsername(''); api(`${base}/shares`).then(setShares); }
|
||||
catch(e) { toast(e.message,'error'); }
|
||||
try {
|
||||
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 => {
|
||||
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>
|
||||
<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 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>
|
||||
|
||||
{/* Teilen-Formular */}
|
||||
{type==='file' && (
|
||||
<div style={{marginBottom:10}}>
|
||||
<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 && (
|
||||
<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=>(
|
||||
<div key={s.id} style={{display:'flex',justifyContent:'space-between',alignItems:'center',
|
||||
padding:'8px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
|
||||
<div>
|
||||
<div style={{color:'rgba(255,255,255,0.8)',fontFamily:'monospace',fontSize:13}}>{s.username}</div>
|
||||
{s.shared_at && <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10}}>seit {fmtDate(s.shared_at)}</div>}
|
||||
<div style={{display:'flex',alignItems:'center',gap:6}}>
|
||||
<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>
|
||||
<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>
|
||||
)}
|
||||
{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>
|
||||
);
|
||||
@@ -128,17 +163,26 @@ export default function Dateien({ toast, mobile }) {
|
||||
const onFileInput = e => { doUpload(e.target.files?.[0]); e.target.value=''; };
|
||||
const onDrop = e => { e.preventDefault(); setDragOver(false); doUpload(e.dataTransfer.files?.[0]); };
|
||||
|
||||
const download = async file => {
|
||||
const download = async (file, pw) => {
|
||||
try {
|
||||
const res = await fetch(`/api/tools/dateien/${file.id}/download`,{
|
||||
headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`}
|
||||
});
|
||||
if (!res.ok){const err=await res.json().catch(()=>({error:'Fehler'}));toast(err.error,'error');return;}
|
||||
const url = `/api/tools/dateien/${file.id}/download${pw ? `?password=${encodeURIComponent(pw)}` : ''}`;
|
||||
const res = await fetch(url, { headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`} });
|
||||
if (!res.ok) {
|
||||
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 url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a'); a.href=url; a.download=file.originalname;
|
||||
const a = Object.assign(document.createElement('a'), {
|
||||
href: URL.createObjectURL(blob), download: file.originalname,
|
||||
});
|
||||
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');}
|
||||
};
|
||||
|
||||
@@ -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}}>
|
||||
{fmtSize(file.size)} · {fmtDate(file.created_at)}
|
||||
{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>
|
||||
{/* Aktionen – gleiche Breite wie bei Ordnern */}
|
||||
|
||||
Reference in New Issue
Block a user