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 ───────────────────────────────────────────────────────────────────
function Stepper({ label, value, onChange, step, unit, min = 0, hint }) {
const dec = () => onChange(Math.max(min, Math.round((value - step) * 10000) / 10000));
const inc = () => onChange(Math.round((value + step) * 10000) / 10000);
const [raw, setRaw] = useState(String(value));
// 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 (
<div>
<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 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>
<input type="number" value={value} step={step}
onChange={e => onChange(parseFloat(e.target.value) || 0)}
<input
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' }}
/>
<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 }) {
const [raw, setRaw] = useState(String(value));
useEffect(() => { setRaw(String(value)); }, [value]);
return (
<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}
onChange={e => onChange(parseFloat(e.target.value) || 0)}
<input
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 }}
/>
</div>
@@ -125,10 +153,12 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile }) {
const handleImagePick = async e => {
const file = e.target.files?.[0];
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 {
const compressed = await compressImage(file);
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 = '';
};