Fortschrittsanzeige beim Upload (Dateien-Tool + öffentliche Upload-Seite), Drag&Drop auf Upload-Seite ergänzt
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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>}
|
||||
|
||||
Reference in New Issue
Block a user