Elemente: BIM Project Browser + Properties-Satellite-Window
Zwei neue Satellite-Windows (analog Kamera/Text-Editor): 1) Projekt-Übersicht (elemente_uebersicht.py + ElementeUebersichtApp.jsx) - Tree Geschoss → Kind → Element-Instanzen - Suche + Kind-Filter-Chips - Klick = selektieren in Rhino, Shift+Klick = zoomen - Erreichbar via account_tree-Button im Elemente-Panel-Header 2) Properties-Satellite (elemente_properties.py + ElementePropertiesApp.jsx) - Eigenes Fenster mit der PropertiesView (gemeinsame Komponente) - Live-Updates: elemente._send_state forwarded zu satellite-bridge via sticky - Erreichbar via open_in_new-Icon oben rechts in der Properties-Karte - Inline-Properties im Panel bleiben — Satellite ist für mehr Platz Plus ElementeApp-Cleanup: - ElementList (alle Elemente-Liste) raus — wird jetzt von Projekt- Übersicht abgedeckt. - Properties springen bei Selektion nach oben, NeuesElement bleibt voll sichtbar darunter (kein Scrollen mehr). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+164
-239
@@ -1,13 +1,13 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Icon from './components/Icon'
|
||||
import { BarToggle, BarButton } from './components/BarControls'
|
||||
import {
|
||||
onMessage, notifyReady,
|
||||
listElemente, createWall, createDecke, createDach,
|
||||
createWall, createDecke, createDach,
|
||||
createFenster, createTuer, createAussparung, createTreppe,
|
||||
createStuetze, createTraeger, createRaum,
|
||||
exportRaeume,
|
||||
openSwisstopo, openSwisstopoDialog, openOsmDialog,
|
||||
updateElement, deleteElement, regenerateAllElements,
|
||||
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
||||
} from './lib/rhinoBridge'
|
||||
|
||||
const labelXs = {
|
||||
@@ -29,17 +29,15 @@ function ReferenzSelector({ value, onChange }) {
|
||||
{ code: 'right', label: 'Rechts', hint: 'Achse auf rechter Aussenseite' },
|
||||
]
|
||||
return (
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{opts.map(o => (
|
||||
<button
|
||||
<BarToggle
|
||||
key={o.code}
|
||||
label={o.label}
|
||||
active={value === o.code}
|
||||
onClick={() => onChange(o.code)}
|
||||
className={value === o.code ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}
|
||||
title={o.hint}
|
||||
>
|
||||
{o.label}
|
||||
</button>
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
@@ -213,10 +211,6 @@ function ElementList({ elements }) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span style={{ ...labelXs, flex: 1 }}>Alle Elemente</span>
|
||||
@@ -328,7 +322,7 @@ function ElementListRow({ el, meta }) {
|
||||
}
|
||||
|
||||
|
||||
function NeuesElementSection({ noGeschoss, activeName }) {
|
||||
function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
|
||||
const [treppeMenuOpen, setTreppeMenuOpen] = useState(false)
|
||||
const [stuetzeMenuOpen, setStuetzeMenuOpen] = useState(false)
|
||||
const [traegerMenuOpen, setTraegerMenuOpen] = useState(false)
|
||||
@@ -380,13 +374,17 @@ function NeuesElementSection({ noGeschoss, activeName }) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 10,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
marginBottom: 8,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 10 }}>
|
||||
<span style={labelXs}>Neues Element</span>
|
||||
<BarToggle
|
||||
icon="account_tree"
|
||||
label={`Projektübersicht${elementsCount > 0 ? ' · ' + elementsCount : ''}`}
|
||||
onClick={() => openElementeUebersicht()}
|
||||
disabled={elementsCount === 0}
|
||||
title={elementsCount > 0
|
||||
? `Projektübersicht öffnen — ${elementsCount} Elemente`
|
||||
: 'Noch keine Elemente vorhanden'} />
|
||||
<div style={{ flex: 1 }} />
|
||||
<span style={{ color: noGeschoss ? 'var(--danger)' : 'var(--text-muted)' }}>
|
||||
{noGeschoss ? 'Kein Geschoss aktiv' : 'auf'}
|
||||
@@ -494,6 +492,39 @@ function NeuesElementSection({ noGeschoss, activeName }) {
|
||||
}
|
||||
|
||||
|
||||
// PropertiesView: gemeinsame Komponente, rendert die passende Property-
|
||||
// Form je nach Element-Typ. Wiederverwendbar in Inline + Satellite-Window.
|
||||
export function PropertiesView({ selected, geschosse, materials, hatchPatterns }) {
|
||||
if (!selected) return null
|
||||
const upd = (p) => updateElement(selected.id, p)
|
||||
const del = (label) => () => { if (window.confirm(`${label} löschen?`)) deleteElement(selected.id) }
|
||||
if (selected.kind === 'wand')
|
||||
return <WallProperties wall={selected} geschosse={geschosse} materials={materials || []}
|
||||
onUpdate={upd} onDelete={del('Wand')} />
|
||||
if (selected.kind === 'decke')
|
||||
return <DeckenProperties decke={selected} geschosse={geschosse}
|
||||
onUpdate={upd} onDelete={del('Decke')} />
|
||||
if (selected.kind === 'dach')
|
||||
return <DachProperties dach={selected} geschosse={geschosse}
|
||||
onUpdate={upd} onDelete={del('Dach')} />
|
||||
if (selected.kind === 'treppe')
|
||||
return <TreppeProperties treppe={selected} geschosse={geschosse}
|
||||
onUpdate={upd} onDelete={del('Treppe')} />
|
||||
if (selected.kind === 'stuetze' || selected.kind === 'traeger') {
|
||||
const lbl = (KIND_META[selected.kind] || {}).label || 'Element'
|
||||
return <TragwerkProperties el={selected} onUpdate={upd} onDelete={del(lbl)} />
|
||||
}
|
||||
if (selected.kind === 'raum')
|
||||
return <RaumProperties raum={selected} geschosse={geschosse}
|
||||
hatchPatterns={hatchPatterns} onUpdate={upd} onDelete={del('Raum')} />
|
||||
if (selected.kind === 'aussparung')
|
||||
return <AussparungProperties aussp={selected} onDelete={del('Aussparung')} />
|
||||
// fenster/tuer
|
||||
return <OeffnungProperties oeff={selected} onUpdate={upd}
|
||||
onDelete={del(selected.kind === 'fenster' ? 'Fenster' : 'Tür')} />
|
||||
}
|
||||
|
||||
|
||||
export default function ElementeApp() {
|
||||
const [state, setState] = useState({
|
||||
elements: [], geschosse: [], selection: null,
|
||||
@@ -520,110 +551,29 @@ export default function ElementeApp() {
|
||||
background: 'var(--bg-base)', color: 'var(--text-primary)',
|
||||
fontFamily: 'var(--font)', fontSize: 11,
|
||||
}}>
|
||||
{/* Header */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 6,
|
||||
padding: '8px 10px', borderBottom: '1px solid var(--border)',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<span style={{ flex: 1, fontWeight: 600 }}>Elemente</span>
|
||||
<span className="chip" style={{ fontSize: 8 }}>{elements.length}</span>
|
||||
<button
|
||||
onClick={() => exportRaeume()}
|
||||
className="btn-icon-tonal"
|
||||
disabled={!elements.some(e => e.kind === 'raum')}
|
||||
title="Raumliste als CSV exportieren"
|
||||
>
|
||||
<Icon name="download" size={14} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => regenerateAllElements()}
|
||||
className="btn-icon-tonal"
|
||||
disabled={elements.length === 0}
|
||||
title="Alle Elemente neu generieren (z.B. nach Geschoss-Änderung)"
|
||||
>
|
||||
<Icon name="sync" size={14} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => listElemente()}
|
||||
className="btn-icon-tonal"
|
||||
title="Aktualisieren"
|
||||
>
|
||||
<Icon name="refresh" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: 8 }}>
|
||||
{/* Element-Toolbar: kategorisierte Pills */}
|
||||
{/* Bei Selektion: Properties OBEN, NeuesElement darunter.
|
||||
Ohne Selektion: nur NeuesElement. Die volle Element-Liste
|
||||
kommt jetzt aus der Projekt-Uebersicht (account_tree-Button). */}
|
||||
{selected && (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div style={{ position: 'absolute', top: 8, right: 38, zIndex: 1 }}>
|
||||
<BarButton icon="open_in_new"
|
||||
onClick={() => openElementeProperties()}
|
||||
title="Eigenschaften in eigenem Fenster öffnen" />
|
||||
</div>
|
||||
<PropertiesView
|
||||
selected={selected}
|
||||
geschosse={geschosse}
|
||||
materials={state.materials || []}
|
||||
hatchPatterns={state.hatchPatterns} />
|
||||
</div>
|
||||
)}
|
||||
<NeuesElementSection
|
||||
noGeschoss={noGeschoss}
|
||||
activeName={activeName}
|
||||
elementsCount={elements.length}
|
||||
/>
|
||||
|
||||
|
||||
{/* Properties */}
|
||||
{selected ? (
|
||||
selected.kind === 'wand' ? (
|
||||
<WallProperties wall={selected} geschosse={geschosse}
|
||||
materials={state.materials || []}
|
||||
onUpdate={(p) => updateElement(selected.id, p)}
|
||||
onDelete={() => { if (window.confirm('Wand löschen?')) deleteElement(selected.id) }} />
|
||||
) : selected.kind === 'decke' ? (
|
||||
<DeckenProperties decke={selected} geschosse={geschosse}
|
||||
onUpdate={(p) => updateElement(selected.id, p)}
|
||||
onDelete={() => { if (window.confirm('Decke löschen?')) deleteElement(selected.id) }} />
|
||||
) : selected.kind === 'dach' ? (
|
||||
<DachProperties dach={selected} geschosse={geschosse}
|
||||
onUpdate={(p) => updateElement(selected.id, p)}
|
||||
onDelete={() => { if (window.confirm('Dach löschen?')) deleteElement(selected.id) }} />
|
||||
) : selected.kind === 'treppe' ? (
|
||||
<TreppeProperties treppe={selected} geschosse={geschosse}
|
||||
onUpdate={(p) => updateElement(selected.id, p)}
|
||||
onDelete={() => { if (window.confirm('Treppe löschen?')) deleteElement(selected.id) }} />
|
||||
) : (selected.kind === 'stuetze' || selected.kind === 'traeger') ? (
|
||||
<TragwerkProperties el={selected}
|
||||
onUpdate={(p) => updateElement(selected.id, p)}
|
||||
onDelete={() => {
|
||||
const lbl = (KIND_META[selected.kind] || {}).label || 'Element'
|
||||
if (window.confirm(`${lbl} löschen?`)) deleteElement(selected.id)
|
||||
}} />
|
||||
) : selected.kind === 'raum' ? (
|
||||
<RaumProperties raum={selected} geschosse={geschosse}
|
||||
hatchPatterns={state.hatchPatterns}
|
||||
onUpdate={(p) => updateElement(selected.id, p)}
|
||||
onDelete={() => { if (window.confirm('Raum löschen?')) deleteElement(selected.id) }} />
|
||||
) : selected.kind === 'aussparung' ? (
|
||||
<AussparungProperties aussp={selected}
|
||||
onDelete={() => { if (window.confirm('Aussparung löschen?')) deleteElement(selected.id) }} />
|
||||
) : (
|
||||
<OeffnungProperties oeff={selected}
|
||||
onUpdate={(p) => updateElement(selected.id, p)}
|
||||
onDelete={() => {
|
||||
const label = selected.kind === 'fenster' ? 'Fenster' : 'Tür'
|
||||
if (window.confirm(`${label} löschen?`)) deleteElement(selected.id)
|
||||
}} />
|
||||
)
|
||||
) : (
|
||||
<div style={{
|
||||
padding: '20px 16px', textAlign: 'center',
|
||||
color: 'var(--text-muted)', fontSize: 11,
|
||||
border: '1px dashed var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
background: 'var(--bg-section)',
|
||||
marginBottom: 8,
|
||||
}}>
|
||||
<Icon name="touch_app" size={24} style={{ color: 'var(--text-muted)', opacity: 0.5 }} />
|
||||
<div style={{ marginTop: 6 }}>Kein Element selektiert.</div>
|
||||
<div style={{ marginTop: 4, fontSize: 10 }}>
|
||||
Eine Wand-Achse oder Decken-Outline in Rhino auswählen.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Liste aller Elemente */}
|
||||
{elements.length > 0 && (
|
||||
<ElementList elements={elements} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -665,7 +615,7 @@ function TragwerkProperties({ el, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -681,7 +631,7 @@ function TragwerkProperties({ el, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>
|
||||
Profil
|
||||
</span>
|
||||
<select value={profil}
|
||||
@@ -715,7 +665,7 @@ function TragwerkProperties({ el, onUpdate, onDelete }) {
|
||||
onCommit={(v) => onUpdate({ angle: v })} />
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>
|
||||
{isStuetze ? 'Höhe' : 'OK über UK'}
|
||||
</span>
|
||||
<input type="text" value={zRaw}
|
||||
@@ -767,7 +717,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -783,7 +733,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Geschoss</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Geschoss</span>
|
||||
<select value={raum.geschoss}
|
||||
onChange={(e) => onUpdate({ geschoss: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
@@ -792,7 +742,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Nummer</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Nummer</span>
|
||||
<input type="text" value={nummer}
|
||||
onChange={(e) => setNummer(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -804,7 +754,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Name</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Name</span>
|
||||
<input type="text" value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -817,7 +767,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Typ</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Typ</span>
|
||||
<select value={raum.sia || ''}
|
||||
onChange={(e) => onUpdate({ sia: e.target.value })}
|
||||
title="SIA 416 Flaechenklassifikation"
|
||||
@@ -832,7 +782,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Füllung</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Füllung</span>
|
||||
<select value={fuell}
|
||||
onChange={(e) => onUpdate({ fuellung: e.target.value })}
|
||||
title="Hatch-Pattern im Normalmodus. Bei aktivem SIA-Modus wird klassifizierten Raeumen automatisch Solid forciert."
|
||||
@@ -847,7 +797,7 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Rundung</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Rundung</span>
|
||||
<select value={raum.rundung || ''}
|
||||
onChange={(e) => onUpdate({ rundung: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}
|
||||
@@ -862,24 +812,20 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns })
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Ausrichtung</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Ausrichtung</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{RAUM_ALIGN.map(a => (
|
||||
<button key={a.code}
|
||||
<BarToggle key={a.code}
|
||||
icon={a.icon}
|
||||
active={(raum.align || 'mid') === a.code}
|
||||
onClick={() => onUpdate({ align: a.code })}
|
||||
className={(raum.align || 'mid') === a.code ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 4px', fontSize: 10,
|
||||
display: 'flex', alignItems: 'center',
|
||||
justifyContent: 'center', gap: 3 }}
|
||||
title={a.label}>
|
||||
<Icon name={a.icon} size={12} />
|
||||
</button>
|
||||
title={a.label} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 60 }}>Texthöhe</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Texthöhe</span>
|
||||
<input type="text" value={txtH}
|
||||
onChange={(e) => setTxtH(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -912,7 +858,7 @@ function AussparungProperties({ aussp, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -964,7 +910,7 @@ function WallProperties({ wall, geschosse, materials, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -978,7 +924,7 @@ function WallProperties({ wall, geschosse, materials, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Geschoss</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Geschoss</span>
|
||||
<select value={wall.geschoss}
|
||||
onChange={(e) => onUpdate({ geschoss: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
@@ -987,25 +933,20 @@ function WallProperties({ wall, geschosse, materials, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Aufbau</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<button
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Aufbau</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
<BarToggle label="Solid" active={!wall.layered}
|
||||
onClick={() => onUpdate({ layered: false })}
|
||||
className={!wall.layered ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}
|
||||
title="Eine homogene Wand-Schicht (Standard)">Solid</button>
|
||||
<button
|
||||
title="Eine homogene Wand-Schicht (Standard)" />
|
||||
<BarToggle label="Mehrschichtig" active={wall.layered}
|
||||
onClick={() => onUpdate({ layered: true })}
|
||||
className={wall.layered ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}
|
||||
title="Mehrere Schichten mit individuellen Dicken und Farben"
|
||||
>Mehrschichtig</button>
|
||||
title="Mehrere Schichten mit individuellen Dicken und Farben" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!wall.layered && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Dicke</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Dicke</span>
|
||||
<input type="text" value={dicke}
|
||||
onChange={(e) => setDicke(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -1025,7 +966,7 @@ function WallProperties({ wall, geschosse, materials, onUpdate, onDelete }) {
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Referenz</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Referenz</span>
|
||||
<ReferenzSelector value={wall.referenz || 'mid'}
|
||||
onChange={(v) => onUpdate({ referenz: v })} />
|
||||
</div>
|
||||
@@ -1090,11 +1031,10 @@ function LayersEditor({ layers, onChange, materials }) {
|
||||
onChange={(patch) => updateAt(i, patch)}
|
||||
onRemove={() => removeAt(i)} />
|
||||
))}
|
||||
<button onClick={addLayer} className="btn-outlined"
|
||||
style={{ padding: '3px 6px', fontSize: 10, marginTop: 2 }}
|
||||
title="Neue Schicht unten anfügen">
|
||||
+ Schicht
|
||||
</button>
|
||||
<div style={{ marginTop: 2 }}>
|
||||
<BarToggle label="+ Schicht" onClick={addLayer}
|
||||
title="Neue Schicht unten anfügen" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1183,7 +1123,7 @@ function DeckenProperties({ decke, geschosse, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1197,7 +1137,7 @@ function DeckenProperties({ decke, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Geschoss</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Geschoss</span>
|
||||
<select value={decke.geschoss}
|
||||
onChange={(e) => onUpdate({ geschoss: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
@@ -1206,7 +1146,7 @@ function DeckenProperties({ decke, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Dicke</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Dicke</span>
|
||||
<input type="text" value={dicke}
|
||||
onChange={(e) => setDicke(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -1259,7 +1199,7 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1274,7 +1214,7 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
|
||||
{/* Dach-Typ */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Typ</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Typ</span>
|
||||
<select value={dachTyp}
|
||||
onChange={(e) => onUpdate({ dachTyp: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
@@ -1286,7 +1226,7 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Geschoss</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Geschoss</span>
|
||||
<select value={dach.geschoss}
|
||||
onChange={(e) => onUpdate({ geschoss: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
@@ -1295,7 +1235,7 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Dicke</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Dicke</span>
|
||||
<input type="text" value={dicke}
|
||||
onChange={(e) => setDicke(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -1308,7 +1248,7 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Neigung</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Neigung</span>
|
||||
<input type="text" value={neigung}
|
||||
onChange={(e) => setNeigung(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -1323,7 +1263,7 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
|
||||
{dachTyp === 'pult' && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Index der Traufkante (0 = Kante zwischen 1. und 2. Punkt)">
|
||||
Traufe
|
||||
</span>
|
||||
@@ -1344,7 +1284,7 @@ function DachProperties({ dach, geschosse, onUpdate, onDelete }) {
|
||||
{dachTyp === 'mansarde' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Variante</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Variante</span>
|
||||
<select value={dach.dachVariante || 'walm'}
|
||||
onChange={(e) => onUpdate({ dachVariante: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
@@ -1379,7 +1319,7 @@ function MansardeFields({ dach, onUpdate }) {
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Untere (steile) Neigung — bis zum Knick">
|
||||
Steil
|
||||
</span>
|
||||
@@ -1395,7 +1335,7 @@ function MansardeFields({ dach, onUpdate }) {
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)' }}>°</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Höhe über Traufe wo der Knick sitzt">
|
||||
Knick H
|
||||
</span>
|
||||
@@ -1577,7 +1517,7 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1591,7 +1531,7 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Start</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Start</span>
|
||||
<select value={treppe.geschoss}
|
||||
onChange={(e) => onUpdate({ geschoss: e.target.value })}
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
@@ -1600,7 +1540,7 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Ziel</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Ziel</span>
|
||||
<select
|
||||
value={hasHOver ? '__custom__' : (treppe.geschossEnd || '')}
|
||||
onChange={(e) => {
|
||||
@@ -1621,7 +1561,7 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Breite</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Breite</span>
|
||||
<input type="text" value={breite}
|
||||
onChange={(e) => setBreite(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -1635,7 +1575,7 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Stufen</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Stufen</span>
|
||||
<input type="text" value={nStufen}
|
||||
onChange={(e) => setNStufen(e.target.value)}
|
||||
onBlur={() => {
|
||||
@@ -1649,15 +1589,13 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Lage</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Lage</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{REF_OPTIONS.map(o => (
|
||||
<button key={o.code}
|
||||
onClick={() => onUpdate({ treppeReferenz: o.code })}
|
||||
className={ref === o.code ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}>
|
||||
{o.label}
|
||||
</button>
|
||||
<BarToggle key={o.code}
|
||||
label={o.label}
|
||||
active={ref === o.code}
|
||||
onClick={() => onUpdate({ treppeReferenz: o.code })} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1666,19 +1604,17 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
|
||||
{/* Unterseite-Modus */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Form der Treppen-Unterseite">
|
||||
Unten
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{MODUS_OPTIONS.map(o => (
|
||||
<button key={o.code}
|
||||
<BarToggle key={o.code}
|
||||
label={o.label}
|
||||
active={modus === o.code}
|
||||
onClick={() => onUpdate({ treppeModus: o.code })}
|
||||
className={modus === o.code ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}
|
||||
title={o.hint}>
|
||||
{o.label}
|
||||
</button>
|
||||
title={o.hint} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1686,7 +1622,7 @@ function TreppeProperties({ treppe, geschosse, onUpdate, onDelete }) {
|
||||
{/* Lauf-Plattendicke (nur fuer flach + plattenrand relevant) */}
|
||||
{modus !== 'massiv' && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Dicke der Lauf-Platte (Materialdicke unter den Stufen)">
|
||||
Platte
|
||||
</span>
|
||||
@@ -1782,7 +1718,7 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
border: '1px solid var(--accent)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -1796,7 +1732,7 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Breite</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Breite</span>
|
||||
<input type="text" value={breite}
|
||||
onChange={(e) => setBreite(e.target.value)}
|
||||
onBlur={() => commit('breite', breite, setBreite, oeff.breite)}
|
||||
@@ -1806,7 +1742,7 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>Höhe</span>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Höhe</span>
|
||||
<input type="text" value={hoehe}
|
||||
onChange={(e) => setHoehe(e.target.value)}
|
||||
onBlur={() => commit('hoehe', hoehe, setHoehe, oeff.hoehe)}
|
||||
@@ -1816,7 +1752,7 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title={isFenster
|
||||
? 'Brüstungshöhe über UK der Wand'
|
||||
: 'Türschwelle / Höhe über UK der Wand'}>
|
||||
@@ -1836,19 +1772,17 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
|
||||
{/* Referenz-Lage: wo sitzt der Klick-Punkt in der Oeffnung */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Lage des Klick-Punkts in der Öffnung">
|
||||
Ref
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{OEFF_REFERENZ_OPTIONS.map(o => (
|
||||
<button key={o.code}
|
||||
<BarToggle key={o.code}
|
||||
label={o.label}
|
||||
active={(oeff.oeffReferenz || 'mid') === o.code}
|
||||
onClick={() => onUpdate({ oeffReferenz: o.code })}
|
||||
className={(oeff.oeffReferenz || 'mid') === o.code ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}
|
||||
title={o.hint}>
|
||||
{o.label}
|
||||
</button>
|
||||
title={o.hint} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1857,7 +1791,7 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
|
||||
{/* Rahmen-Profil (Breite × Tiefe) — beide Felder gleich breit */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Rahmen-Profil: Breite (in Wandflaeche) × Tiefe (entlang Wandnormale)">
|
||||
Rahmen
|
||||
</span>
|
||||
@@ -1879,19 +1813,17 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
|
||||
{/* Rahmen-Lage im Wandquerschnitt */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Lage des Rahmens im Wandquerschnitt">
|
||||
Lage
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{RAHMEN_POS_OPTIONS.map(o => (
|
||||
<button key={o.code}
|
||||
<BarToggle key={o.code}
|
||||
label={o.label}
|
||||
active={rahmenPos === o.code}
|
||||
onClick={() => onUpdate({ rahmenPos: o.code })}
|
||||
className={rahmenPos === o.code ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}
|
||||
title={o.hint}>
|
||||
{o.label}
|
||||
</button>
|
||||
title={o.hint} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1899,18 +1831,16 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
{/* Fluegel-Anzahl — nur fuer Fenster (Tueren haben ein einzelnes Tuerblatt) */}
|
||||
{isFenster && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Anzahl Flügel (vertikale Unterteilung)">
|
||||
Flügel
|
||||
</span>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 2 }}>
|
||||
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
|
||||
{[1, 2, 3, 4].map(n => (
|
||||
<button key={n}
|
||||
onClick={() => onUpdate({ fluegel: n })}
|
||||
className={fluegel === n ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}>
|
||||
{n}
|
||||
</button>
|
||||
<BarToggle key={n}
|
||||
label={String(n)}
|
||||
active={fluegel === n}
|
||||
onClick={() => onUpdate({ fluegel: n })} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1920,7 +1850,7 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
{isFenster && (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Aussensims — Platte unter Öffnung, ragt aussen heraus">
|
||||
Sims a.
|
||||
</span>
|
||||
@@ -1932,7 +1862,7 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||
title="Innensims — Platte unter Öffnung, ragt innen heraus">
|
||||
Sims i.
|
||||
</span>
|
||||
@@ -1948,13 +1878,11 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
|
||||
{/* Glas-Toggle: bei Tueren ersetzt Glas das Tuerblatt (verglaste Tuer) */}
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<button
|
||||
<BarToggle
|
||||
label={(isFenster ? 'Glas' : 'Verglast') + (oeff.glas ? ' ✓' : '')}
|
||||
active={!!oeff.glas}
|
||||
onClick={() => onUpdate({ glas: !oeff.glas })}
|
||||
className={oeff.glas ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ flex: 1, padding: '3px 6px', fontSize: 10 }}
|
||||
title={isFenster ? 'Glasscheibe sichtbar' : 'Verglaste Tür (statt Türblatt)'}>
|
||||
{isFenster ? 'Glas' : 'Verglast'} {oeff.glas ? '✓' : ''}
|
||||
</button>
|
||||
title={isFenster ? 'Glasscheibe sichtbar' : 'Verglaste Tür (statt Türblatt)'} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -1965,13 +1893,10 @@ function OeffnungProperties({ oeff, onUpdate, onDelete }) {
|
||||
function AutoOverrideField({ label, auto, autoValue, rawValue, onChangeRaw, onToggle, onCommit }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-muted)', width: 50 }}>{label}</span>
|
||||
<button onClick={onToggle}
|
||||
className={auto ? 'btn-contained' : 'btn-outlined'}
|
||||
style={{ padding: '3px 8px', fontSize: 10 }}
|
||||
title={auto ? 'Folgt Geschoss' : 'Eigener Wert'}>
|
||||
{auto ? 'Auto' : 'Custom'}
|
||||
</button>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>{label}</span>
|
||||
<BarToggle label={auto ? 'Auto' : 'Custom'} active={auto}
|
||||
onClick={onToggle}
|
||||
title={auto ? 'Folgt Geschoss' : 'Eigener Wert'} />
|
||||
<input type="text"
|
||||
value={auto ? fmtNum(autoValue) : rawValue}
|
||||
disabled={auto}
|
||||
|
||||
Reference in New Issue
Block a user