import { useState, useRef, useCallback, useEffect } from 'react';
import { api, S } from '../lib.js';
// ── Hilfsfunktionen ──────────────────────────────────────────────────────────
const uid = () => Math.random().toString(36).slice(2,8);
const dist = (x1,y1,x2,y2) => Math.sqrt((x2-x1)**2+(y2-y1)**2);
const snapToGrid = (v, g) => Math.round(v / g) * g;
function parseDim(s) {
if (!s) return null;
const n = parseFloat(String(s).replace(/[^0-9.,\-]/g,'').replace(',','.'));
return isNaN(n) ? null : n;
}
function evalFormula(expr, vars) {
try {
const sanitized = expr.replace(/[^0-9+\-*/().,%a-zA-Z_\s]/g,'');
const keys = Object.keys(vars), vals = keys.map(k=>vars[k]);
// eslint-disable-next-line no-new-func
return new Function(...keys, `"use strict"; return (${sanitized});`)(...vals);
} catch { return null; }
}
function buildVars(shapes) {
const vars = {};
shapes.forEach(s => {
const n = (s.name||'').replace(/[^a-zA-Z0-9_]/g,'_');
if (!n) return;
if (s.type==='rect') {
const l=parseDim(s.dimW), b=parseDim(s.dimH);
if (l!=null) { vars[`${n}_l`]=l; vars[`${n}_b`]=b??l; }
if (l!=null&&b!=null) { vars[`${n}_fl`]=+(l*b).toFixed(4); vars[`${n}_um`]=+(2*(l+b)).toFixed(4); vars[`${n}_d`]=+(Math.sqrt(l*l+b*b)).toFixed(4); }
}
if (s.type==='line') { const v=parseDim(s.dimLen); if(v!=null) vars[`${n}_len`]=v; }
if (s.type==='circle') {
const r=parseDim(s.dimR);
if(r!=null){ vars[`${n}_r`]=r; vars[`${n}_d`]=+(r*2).toFixed(4); vars[`${n}_fl`]=+(Math.PI*r*r).toFixed(4); vars[`${n}_um`]=+(2*Math.PI*r).toFixed(4); }
}
});
return vars;
}
// ── Row-Komponente (AUSSERHALB um Input-Bug zu vermeiden) ─────────────────────
function DimRow({ label, value, field, placeholder, onChange }) {
return (
);
}
// ── Maßlinie ─────────────────────────────────────────────────────────────────
function DimLine({x1,y1,x2,y2,label,color='#4ecdc4',offset=18}) {
const mx=(x1+x2)/2, my=(y1+y2)/2;
const dx=x2-x1, dy=y2-y1, len=Math.sqrt(dx*dx+dy*dy);
if (len<4) return null;
const nx=-dy/len*offset, ny=dx/len*offset;
return (
{label}
);
}
// ── Shape + Mittelpunkt + Versatz ─────────────────────────────────────────────
function Shape({ s, selected, onClick }) {
const sel = selected===s.id;
const c = sel ? '#4ecdc4' : (s.color||'#7ecaff');
const offset = parseDim(s.offset) || 0; // Versatz in px (scaled)
if (s.type==='rect') {
const x=Math.min(s.x,s.x+s.w), y=Math.min(s.y,s.y+s.h);
const w=Math.abs(s.w), h=Math.abs(s.h);
const cx=x+w/2, cy=y+h/2;
const offPx = s.offsetPx||0;
return (
{e.stopPropagation();onClick(s.id);}} style={{cursor:'pointer'}}>
{/* Mittelpunkt */}
{/* Versatz */}
{offPx!==0&&0?'#ffe66d':'#ff6b9d'} strokeWidth={1} strokeDasharray="4,3" opacity={0.7}/>}
{s.name&&{s.name}}
{s.dimW&&}
{s.dimH&&}
{sel&&<>
>}
);
}
if (s.type==='line') {
const len=dist(s.x1,s.y1,s.x2,s.y2);
const mx=(s.x1+s.x2)/2, my=(s.y1+s.y2)/2;
return (
{e.stopPropagation();onClick(s.id);}} style={{cursor:'pointer'}}>
{(s.dimLen||s.name)&&len>20&&}
{sel&&<>>}
);
}
if (s.type==='circle') {
const r=Math.max(1,s.r);
const offPx=s.offsetPx||0;
return (
{e.stopPropagation();onClick(s.id);}} style={{cursor:'pointer'}}>
{offPx!==0&&0?'#ffe66d':'#ff6b9d'} strokeWidth={1} strokeDasharray="4,3" opacity={0.7}/>}
{/* Mittelpunkt */}
{/* Radiuslinie */}
{s.name&&{s.name}}
{s.dimR&&}
{sel&&}
);
}
if (s.type==='text') {
return (
{e.stopPropagation();onClick(s.id);}} style={{cursor:'pointer'}}>
{s.label||'Text'}
{sel&&}
);
}
return null;
}
// ── Properties Panel ──────────────────────────────────────────────────────────
function PropsPanel({ shape, onChange, onDelete, vars, scale, onScaleChange }) {
if (!shape) return (
Form auswählen oder zeichnen.
V=Auswählen R=Rect L=Linie C=Kreis
);
const shapeVarKeys = Object.keys(vars).filter(k=>k.startsWith((shape.name||'').replace(/[^a-zA-Z0-9_]/g,'_')+'_'));
return (
EIGENSCHAFTEN
{shape.type==='rect'&&<>
>}
{shape.type==='line'&&}
{shape.type==='circle'&&<>
>}
{shape.type==='text'&&}
{shapeVarKeys.length>0&&(
VARIABLEN
{shapeVarKeys.map(k=>(
{k}
{typeof vars[k]==='number'?vars[k].toFixed(2):vars[k]}
))}
)}
);
}
// ── Main ──────────────────────────────────────────────────────────────────────
const TOOLS = [
{id:'select',label:'▲',title:'Auswählen (V)'},
{id:'rect', label:'▭',title:'Rechteck (R)'},
{id:'line', label:'╱',title:'Linie (L)'},
{id:'circle',label:'○',title:'Kreis (C)'},
{id:'text', label:'T', title:'Text (T)'},
];
const GRID = 10;
const CALC_BTNS = ['(',')',' ','7','8','9','×','4','5','6','÷','1','2','3','-','0','.','=','+'];
export default function Skizze({ toast, mobile }) {
const [shapes, setShapes] = useState([]);
const [selected, setSelected] = useState(null);
const [tool, setTool] = useState('select');
const [drawing, setDrawing] = useState(null);
const [formula, setFormula] = useState('');
const [results, setResults] = useState([]);
const [snapGrid, setSnapGrid] = useState(true);
const [scale, setScale] = useState(1); // mm per pixel
const svgRef = useRef(null);
const formulaRef= useRef(null);
const vars = buildVars(shapes);
const selShape = shapes.find(s=>s.id===selected)||null;
// Keyboard shortcuts
useEffect(()=>{
const h = e => {
if (e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA') return;
const map={v:'select',r:'rect',l:'line',c:'circle',t:'text'};
if (map[e.key.toLowerCase()]) setTool(map[e.key.toLowerCase()]);
if ((e.key==='Delete'||e.key==='Backspace')&&selected) deleteSelected();
if (e.key==='Escape') { setSelected(null); setDrawing(null); }
};
window.addEventListener('keydown',h);
return()=>window.removeEventListener('keydown',h);
},[selected]);
const getSVGPoint = useCallback(e=>{
const rect=svgRef.current?.getBoundingClientRect();
if (!rect) return{x:0,y:0};
const cx=e.touches?e.touches[0].clientX:e.clientX;
const cy=e.touches?e.touches[0].clientY:e.clientY;
const x=cx-rect.left, y=cy-rect.top;
return {x:snapGrid?snapToGrid(x,GRID):x, y:snapGrid?snapToGrid(y,GRID):y};
},[snapGrid]);
const onSVGDown = useCallback(e=>{
if (tool==='select'){setSelected(null);return;}
const pt=getSVGPoint(e);
if (tool==='text'){
setShapes(p=>[...p,{id:uid(),type:'text',x:pt.x,y:pt.y,label:'Text',name:''}]);
setTool('select'); return;
}
setDrawing({tool,startX:pt.x,startY:pt.y,curX:pt.x,curY:pt.y});
},[tool,getSVGPoint]);
const onSVGMove = useCallback(e=>{
if (!drawing) return;
const pt=getSVGPoint(e);
setDrawing(d=>({...d,curX:pt.x,curY:pt.y}));
},[drawing,getSVGPoint]);
const onSVGUp = useCallback(e=>{
if (!drawing) return;
const pt=getSVGPoint(e);
const dx=pt.x-drawing.startX, dy=pt.y-drawing.startY;
if (Math.abs(dx)<4&&Math.abs(dy)<4){setDrawing(null);return;}
let s;
if (drawing.tool==='rect') s={id:uid(),type:'rect',x:drawing.startX,y:drawing.startY,w:dx,h:dy,name:'',dimW:'',dimH:'',dimOffset:''};
if (drawing.tool==='line') s={id:uid(),type:'line',x1:drawing.startX,y1:drawing.startY,x2:pt.x,y2:pt.y,name:'',dimLen:''};
if (drawing.tool==='circle') s={id:uid(),type:'circle',cx:drawing.startX,cy:drawing.startY,r:dist(drawing.startX,drawing.startY,pt.x,pt.y),name:'',dimR:'',dimOffset:''};
if (s){setShapes(p=>[...p,s]);setSelected(s.id);}
setDrawing(null);
},[drawing,getSVGPoint]);
const deleteSelected = ()=>{setShapes(p=>p.filter(s=>s.id!==selected));setSelected(null);};
// Update shape + auto-resize visual proportions based on scale
const updateShape = patch => {
setShapes(p=>p.map(s=>{
if (s.id!==selected) return s;
const upd={...s,...patch};
// Auto-resize rect
if (upd.type==='rect') {
const mW=parseDim(upd.dimW), mH=parseDim(upd.dimH);
if (mW!=null&&mW>0) {
upd.w=Math.sign(upd.w||1)*mW/scale;
if (mH!=null&&mH>0) upd.h=Math.sign(upd.h||1)*mH/scale;
}
// Versatz in px
const mO=parseDim(upd.dimOffset);
upd.offsetPx=mO!=null?mO/scale:0;
}
// Auto-resize circle
if (upd.type==='circle') {
const mR=parseDim(upd.dimR);
if (mR!=null&&mR>0) upd.r=mR/scale;
const mO=parseDim(upd.dimOffset);
upd.offsetPx=mO!=null?mO/scale:0;
}
return upd;
}));
};
const calcFormula = () => {
if (!formula.trim()) return;
const result=evalFormula(formula,vars);
const isArea=formula.toLowerCase().includes('_fl');
setResults(p=>[{
id:uid(), expr:formula,
result:result!=null?`${typeof result==='number'?result.toFixed(4).replace(/.?0+$/,''):result}${isArea?' mm²':' mm'}`:null,
},...p].slice(0,30));
};
const insertCalc = btn => {
if (btn==='='){calcFormula();return;}
const map={'×':'*','÷':'/',' ':' '};
const char=map[btn]??btn;
setFormula(f=>f+char);
formulaRef.current?.focus();
};
// Preview while drawing
let preview=null;
if (drawing){
const dx=drawing.curX-drawing.startX, dy=drawing.curY-drawing.startY;
const pc='#4ecdc4';
if (drawing.tool==='rect') preview=;
if (drawing.tool==='line') preview=;
if (drawing.tool==='circle'){const r=dist(drawing.startX,drawing.startY,drawing.curX,drawing.curY); preview=;}
}
const canvasH = mobile ? 260 : 420;
return (
{/* Toolbar */}
{TOOLS.map(t=>(
))}
{TOOLS.find(t=>t.id===tool)?.title}
{/* Canvas + Panel */}
{/* Rechner */}
RECHNER
{results.length>0&&
}
{/* Alle Variablen */}
{Object.keys(vars).length>0&&(
{Object.entries(vars).map(([k,v])=>(
{setFormula(f=>f+k);formulaRef.current?.focus();}}>
{k}
={typeof v==='number'?v.toFixed(2):v}
))}
)}
{/* Input */}
setFormula(e.target.value)}
onKeyDown={e=>e.key==='Enter'&&calcFormula()}
placeholder="z.B. Kasten_l / 2"
autoCapitalize="none"
style={{...S.inp,flex:1,fontFamily:"'Courier New',monospace",fontSize:13}}/>
{/* Operator-Buttons */}
{['(',')',' × ',' ÷ ',' '].map((b,i)=>(
b.trim()===''?
:
))}
{/* Ergebnisse */}
{results.length===0&&Object.keys(vars).length===0&&(
Formen zeichnen → benennen → hier rechnen
)}
{results.map(r=>(
{r.expr}
{r.result||'❌ Fehler'}
))}
);
}