merge: combine all features from both codebases - mobile dashboard + layer panel + notifications + shares + all fixes
This commit is contained in:
@@ -1,22 +1,75 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { PropertiesPanelProps } from '../types/ui.types';
|
||||
import { formatInputValue, parseDistance, type UnitType } from '../utils/format';
|
||||
|
||||
const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ selectedElement, layers, onUpdateProperty, onDelete }) => {
|
||||
const parseNum = (s: string) => parseFloat(s.replace(/[^0-9.\-]/g, '')) || 0;
|
||||
|
||||
const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
||||
selectedElement,
|
||||
layers,
|
||||
onUpdateProperty,
|
||||
onDelete,
|
||||
unit = 'mm',
|
||||
scaleFactor = 1,
|
||||
}) => {
|
||||
const el = selectedElement;
|
||||
const x = el ? String(el.x) : '0';
|
||||
const y = el ? String(el.y) : '0';
|
||||
const w = el ? String(el.width) : '0.5';
|
||||
const h = el ? String(el.height) : '0.5';
|
||||
const rot = el ? String(el.properties.rotation ?? 0) : '0';
|
||||
|
||||
// Local input state — allows user to type freely, synced when element or unit changes
|
||||
const [xInput, setXInput] = useState('');
|
||||
const [yInput, setYInput] = useState('');
|
||||
const [wInput, setWInput] = useState('');
|
||||
const [hInput, setHInput] = useState('');
|
||||
const [rotInput, setRotInput] = useState('');
|
||||
const [radiusInput, setRadiusInput] = useState('');
|
||||
|
||||
// Sync displayed values when element changes or unit changes
|
||||
useEffect(() => {
|
||||
if (el) {
|
||||
setXInput(formatInputValue(el.x, unit, scaleFactor));
|
||||
setYInput(formatInputValue(el.y, unit, scaleFactor));
|
||||
setWInput(formatInputValue(el.width, unit, scaleFactor));
|
||||
setHInput(formatInputValue(el.height, unit, scaleFactor));
|
||||
setRotInput(String(el.properties.rotation ?? 0));
|
||||
const radius = el.properties.radius as number | undefined;
|
||||
setRadiusInput(radius !== undefined ? formatInputValue(radius, unit, scaleFactor) : '');
|
||||
}
|
||||
}, [el, unit, scaleFactor]);
|
||||
|
||||
const color = (el?.properties.stroke as string) || '#3b82f6';
|
||||
const blockName = el?.properties.blockId || 'Stuhl-Standard';
|
||||
const desc = el?.properties.text as string || 'Standard-Konferenzstuhl 0.5×0.5m';
|
||||
const blockName = el?.properties.blockId || '';
|
||||
const desc = (el?.properties.text as string) || '';
|
||||
|
||||
const handleXChange = (val: string) => {
|
||||
setXInput(val);
|
||||
const world = parseDistance(val, unit, scaleFactor);
|
||||
onUpdateProperty('x', world);
|
||||
};
|
||||
const handleYChange = (val: string) => {
|
||||
setYInput(val);
|
||||
const world = parseDistance(val, unit, scaleFactor);
|
||||
onUpdateProperty('y', world);
|
||||
};
|
||||
const handleWChange = (val: string) => {
|
||||
setWInput(val);
|
||||
const world = parseDistance(val, unit, scaleFactor);
|
||||
onUpdateProperty('width', world);
|
||||
};
|
||||
const handleHChange = (val: string) => {
|
||||
setHInput(val);
|
||||
const world = parseDistance(val, unit, scaleFactor);
|
||||
onUpdateProperty('height', world);
|
||||
};
|
||||
const handleRadiusChange = (val: string) => {
|
||||
setRadiusInput(val);
|
||||
const world = parseDistance(val, unit, scaleFactor);
|
||||
onUpdateProperty('radius', world);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title">
|
||||
<span>Auswahl · 1 Stuhl</span>
|
||||
<span>Auswahl {el ? `· ${el.type}` : ''}</span>
|
||||
<div style={{ display: 'flex', gap: '4px' }}>
|
||||
{onDelete && el && (
|
||||
<button
|
||||
@@ -38,24 +91,30 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ selectedElement, laye
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title"><span>Geometrie</span></div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Position X</span>
|
||||
<input className="prop-input" type="text" value={x} aria-label="Position X" onChange={(e) => onUpdateProperty('x', parseFloat(e.target.value) || 0)} />
|
||||
<span className="prop-label">Position X ({unit})</span>
|
||||
<input className="prop-input" type="text" value={xInput} aria-label="Position X" onChange={(e) => handleXChange(e.target.value)} />
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Position Y</span>
|
||||
<input className="prop-input" type="text" value={y} aria-label="Position Y" onChange={(e) => onUpdateProperty('y', parseFloat(e.target.value) || 0)} />
|
||||
<span className="prop-label">Position Y ({unit})</span>
|
||||
<input className="prop-input" type="text" value={yInput} aria-label="Position Y" onChange={(e) => handleYChange(e.target.value)} />
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Breite</span>
|
||||
<input className="prop-input" type="text" value={w} aria-label="Breite" onChange={(e) => onUpdateProperty('width', parseFloat(e.target.value) || 0)} />
|
||||
<span className="prop-label">Breite ({unit})</span>
|
||||
<input className="prop-input" type="text" value={wInput} aria-label="Breite" onChange={(e) => handleWChange(e.target.value)} />
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Tiefe</span>
|
||||
<input className="prop-input" type="text" value={h} aria-label="Tiefe" onChange={(e) => onUpdateProperty('height', parseFloat(e.target.value) || 0)} />
|
||||
<span className="prop-label">Tiefe ({unit})</span>
|
||||
<input className="prop-input" type="text" value={hInput} aria-label="Tiefe" onChange={(e) => handleHChange(e.target.value)} />
|
||||
</div>
|
||||
{el?.type === 'circle' && (
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Radius ({unit})</span>
|
||||
<input className="prop-input" type="text" value={radiusInput} aria-label="Radius" onChange={(e) => handleRadiusChange(e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Drehung</span>
|
||||
<input className="prop-input" type="text" value={rot} aria-label="Drehung" onChange={(e) => onUpdateProperty('rotation', parseFloat(e.target.value) || 0)} />
|
||||
<span className="prop-label">Drehung (°)</span>
|
||||
<input className="prop-input" type="text" value={rotInput} aria-label="Drehung" onChange={(e) => { setRotInput(e.target.value); onUpdateProperty('rotation', parseNum(e.target.value)); }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,26 +124,26 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ selectedElement, laye
|
||||
<span className="prop-label">Farbe</span>
|
||||
<div className="prop-color-row">
|
||||
<div className="prop-color-swatch" style={{ background: color }} aria-hidden="true"></div>
|
||||
<input className="prop-swatch-input" type="color" value={color} aria-label="Stuhlfarbe" onChange={(e) => onUpdateProperty('stroke', e.target.value)} />
|
||||
<input className="prop-swatch-input" type="color" value={color} aria-label="Elementfarbe" onChange={(e) => onUpdateProperty('stroke', e.target.value)} />
|
||||
<input className="prop-input" type="text" value={color.toUpperCase()} aria-label="Farb-Hex" onChange={(e) => onUpdateProperty('stroke', e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Linientyp</span>
|
||||
<select className="prop-select" aria-label="Linientyp" value={el?.properties.lineType || 'solid'} onChange={(e) => onUpdateProperty('lineType', e.target.value)}>
|
||||
<option>Solid (durchgehend)</option>
|
||||
<option>Dashed (gestrichelt)</option>
|
||||
<option>Dotted (gepunktet)</option>
|
||||
<option>Dash-Dot</option>
|
||||
<option value="solid">Solid (durchgehend)</option>
|
||||
<option value="dashed">Dashed (gestrichelt)</option>
|
||||
<option value="dotted">Dotted (gepunktet)</option>
|
||||
<option value="dash-dot">Dash-Dot</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Stärke</span>
|
||||
<select className="prop-select" aria-label="Linienstärke" value={String(el?.properties.strokeWidth ?? '0.18')} onChange={(e) => onUpdateProperty('strokeWidth', e.target.value)}>
|
||||
<option>0.18 mm · Normal</option>
|
||||
<option>0.25 mm · Dick</option>
|
||||
<option>0.35 mm · Extra</option>
|
||||
<option>0.50 mm · Max</option>
|
||||
<select className="prop-select" aria-label="Linienstärke" value={String(el?.properties.strokeWidth ?? '1')} onChange={(e) => onUpdateProperty('strokeWidth', parseNum(e.target.value))}>
|
||||
<option value="0.18">0.18 mm · Normal</option>
|
||||
<option value="0.25">0.25 mm · Dick</option>
|
||||
<option value="0.35">0.35 mm · Extra</option>
|
||||
<option value="0.50">0.50 mm · Max</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
@@ -93,28 +152,25 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ selectedElement, laye
|
||||
{layers.length > 0 ? layers.map((l) => (
|
||||
<option key={l.id} value={l.id}>{l.name}</option>
|
||||
)) : (
|
||||
<>
|
||||
<option>Bestuhlung · Standard</option>
|
||||
<option>Bestuhlung · VIP</option>
|
||||
<option>Möbel · Tische</option>
|
||||
<option>Bühne</option>
|
||||
</>
|
||||
<option value="">Kein Layer</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title"><span>Block</span></div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Block-Name</span>
|
||||
<input className="prop-input" type="text" value={blockName} aria-label="Block-Name" onChange={(e) => onUpdateProperty('blockId', e.target.value)} />
|
||||
{el?.type === 'block_instance' && (
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title"><span>Block</span></div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Block-Name</span>
|
||||
<input className="prop-input" type="text" value={blockName} aria-label="Block-Name" onChange={(e) => onUpdateProperty('blockId', e.target.value)} />
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Beschreibung</span>
|
||||
<input className="prop-input" type="text" value={desc} aria-label="Beschreibung" onChange={(e) => onUpdateProperty('text', e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="prop-row">
|
||||
<span className="prop-label">Beschreibung</span>
|
||||
<input className="prop-input" type="text" value={desc} aria-label="Beschreibung" onChange={(e) => onUpdateProperty('text', e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user