Dateien nach "frontend/src/tools" hochladen
This commit is contained in:
@@ -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 = '';
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user