Fortschrittsanzeige beim Upload (Dateien-Tool + öffentliche Upload-Seite), Drag&Drop auf Upload-Seite ergänzt

This commit is contained in:
2026-06-23 23:52:32 +02:00
parent 25bca76dad
commit 1d97791237
3 changed files with 115 additions and 28 deletions

View File

@@ -15,6 +15,35 @@ export const api = async (path, { body, method, isFile } = {}) => {
return data;
};
// ── Upload mit echtem Byte-Fortschritt ──────────────────────────────────────
// fetch() liefert keinerlei Upload-Progress-Events, daher hier bewusst auf
// XMLHttpRequest aufgebaut. Nur für Datei-Uploads gedacht (FormData-Body).
// onProgress(percent: 0-100) wird während des Uploads laufend aufgerufen.
export const apiUpload = (path, formData, { onProgress } = {}) => {
const token = localStorage.getItem('sk_token');
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', `/api${path}`);
if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`);
xhr.upload.onprogress = e => {
if (e.lengthComputable && onProgress) {
onProgress(Math.round((e.loaded / e.total) * 100));
}
};
xhr.onload = () => {
let data = {};
try { data = JSON.parse(xhr.responseText); } catch { /* leere/ungültige Antwort */ }
if (xhr.status >= 200 && xhr.status < 300) resolve(data);
else reject(new Error(data.error || 'Fehler'));
};
xhr.onerror = () => reject(new Error('Verbindung fehlgeschlagen'));
xhr.send(formData);
});
};
// ── Design Tokens ─────────────────────────────────────────────────────────────
// Einheitliche Styles für alle Tools in jedem Tool importierbar
export const S = {

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { api, S } from '../lib.js';
import { api, apiUpload, S } from '../lib.js';
import { useConfirm } from '../confirm.jsx';
import { UploadIcon, DownloadIcon, TrashIcon, SearchIcon, ChevronIcon } from '../icons.jsx';
@@ -602,6 +602,7 @@ export default function Dateien({ toast, mobile }) {
});
const [pwPrompt, setPwPrompt] = useState(null); // {file}
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [dragOver, setDragOver] = useState(false);
const [newFolder, setNewFolder] = useState(false);
const [folderName, setFolderName] = useState('');
@@ -634,13 +635,14 @@ export default function Dateien({ toast, mobile }) {
const doUpload = async file => {
if (!file) return;
setUploading(true);
setUploadProgress(0);
try {
const form = new FormData();
form.append('file', file);
if (currentFolderId) form.append('folder_id', currentFolderId);
await api('/tools/dateien',{method:'POST',body:form,isFile:true});
await apiUpload('/tools/dateien', form, { onProgress: setUploadProgress });
toast(`${file.name} hochgeladen ✓`); load();
} catch(e) { toast(e.message,'error'); } finally { setUploading(false); }
} catch(e) { toast(e.message,'error'); } finally { setUploading(false); setUploadProgress(0); }
};
const onFileInput = e => { doUpload(e.target.files?.[0]); e.target.value=''; };
@@ -912,6 +914,7 @@ export default function Dateien({ toast, mobile }) {
<div onDragOver={e=>{e.preventDefault();setDragOver(true);}} onDragLeave={()=>setDragOver(false)}
onDrop={onDrop} onClick={()=>fileRef.current?.click()}
style={{
position:'relative',overflow:'hidden',
display:'flex',alignItems:'center',justifyContent:'center',gap:8,
padding:'0 18px',height:44,borderRadius:10,cursor:'pointer',
border:`2px dashed ${dragOver?'#4ecdc4':'rgba(255,255,255,0.15)'}`,
@@ -919,9 +922,14 @@ export default function Dateien({ toast, mobile }) {
transition:'all 0.15s',flexShrink:0,
minWidth:mobile?'100%':200,
}}>
{uploading && (
<div style={{position:'absolute',left:0,top:0,bottom:0,
width:`${uploadProgress}%`,background:'rgba(78,205,196,0.18)',
transition:'width 0.2s',pointerEvents:'none'}}/>
)}
<UploadIcon size={14} color={dragOver?'#4ecdc4':'rgba(255,255,255,0.4)'}/>
<span style={{color:dragOver?'#4ecdc4':'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:11}}>
{uploading?'Hochladen…':dragOver?'Loslassen':mobile?'Datei hochladen':'Hier ablegen oder klicken'}
<span style={{position:'relative',color:dragOver?'#4ecdc4':'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:11}}>
{uploading?`Hochladen… ${uploadProgress}%`:dragOver?'Loslassen':mobile?'Datei hochladen':'Hier ablegen oder klicken'}
</span>
</div>
</div>}