import { useState, useEffect, useRef } from 'react' import Icon from './components/Icon' import { onMessage, notifyReady, requestMassstab, setMassstab, zoomOneToOne, zoomExtents, zoomSelection, setMassstabDpi, detectMassstabDpi, setShowLineweights, } from './lib/rhinoBridge' const PRESETS = [ { value: 1, label: '1:1' }, { value: 5, label: '1:5' }, { value: 10, label: '1:10' }, { value: 20, label: '1:20' }, { value: 25, label: '1:25' }, { value: 50, label: '1:50' }, { value: 100, label: '1:100' }, { value: 200, label: '1:200' }, { value: 500, label: '1:500' }, { value: 1000, label: '1:1000'}, ] function fmtScale(s) { if (s == null) return '—' if (s >= 1) return '1:' + (s >= 10 ? s.toFixed(0) : s.toFixed(1)) return (1 / s).toFixed(2) + ':1' } function snapToPreset(s, tol = 0.03) { if (s == null) return null for (const p of PRESETS) { if (Math.abs(s - p.value) / p.value <= tol) return p.value } return null } function parseScale(input) { if (!input) return null const s = String(input).trim() for (const sep of [':', '=', '/']) { if (s.includes(sep)) { const [a, b] = s.split(sep, 2) const pa = parseFloat(a) const pb = parseFloat(b) if (pa > 0 && pb > 0) return pb / pa return null } } const n = parseFloat(s) return n > 0 ? n : null } // --------------------------------------------------------------------------- export default function MassstabApp() { const [state, setState] = useState({ viewName: null, parallel: false, scale: null, pixelWidth: null, pixelHeight: null, unitSystem: '?', dpi: 96, dpiSource: 'default', showLineweights: false, }) const [appliedScale, setAppliedScale] = useState(null) const appliedScaleRef = useRef(null) const [draft, setDraft] = useState('') const [dpiOpen, setDpiOpen] = useState(false) const [dpiDraft, setDpiDraft] = useState('96') useEffect(() => { onMessage('STATE', (s) => { setState((prev) => ({ ...prev, ...s })) // Backend appliedScale (gilt nur fuer aktuellen Viewport) > Live-Snap > roher Live-Wert let next = null if (typeof s?.appliedScale === 'number' && s.appliedScale > 0) { next = s.appliedScale } else if (s?.parallel && typeof s?.scale === 'number' && s.scale > 0) { const snap = snapToPreset(s.scale) next = snap != null ? snap : Math.round(s.scale * 10) / 10 } if (next != null && next > 0 && next !== appliedScaleRef.current) { setAppliedScale(next) appliedScaleRef.current = next } }) notifyReady() setTimeout(() => requestMassstab(), 50) }, []) const isPerspective = state.parallel === false const scaleVal = state.scale const dropdownValue = appliedScale != null ? String(appliedScale) : '__none__' const applyDropdown = (val) => { if (val === '__none__') return const r = parseFloat(val) if (r > 0) { setAppliedScale(r) appliedScaleRef.current = r setMassstab(r) } } const applyDraft = () => { const r = parseScale(draft) if (r != null) { setAppliedScale(r) appliedScaleRef.current = r setMassstab(r) setDraft('') } } const commitDpi = () => { const v = parseFloat(dpiDraft) if (v >= 30 && v <= 600) setMassstabDpi(v) setDpiOpen(false) } // "100%" = Viewport-Zoom auf den aktuell eingestellten Massstab snappen // (nicht: Massstab auf 1:1 setzen). Praktisch nach Pan/Zoom, um wieder // zur Soll-Skala zu kommen. const apply100 = () => { if (appliedScale && appliedScale > 0) { setMassstab(appliedScale) } } // --- Style-Bausteine ------------------------------------------------------ const cellBtn = { fontSize: 11, padding: '0 8px', height: 24, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 4, background: 'var(--bg-item)', border: '1px solid var(--border)', borderRadius: 'var(--r)', color: 'var(--text-primary)', cursor: 'pointer', whiteSpace: 'nowrap', } const cellInput = { fontSize: 11, padding: '0 6px', height: 24, minWidth: 0, background: 'var(--bg-input)', border: '1px solid var(--border)', borderRadius: 'var(--r)', color: 'var(--text-primary)', } return (
{/* Live-Zoom Anzeige */}
{isPerspective ? '—' : fmtScale(scaleVal)}
{/* Skala-Dropdown */} {/* Freitext */} setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') applyDraft() }} onBlur={() => { if (draft) applyDraft() }} style={{ ...cellInput, width: 64 }} title="Eigenen Massstab eingeben (Enter)" />
{/* Aktions-Buttons */}
{/* Print-View / Strichstaerken-Toggle Beide Icons werden permanent gerendert; nur display: none togglet, damit die Font-Ligatur nicht neu aufgeloest wird (sonst Flackern). */} {/* Spacer */}
{/* View-Name */}
{state.viewName || '?'}{isPerspective ? ' · Persp.' : ''}
{/* DPI-Popover */}
{dpiOpen && (
Bildschirm-Aufloesung fuer Massstab-Berechnung
setDpiDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') commitDpi() }} autoFocus style={{ ...cellInput, flex: 1 }} />
)}
) }