Dateien nach "frontend/src/tools" hochladen

This commit is contained in:
2026-05-28 01:45:22 +02:00
parent 8835789a05
commit 134bbcc99c

View File

@@ -48,8 +48,14 @@ function compressImage(file) {
// ── Stepper ─────────────────────────────────────────────────────────────────── // ── Stepper ───────────────────────────────────────────────────────────────────
function Stepper({ label, value, onChange, step, unit, min = 0, hint }) { function Stepper({ label, value, onChange, step, unit, min = 0, hint }) {
const dec = () => onChange(Math.max(min, Math.round((value - step) * 10000) / 10000)); const [raw, setRaw] = useState(String(value));
const inc = () => onChange(Math.round((value + step) * 10000) / 10000);
// Sync wenn value sich von außen ändert (z.B. beim Bearbeiten eines Archiv-Eintrags)
useEffect(() => { setRaw(String(value)); }, [value]);
const dec = () => { const v = Math.max(min, Math.round((value - step) * 10000) / 10000); onChange(v); setRaw(String(v)); };
const inc = () => { const v = Math.round((value + step) * 10000) / 10000; onChange(v); setRaw(String(v)); };
return ( return (
<div> <div>
<div style={{ color:'rgba(255,255,255,0.4)', fontSize:10, fontFamily:'monospace', marginBottom:5, letterSpacing:1 }}> <div style={{ color:'rgba(255,255,255,0.4)', fontSize:10, fontFamily:'monospace', marginBottom:5, letterSpacing:1 }}>
@@ -57,8 +63,18 @@ function Stepper({ label, value, onChange, step, unit, min = 0, hint }) {
</div> </div>
<div style={{ display:'flex', alignItems:'center', background:'rgba(255,255,255,0.05)', borderRadius:8, border:'1px solid rgba(255,255,255,0.1)', overflow:'hidden' }}> <div style={{ display:'flex', alignItems:'center', background:'rgba(255,255,255,0.05)', borderRadius:8, border:'1px solid rgba(255,255,255,0.1)', overflow:'hidden' }}>
<button onClick={dec} style={{ padding:'10px 12px', background:'transparent', border:'none', color:'rgba(255,255,255,0.6)', fontSize:20, cursor:'pointer', flexShrink:0, lineHeight:1 }}></button> <button onClick={dec} style={{ padding:'10px 12px', background:'transparent', border:'none', color:'rgba(255,255,255,0.6)', fontSize:20, cursor:'pointer', flexShrink:0, lineHeight:1 }}></button>
<input type="number" value={value} step={step} <input
onChange={e => onChange(parseFloat(e.target.value) || 0)} type="number" value={raw} step={step}
onChange={e => {
setRaw(e.target.value);
const parsed = parseFloat(e.target.value);
if (!isNaN(parsed)) onChange(Math.max(min, parsed));
}}
onBlur={() => {
const parsed = parseFloat(raw);
const safe = isNaN(parsed) ? min : Math.max(min, parsed);
onChange(safe); setRaw(String(safe));
}}
style={{ flex:1, background:'transparent', border:'none', color:'#fff', fontSize:15, fontFamily:"'Space Mono',monospace", textAlign:'center', outline:'none', minWidth:0, padding:'10px 4px' }} style={{ flex:1, background:'transparent', border:'none', color:'#fff', fontSize:15, fontFamily:"'Space Mono',monospace", textAlign:'center', outline:'none', minWidth:0, padding:'10px 4px' }}
/> />
<button onClick={inc} style={{ padding:'10px 12px', background:'transparent', border:'none', color:'rgba(255,255,255,0.6)', fontSize:20, cursor:'pointer', flexShrink:0, lineHeight:1 }}>+</button> <button onClick={inc} style={{ padding:'10px 12px', background:'transparent', border:'none', color:'rgba(255,255,255,0.6)', fontSize:20, cursor:'pointer', flexShrink:0, lineHeight:1 }}>+</button>
@@ -69,11 +85,23 @@ function Stepper({ label, value, onChange, step, unit, min = 0, hint }) {
} }
function SmallInput({ label, value, onChange, step = 0.01 }) { function SmallInput({ label, value, onChange, step = 0.01 }) {
const [raw, setRaw] = useState(String(value));
useEffect(() => { setRaw(String(value)); }, [value]);
return ( return (
<div> <div>
<div style={{ color:'rgba(255,255,255,0.6)', fontSize:9, fontFamily:'monospace', marginBottom:3 }}>{label}</div> <div style={{ color:'rgba(255,255,255,0.6)', fontSize:9, fontFamily:'monospace', marginBottom:3 }}>{label}</div>
<input type="number" value={value} step={step} <input
onChange={e => onChange(parseFloat(e.target.value) || 0)} type="number" value={raw} step={step}
onChange={e => {
setRaw(e.target.value);
const parsed = parseFloat(e.target.value);
if (!isNaN(parsed)) onChange(parsed);
}}
onBlur={() => {
const parsed = parseFloat(raw);
if (!isNaN(parsed)) { onChange(parsed); setRaw(String(parsed)); }
else setRaw(String(value));
}}
style={{ ...S.inp, padding:'7px 8px', fontSize:13 }} style={{ ...S.inp, padding:'7px 8px', fontSize:13 }}
/> />
</div> </div>
@@ -125,10 +153,12 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile }) {
const handleImagePick = async e => { const handleImagePick = async e => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
if (!file.type.startsWith('image/')) { toast('Nur Bilddateien erlaubt (JPG, PNG, …)', 'error'); e.target.value = ''; return; }
if (file.size > 20 * 1024 * 1024) { toast(`Bild zu groß (${(file.size/1024/1024).toFixed(1)} MB) max. 20 MB`, 'error'); e.target.value = ''; return; }
try { try {
const compressed = await compressImage(file); const compressed = await compressImage(file);
setImage(compressed); setImage(compressed);
} catch { toast('Bild konnte nicht geladen werden', 'error'); } } catch { toast('Bild konnte nicht verarbeitet werden ist die Datei beschädigt?', 'error'); }
e.target.value = ''; e.target.value = '';
}; };