UI-Konsistenz: shared BarControls + Tabellen-Look fuer Panels
Pill-basierte Toolbar-Primitiven aus OberleisteApp extrahiert nach src/components/BarControls.jsx — BarCombo (Dropdown), BarButton (Icon-Button), BarToggle (Label/Icon mit Active-State), BAR_H=22. OberleisteApp nutzt jetzt die geteilten Komponenten (Verhalten unveraendert). EbenenManager + GeschossManager: - Sichtbarkeits-Toolbar: native <select> + btn-icon-sm → BarCombo (mit visibility-Icon links) + BarButton add/settings. - GeschossManager Stift-Icon (edit) → Settings-Icon. - Zeilen-Layout: eckig statt Pill (margin 0, borderRadius 0, 3px Accent-Strip links fuer aktive Zeile), minHeight 24, gap 4, kompaktere Padding/Icon-Sizes — Vectorworks-naeher. DimensionenApp: - Welt/CPlane: 2x BarToggle statt btn-contained/outlined - Z-Selektor: 3x BarToggle (icon-only) - Drehen-Apply + 90°-CCW/CW: BarButton mit rotate_*-Icons (4 Preset-Buttons -90/-45/45/90 ersetzt durch 2 schnelle 90°-Buttons — passt besser in die schmale Sidebar) README aktualisiert: Runtime jetzt CPython 3.9, TextEntity-RTF-Limit dokumentiert, BarControls + text_editor/text_create erwaehnt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Rhino-8 Plugin für architektonisches Entwerfen mit smarten Bauteilen — Geschosse, Wände, Decken, Dächer, Öffnungen (Fenster/Türen), Treppen (gerade · L · Wendel). Teil der **OpenStudio-Suite** (mit Rapport als Schwestertool).
|
Rhino-8 Plugin für architektonisches Entwerfen mit smarten Bauteilen — Geschosse, Wände, Decken, Dächer, Öffnungen (Fenster/Türen), Treppen (gerade · L · Wendel). Teil der **OpenStudio-Suite** (mit Rapport als Schwestertool).
|
||||||
|
|
||||||
Die React-UI wird in Rhinos Eto.Forms-WebView über `LoadHtml` (inline) eingebettet — die Plugin-Logik läuft in IronPython3 in Rhino 8 (Mac).
|
Die React-UI wird in Rhinos Eto.Forms-WebView über `LoadHtml` (inline) eingebettet — die Plugin-Logik läuft in **CPython 3.9** (Rhino 8 Script-Editor-Engine, Mac).
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ Die React-UI wird in Rhinos Eto.Forms-WebView über `LoadHtml` (inline) eingebet
|
|||||||
| Rhino | 8 (Mac · Windows untestet) |
|
| Rhino | 8 (Mac · Windows untestet) |
|
||||||
| Node.js | ≥ 20 (für Vite 8) |
|
| Node.js | ≥ 20 (für Vite 8) |
|
||||||
| npm | ≥ 10 |
|
| npm | ≥ 10 |
|
||||||
| Python | IronPython 3 (in Rhino integriert) |
|
| Python | CPython 3.9 (Rhino 8 Script-Editor-Engine) |
|
||||||
|
|
||||||
Optional — für den Standalone-Launcher:
|
Optional — für den Standalone-Launcher:
|
||||||
|
|
||||||
@@ -105,7 +105,8 @@ for m in list(sys.modules):
|
|||||||
│ ├── MassstabApp.jsx Massstab/Display-Modes
|
│ ├── MassstabApp.jsx Massstab/Display-Modes
|
||||||
│ ├── DimensionenApp.jsx Objekt-Info (Position/Abmessungen)
|
│ ├── DimensionenApp.jsx Objekt-Info (Position/Abmessungen)
|
||||||
│ ├── OverridePanel.jsx Override-Regeln + Kombinationen
|
│ ├── OverridePanel.jsx Override-Regeln + Kombinationen
|
||||||
│ ├── components/ EbenenManager, GeschossManager, ...
|
│ ├── TextEditorApp.jsx DOSSIER-Text WYSIWYG-Editor (Rich-Text via RTF)
|
||||||
|
│ ├── components/ EbenenManager, GeschossManager, BarControls (shared Pill-UI), ...
|
||||||
│ └── lib/rhinoBridge.js React↔Python Bridge
|
│ └── lib/rhinoBridge.js React↔Python Bridge
|
||||||
├── rhino/ Backend (IronPython 3)
|
├── rhino/ Backend (IronPython 3)
|
||||||
│ ├── rhinopanel.py Haupt-Entry, Bridge-Pattern
|
│ ├── rhinopanel.py Haupt-Entry, Bridge-Pattern
|
||||||
@@ -118,6 +119,8 @@ for m in list(sys.modules):
|
|||||||
│ ├── dimensionen.py Objekt-Info Panel
|
│ ├── dimensionen.py Objekt-Info Panel
|
||||||
│ ├── gestaltung.py Gestaltung (Override-Editor)
|
│ ├── gestaltung.py Gestaltung (Override-Editor)
|
||||||
│ ├── werkzeuge.py Werkzeug-Sammlung
|
│ ├── werkzeuge.py Werkzeug-Sammlung
|
||||||
|
│ ├── text_editor.py DOSSIER-Text Backend (Frame-Pick + Rich-Text-RTF)
|
||||||
|
│ ├── text_create.py Text-Styles, Font-Apply, Selection-Settings
|
||||||
│ └── oberleiste.py Top-Menue (verbindet alle Panels)
|
│ └── oberleiste.py Top-Menue (verbindet alle Panels)
|
||||||
├── launcher/ Tauri-2 Standalone-Launcher (optional)
|
├── launcher/ Tauri-2 Standalone-Launcher (optional)
|
||||||
├── dist/ Gebaute React-App (npm run build)
|
├── dist/ Gebaute React-App (npm run build)
|
||||||
@@ -128,9 +131,10 @@ for m in list(sys.modules):
|
|||||||
|
|
||||||
## Bekannte Limitierungen
|
## Bekannte Limitierungen
|
||||||
|
|
||||||
- IronPython3-spezifisch: keine Umlaute in Source-Strings (`ue/oe/ae` statt `ü/ö/ä`); UTF-8-Header-Kommentar in allen `.py`-Files.
|
- **Python-Identifier ohne Umlaute** (`ue/oe/ae` statt `ü/ö/ä`) — UI-Strings dürfen Umlaute, Code-Bezeichner / Layer-Codes / UserString-Keys nicht. Konvention seit der Py3-Migration.
|
||||||
- **Kein Docking** der Panels (Rhinos `RegisterPanel` schlägt fehl: `"constructor must accept uint, RhinoDoc or no params"`). Panels laufen daher als schwebende `forms.Form`-Fenster.
|
- **Kein Docking** der Panels (Rhinos `RegisterPanel` schlägt fehl: `"constructor must accept uint, RhinoDoc or no params"`). Panels laufen daher als schwebende `forms.Form`-Fenster.
|
||||||
- **`LoadHtml`-inline** statt `file://`-URL — Rhinos WKWebView blockiert sonst `<script type="module">` durch CORS-Restrictions.
|
- **`LoadHtml`-inline** statt `file://`-URL — Rhinos WKWebView blockiert sonst `<script type="module">` durch CORS-Restrictions.
|
||||||
|
- **TextEntity-RTF**: Rhinos eingebauter Parser unterstützt nur `\b \i \ul \strike \fN \tab {}` plus Newline-via-`\par`-zwischen-Groups. **Kein `\fs`** (= eine TextEntity hat global eine Schriftgröße, keine per-Segment-Sizes). Newlines/Replace-Quirks siehe `_runs_to_rtf` in `rhino/text_editor.py`.
|
||||||
|
|
||||||
## Lizenz
|
## Lizenz
|
||||||
|
|
||||||
|
|||||||
+28
-36
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState, useRef } from 'react'
|
import { useEffect, useState, useRef } from 'react'
|
||||||
import Icon from './components/Icon'
|
import Icon from './components/Icon'
|
||||||
|
import { BarToggle, BarButton } from './components/BarControls'
|
||||||
import {
|
import {
|
||||||
onMessage, notifyReady,
|
onMessage, notifyReady,
|
||||||
setRefPoint, setCoordSystem,
|
setRefPoint, setCoordSystem,
|
||||||
@@ -101,24 +102,19 @@ function RefPointGrid({ ref, onChange }) {
|
|||||||
// Z-Referenz-Selektor (Bottom / Mid / Top) — kompakt, nur Icons.
|
// Z-Referenz-Selektor (Bottom / Mid / Top) — kompakt, nur Icons.
|
||||||
function RefZSelector({ z, onChange }) {
|
function RefZSelector({ z, onChange }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', gap: 2 }}>
|
<div style={{ display: 'flex', gap: 3 }}>
|
||||||
{[
|
{[
|
||||||
{ code: 'max', icon: 'vertical_align_top', title: 'Z = Top' },
|
{ code: 'max', icon: 'vertical_align_top', title: 'Z = Top' },
|
||||||
{ code: 'mid', icon: 'vertical_align_center', title: 'Z = Mid' },
|
{ code: 'mid', icon: 'vertical_align_center', title: 'Z = Mid' },
|
||||||
{ code: 'min', icon: 'vertical_align_bottom', title: 'Z = Bottom' },
|
{ code: 'min', icon: 'vertical_align_bottom', title: 'Z = Bottom' },
|
||||||
].map(opt => (
|
].map(opt => (
|
||||||
<button
|
<BarToggle
|
||||||
key={opt.code}
|
key={opt.code}
|
||||||
|
icon={opt.icon}
|
||||||
|
active={z === opt.code}
|
||||||
onClick={() => onChange(opt.code)}
|
onClick={() => onChange(opt.code)}
|
||||||
className={z === opt.code ? 'btn-contained' : 'btn-outlined'}
|
|
||||||
style={{
|
|
||||||
padding: '2px 5px', fontSize: 10,
|
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
title={opt.title}
|
title={opt.title}
|
||||||
>
|
/>
|
||||||
<Icon name={opt.icon} size={12} />
|
|
||||||
</button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -203,19 +199,19 @@ export default function DimensionenApp() {
|
|||||||
}}>
|
}}>
|
||||||
<Icon name="select_all" size={14} style={{ color: 'var(--text-muted)' }} />
|
<Icon name="select_all" size={14} style={{ color: 'var(--text-muted)' }} />
|
||||||
<span style={{ flex: 1, fontWeight: 500 }}>{selLabel()}</span>
|
<span style={{ flex: 1, fontWeight: 500 }}>{selLabel()}</span>
|
||||||
<div style={{ display: 'flex', gap: 2 }}>
|
<div style={{ display: 'flex', gap: 3 }}>
|
||||||
<button
|
<BarToggle
|
||||||
|
label="Welt"
|
||||||
|
active={state.coordSystem === 'world'}
|
||||||
onClick={() => onCoordChange('world')}
|
onClick={() => onCoordChange('world')}
|
||||||
className={state.coordSystem === 'world' ? 'btn-contained' : 'btn-outlined'}
|
|
||||||
style={{ fontSize: 10, padding: '3px 8px' }}
|
|
||||||
title="Weltkoordinaten"
|
title="Weltkoordinaten"
|
||||||
>Welt</button>
|
/>
|
||||||
<button
|
<BarToggle
|
||||||
|
label="CPlane"
|
||||||
|
active={state.coordSystem === 'cplane'}
|
||||||
onClick={() => onCoordChange('cplane')}
|
onClick={() => onCoordChange('cplane')}
|
||||||
className={state.coordSystem === 'cplane' ? 'btn-contained' : 'btn-outlined'}
|
|
||||||
style={{ fontSize: 10, padding: '3px 8px' }}
|
|
||||||
title="Aktive Konstruktionsebene"
|
title="Aktive Konstruktionsebene"
|
||||||
>CPlane</button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -321,27 +317,23 @@ export default function DimensionenApp() {
|
|||||||
<div style={{ width: 56 }}>
|
<div style={{ width: 56 }}>
|
||||||
<NumInput value={rotationDelta} onCommit={setRotationDelta} suffix="°" />
|
<NumInput value={rotationDelta} onCommit={setRotationDelta} suffix="°" />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<BarButton
|
||||||
className="btn-outlined"
|
icon="rotate_right"
|
||||||
onClick={() => { if (rotationDelta) setDimRotationZ(rotationDelta) }}
|
onClick={() => { if (rotationDelta) setDimRotationZ(rotationDelta) }}
|
||||||
disabled={!rotationDelta}
|
disabled={!rotationDelta}
|
||||||
title="Selektion um Z-Achse der aktiven Plane drehen"
|
title="Selektion um Z-Achse der aktiven Plane drehen"
|
||||||
style={{ padding: '3px 8px', fontSize: 11 }}
|
/>
|
||||||
>
|
|
||||||
<Icon name="rotate_right" size={13} />
|
|
||||||
</button>
|
|
||||||
<div style={{ flex: 1 }} />
|
<div style={{ flex: 1 }} />
|
||||||
{[-90, -45, 45, 90].map(a => (
|
<BarButton
|
||||||
<button
|
icon="rotate_90_degrees_ccw"
|
||||||
key={a}
|
onClick={() => setDimRotationZ(-90)}
|
||||||
className="btn-outlined"
|
title="90° gegen den Uhrzeigersinn"
|
||||||
onClick={() => setDimRotationZ(a)}
|
/>
|
||||||
style={{ padding: '3px 6px', fontSize: 9, minWidth: 28 }}
|
<BarButton
|
||||||
title={`${a}°`}
|
icon="rotate_90_degrees_cw"
|
||||||
>
|
onClick={() => setDimRotationZ(90)}
|
||||||
{a > 0 ? '+' : ''}{a}°
|
title="90° im Uhrzeigersinn"
|
||||||
</button>
|
/>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+6
-130
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useRef } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import Icon from './components/Icon'
|
import Icon from './components/Icon'
|
||||||
|
import { BarCombo, BarButton, BAR_H } from './components/BarControls'
|
||||||
import {
|
import {
|
||||||
onMessage, notifyReady,
|
onMessage, notifyReady,
|
||||||
requestMassstab, setMassstab,
|
requestMassstab, setMassstab,
|
||||||
@@ -73,7 +74,6 @@ function parseScale(input) {
|
|||||||
// zwischen Icon-Kompartiment und Inhalt.
|
// zwischen Icon-Kompartiment und Inhalt.
|
||||||
|
|
||||||
const PILL_H = 20 // alte Pill-Hoehe (Buttons/Chips die nicht migriert sind)
|
const PILL_H = 20 // alte Pill-Hoehe (Buttons/Chips die nicht migriert sind)
|
||||||
const BAR_H = 22 // neue Widget-Hoehe (BarSelect, BarButton, BarGroup)
|
|
||||||
|
|
||||||
const sep = {
|
const sep = {
|
||||||
width: 1, height: 20,
|
width: 1, height: 20,
|
||||||
@@ -92,135 +92,9 @@ const pillSelect = {
|
|||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
// BarCombo: dunklerer (bg-input) Pill-Container der select + optional gear
|
// BarCombo + BarButton + BAR_H jetzt zentral in ./components/BarControls.jsx —
|
||||||
// als EINE nahtlose Box rendert. Icon roh links daneben (kein Container).
|
// werden auch in Ebenen/anderen Panels verwendet.
|
||||||
// iconClickable=true macht das Icon zum Toggle-Button (Overrides etc.).
|
|
||||||
// valueAccent=true faerbt den Select-Text accent (fuer Massstab "gesetzt").
|
|
||||||
function BarCombo({
|
|
||||||
icon, iconActive, iconClickable, onIconClick, iconTitle,
|
|
||||||
value, onChange, width, title, children, disabled,
|
|
||||||
onGear, gearTitle, valueAccent,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div style={{
|
|
||||||
display: 'inline-flex', alignItems: 'center', gap: 5,
|
|
||||||
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
|
||||||
}}>
|
|
||||||
{/* Icon links — fixe Breite fuer X-Axis-Alignment zwischen Reihen.
|
|
||||||
Wenn icon=null/undefined wird kein Icon-Slot reserviert. */}
|
|
||||||
{icon && (iconClickable ? (
|
|
||||||
<button onClick={onIconClick} title={iconTitle}
|
|
||||||
style={{
|
|
||||||
width: 18, height: BAR_H,
|
|
||||||
background: 'transparent', border: 'none',
|
|
||||||
cursor: 'pointer', flexShrink: 0, padding: 0,
|
|
||||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
}}>
|
|
||||||
<Icon name={icon} size={13}
|
|
||||||
style={{ color: iconActive ? 'var(--accent)' : 'var(--text-muted)' }} />
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<span style={{
|
|
||||||
width: 18, height: BAR_H, flexShrink: 0,
|
|
||||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
}}>
|
|
||||||
<Icon name={icon} size={13} style={{ color: 'var(--text-muted)' }} />
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
{/* Combined pill: select + optional gear, gemeinsamer bg + border */}
|
|
||||||
<div title={title}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
if (disabled) return
|
|
||||||
e.currentTarget.style.borderColor = 'var(--accent)'
|
|
||||||
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.borderColor = 'var(--border)'
|
|
||||||
e.currentTarget.style.background = 'var(--bg-input)'
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
display: 'inline-flex', alignItems: 'stretch',
|
|
||||||
height: BAR_H + 2, width, boxSizing: 'border-box',
|
|
||||||
background: 'var(--bg-input)',
|
|
||||||
border: '1px solid var(--border)',
|
|
||||||
borderRadius: 999,
|
|
||||||
overflow: 'hidden',
|
|
||||||
transition: 'border-color 0.15s, background 0.15s',
|
|
||||||
}}>
|
|
||||||
<select
|
|
||||||
value={value || ''}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={(e) => onChange(e.target.value)}
|
|
||||||
style={{
|
|
||||||
flex: 1, minWidth: 0,
|
|
||||||
background: 'transparent',
|
|
||||||
color: valueAccent ? 'var(--accent-light)' : 'var(--text-primary)',
|
|
||||||
border: 'none', outline: 'none',
|
|
||||||
padding: '0 22px 0 12px',
|
|
||||||
fontSize: 11, fontFamily: 'var(--font)',
|
|
||||||
fontWeight: valueAccent ? 600 : 500,
|
|
||||||
appearance: 'none', WebkitAppearance: 'none',
|
|
||||||
backgroundImage: 'var(--select-arrow)',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
// Caret-Position differenziert: ohne Gear normaler Abstand
|
|
||||||
// (10px vom Pill-Rand), mit Gear minimaler Abstand damit
|
|
||||||
// er an den Gear ranruckt.
|
|
||||||
backgroundPosition: onGear ? 'right 1px center' : 'right 10px center',
|
|
||||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
||||||
letterSpacing: 0,
|
|
||||||
}}
|
|
||||||
>{children}</select>
|
|
||||||
{onGear && (
|
|
||||||
<button onClick={onGear} title={gearTitle}
|
|
||||||
style={{
|
|
||||||
background: 'transparent', border: 'none',
|
|
||||||
padding: '0 8px', cursor: 'pointer',
|
|
||||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
flexShrink: 0,
|
|
||||||
}}>
|
|
||||||
<Icon name="settings" size={12}
|
|
||||||
style={{ color: 'var(--text-muted)' }} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BarButton: pill-foermiger Icon-Button im selben Stil wie BarSelect.
|
|
||||||
// joinedLeft = linke Kante flach (dockt rechts an einen BarSelect-joinedRight).
|
|
||||||
function BarButton({ icon, onClick, title, disabled, active, joinedLeft }) {
|
|
||||||
return (
|
|
||||||
<button onClick={onClick} disabled={disabled} title={title}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
if (disabled || active) return
|
|
||||||
e.currentTarget.style.borderColor = 'var(--accent)'
|
|
||||||
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
if (active) return
|
|
||||||
e.currentTarget.style.borderColor = 'var(--border)'
|
|
||||||
e.currentTarget.style.background = 'var(--bg-input)'
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
height: BAR_H, width: BAR_H,
|
|
||||||
background: active ? 'var(--accent)' : 'var(--bg-input)',
|
|
||||||
border: '1px solid var(--border)',
|
|
||||||
borderTopLeftRadius: joinedLeft ? 0 : 999,
|
|
||||||
borderBottomLeftRadius: joinedLeft ? 0 : 999,
|
|
||||||
borderTopRightRadius: 999, borderBottomRightRadius: 999,
|
|
||||||
borderLeft: joinedLeft ? 'none' : '1px solid var(--border)',
|
|
||||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
||||||
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
|
||||||
padding: 0,
|
|
||||||
transition: 'border-color 0.15s, background 0.15s',
|
|
||||||
}}>
|
|
||||||
<Icon name={icon} size={13}
|
|
||||||
style={{ color: active ? 'var(--bg-panel)' : 'var(--text-muted)' }} />
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const pillInput = {
|
const pillInput = {
|
||||||
height: PILL_H, lineHeight: PILL_H + 'px',
|
height: PILL_H, lineHeight: PILL_H + 'px',
|
||||||
padding: '0 8px', boxSizing: 'border-box',
|
padding: '0 8px', boxSizing: 'border-box',
|
||||||
@@ -796,7 +670,9 @@ export default function OberleisteApp() {
|
|||||||
const ts = sel || state.textSettings || {}
|
const ts = sel || state.textSettings || {}
|
||||||
const fonts = state.textFonts || []
|
const fonts = state.textFonts || []
|
||||||
const styles = state.textStyles || []
|
const styles = state.textStyles || []
|
||||||
const activeStyleId = state.textStyleActiveId
|
// Bei Selektion: Style-ID vom Text selber (falls per apply_style gesetzt),
|
||||||
|
// sonst auf globalen Active-Style fallen
|
||||||
|
const activeStyleId = (sel && sel.styleId) || state.textStyleActiveId
|
||||||
const updateTs = (patch) => setTextSettings({ ...ts, ...patch })
|
const updateTs = (patch) => setTextSettings({ ...ts, ...patch })
|
||||||
const STYLE_W = 110
|
const STYLE_W = 110
|
||||||
const FONT_W = 130
|
const FONT_W = 130
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import Icon from './Icon'
|
||||||
|
|
||||||
|
// Gemeinsame Toolbar-Primitiven für Panels im Oberleiste-Stil:
|
||||||
|
// Pill-Container mit konsistenter Höhe, Accent-Border bei Hover/Active.
|
||||||
|
// Quelle: ursprünglich in OberleisteApp.jsx — zur Wiederverwendung in
|
||||||
|
// weiteren Panels extrahiert.
|
||||||
|
|
||||||
|
export const BAR_H = 22
|
||||||
|
|
||||||
|
// BarCombo: dunklerer (bg-input) Pill-Container der select + optional gear
|
||||||
|
// als EINE nahtlose Box rendert. Icon roh links daneben (kein Container).
|
||||||
|
// iconClickable=true macht das Icon zum Toggle-Button.
|
||||||
|
// valueAccent=true faerbt den Select-Text accent.
|
||||||
|
export function BarCombo({
|
||||||
|
icon, iconActive, iconClickable, onIconClick, iconTitle,
|
||||||
|
value, onChange, width, title, children, disabled,
|
||||||
|
onGear, gearTitle, valueAccent,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'inline-flex', alignItems: 'center', gap: 5,
|
||||||
|
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
{icon && (iconClickable ? (
|
||||||
|
<button onClick={onIconClick} title={iconTitle}
|
||||||
|
style={{
|
||||||
|
width: 18, height: BAR_H,
|
||||||
|
background: 'transparent', border: 'none',
|
||||||
|
cursor: 'pointer', flexShrink: 0, padding: 0,
|
||||||
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
}}>
|
||||||
|
<Icon name={icon} size={13}
|
||||||
|
style={{ color: iconActive ? 'var(--accent)' : 'var(--text-muted)' }} />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span style={{
|
||||||
|
width: 18, height: BAR_H, flexShrink: 0,
|
||||||
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
}}>
|
||||||
|
<Icon name={icon} size={13} style={{ color: 'var(--text-muted)' }} />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<div title={title}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (disabled) return
|
||||||
|
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||||
|
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = 'var(--border)'
|
||||||
|
e.currentTarget.style.background = 'var(--bg-input)'
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex', alignItems: 'stretch',
|
||||||
|
height: BAR_H + 2, width, boxSizing: 'border-box',
|
||||||
|
background: 'var(--bg-input)',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
borderRadius: 999,
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: 'border-color 0.15s, background 0.15s',
|
||||||
|
}}>
|
||||||
|
<select
|
||||||
|
value={value || ''}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
style={{
|
||||||
|
flex: 1, minWidth: 0,
|
||||||
|
background: 'transparent',
|
||||||
|
color: valueAccent ? 'var(--accent-light)' : 'var(--text-primary)',
|
||||||
|
border: 'none', outline: 'none',
|
||||||
|
padding: '0 22px 0 12px',
|
||||||
|
fontSize: 11, fontFamily: 'var(--font)',
|
||||||
|
fontWeight: valueAccent ? 600 : 500,
|
||||||
|
appearance: 'none', WebkitAppearance: 'none',
|
||||||
|
backgroundImage: 'var(--select-arrow)',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: onGear ? 'right 1px center' : 'right 10px center',
|
||||||
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||||
|
letterSpacing: 0,
|
||||||
|
}}
|
||||||
|
>{children}</select>
|
||||||
|
{onGear && (
|
||||||
|
<button onClick={onGear} title={gearTitle}
|
||||||
|
style={{
|
||||||
|
background: 'transparent', border: 'none',
|
||||||
|
padding: '0 8px', cursor: 'pointer',
|
||||||
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
<Icon name="settings" size={12}
|
||||||
|
style={{ color: 'var(--text-muted)' }} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BarToggle: Pill-Button mit Label (+ optionalem Icon) und Active-State.
|
||||||
|
// Eignet sich fuer Toggles wie Welt/CPlane, Z-Selektor, Preset-Buttons.
|
||||||
|
export function BarToggle({ icon, label, active, onClick, title, disabled, minWidth }) {
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} disabled={disabled} title={title}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (disabled || active) return
|
||||||
|
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||||
|
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
if (active) return
|
||||||
|
e.currentTarget.style.borderColor = 'var(--border)'
|
||||||
|
e.currentTarget.style.background = 'var(--bg-input)'
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
height: BAR_H, minWidth: minWidth || BAR_H,
|
||||||
|
padding: label ? '0 10px' : 0,
|
||||||
|
background: active ? 'var(--accent)' : 'var(--bg-input)',
|
||||||
|
color: active ? 'var(--bg-panel)' : 'var(--text-primary)',
|
||||||
|
border: '1px solid ' + (active ? 'var(--accent)' : 'var(--border)'),
|
||||||
|
borderRadius: 999,
|
||||||
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
gap: 4, fontSize: 11,
|
||||||
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: disabled ? 0.5 : 1,
|
||||||
|
appearance: 'none', WebkitAppearance: 'none',
|
||||||
|
lineHeight: 1, boxSizing: 'border-box', flexShrink: 0,
|
||||||
|
transition: 'background 0.15s, border-color 0.15s, color 0.15s',
|
||||||
|
}}>
|
||||||
|
{icon && <Icon name={icon} size={12}
|
||||||
|
style={{ color: active ? 'var(--bg-panel)' : 'var(--text-muted)' }} />}
|
||||||
|
{label && <span>{label}</span>}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BarButton: pill-foermiger Icon-Button im selben Stil wie BarCombo.
|
||||||
|
// joinedLeft = linke Kante flach (dockt rechts an einen BarCombo).
|
||||||
|
export function BarButton({ icon, onClick, title, disabled, active, joinedLeft }) {
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} disabled={disabled} title={title}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (disabled || active) return
|
||||||
|
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||||
|
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
if (active) return
|
||||||
|
e.currentTarget.style.borderColor = 'var(--border)'
|
||||||
|
e.currentTarget.style.background = 'var(--bg-input)'
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
height: BAR_H, width: BAR_H,
|
||||||
|
background: active ? 'var(--accent)' : 'var(--bg-input)',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
borderTopLeftRadius: joinedLeft ? 0 : 999,
|
||||||
|
borderBottomLeftRadius: joinedLeft ? 0 : 999,
|
||||||
|
borderTopRightRadius: 999, borderBottomRightRadius: 999,
|
||||||
|
borderLeft: joinedLeft ? 'none' : '1px solid var(--border)',
|
||||||
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
||||||
|
padding: 0,
|
||||||
|
transition: 'border-color 0.15s, background 0.15s',
|
||||||
|
}}>
|
||||||
|
<Icon name={icon} size={13}
|
||||||
|
style={{ color: active ? 'var(--bg-panel)' : 'var(--text-muted)' }} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useState, useRef, useMemo, useEffect } from 'react'
|
|||||||
import Icon from './Icon'
|
import Icon from './Icon'
|
||||||
import ConfirmDeleteEbene from './ConfirmDeleteEbene'
|
import ConfirmDeleteEbene from './ConfirmDeleteEbene'
|
||||||
import ContextMenu from './ContextMenu'
|
import ContextMenu from './ContextMenu'
|
||||||
|
import { BarCombo, BarButton } from './BarControls'
|
||||||
import { setLayerStyle, deleteEbene, moveSelectionToEbene, openEbenenSettings } from '../lib/rhinoBridge'
|
import { setLayerStyle, deleteEbene, moveSelectionToEbene, openEbenenSettings } from '../lib/rhinoBridge'
|
||||||
|
|
||||||
const MODES = [
|
const MODES = [
|
||||||
@@ -79,11 +80,11 @@ function LwCell({ lw, onChange }) {
|
|||||||
>{lw.toFixed(2)}</span>
|
>{lw.toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
||||||
<button className="btn-step" onClick={(ev) => { ev.stopPropagation(); step(+1) }} style={{ width: 10, height: 7 }}>
|
<button className="btn-step" onClick={(ev) => { ev.stopPropagation(); step(+1) }} style={{ width: 10, height: 6 }}>
|
||||||
<Icon name="arrow_drop_up" size={10} />
|
<Icon name="arrow_drop_up" size={9} />
|
||||||
</button>
|
</button>
|
||||||
<button className="btn-step" onClick={(ev) => { ev.stopPropagation(); step(-1) }} style={{ width: 10, height: 7 }}>
|
<button className="btn-step" onClick={(ev) => { ev.stopPropagation(); step(-1) }} style={{ width: 10, height: 6 }}>
|
||||||
<Icon name="arrow_drop_down" size={10} />
|
<Icon name="arrow_drop_down" size={9} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,21 +244,21 @@ function EbeneRow({ e, depth, hasChildren, expanded, onToggleExpand, active, mod
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex', alignItems: 'center', gap: 5,
|
display: 'flex', alignItems: 'center', gap: 4,
|
||||||
padding: '3px 12px',
|
padding: '1px 12px',
|
||||||
paddingLeft: 12 + (depth || 0) * 14,
|
paddingLeft: 12 + (depth || 0) * 12,
|
||||||
margin: active ? '1px 6px' : '0',
|
margin: 0,
|
||||||
background: active ? 'var(--active-dim)'
|
background: active ? 'var(--active-dim)'
|
||||||
: (e.visible !== false) ? 'var(--bg-item)'
|
: (e.visible !== false) ? 'var(--bg-item)'
|
||||||
: 'var(--bg-panel)',
|
: 'var(--bg-panel)',
|
||||||
// Pill-Form fuer die aktive Ebene, sonst Standard-Zeile mit Bottom-Border
|
// Eckige Tabellen-Zeile mit Accent-Strip links fuer aktive Ebene
|
||||||
borderRadius: active ? 999 : 0,
|
borderRadius: 0,
|
||||||
borderLeft: active ? 'none' : '3px solid transparent',
|
borderLeft: '3px solid ' + (active ? 'var(--accent)' : 'transparent'),
|
||||||
borderBottom: active ? 'none' : '1px solid var(--border-light)',
|
borderBottom: '1px solid var(--border-light)',
|
||||||
boxShadow: active ? 'inset 0 0 0 1px var(--active-light)' : 'none',
|
|
||||||
opacity: (!active && e.visible === false && mode !== 'all') ? 0.45 : 1,
|
opacity: (!active && e.visible === false && mode !== 'all') ? 0.45 : 1,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
|
minHeight: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hasChildren ? (
|
{hasChildren ? (
|
||||||
@@ -265,24 +266,24 @@ function EbeneRow({ e, depth, hasChildren, expanded, onToggleExpand, active, mod
|
|||||||
className="btn-icon-xs"
|
className="btn-icon-xs"
|
||||||
onClick={(ev) => { ev.stopPropagation(); onToggleExpand() }}
|
onClick={(ev) => { ev.stopPropagation(); onToggleExpand() }}
|
||||||
title={expanded ? 'Einklappen' : 'Aufklappen'}
|
title={expanded ? 'Einklappen' : 'Aufklappen'}
|
||||||
style={{ width: 14, height: 14 }}
|
style={{ width: 12, height: 12 }}
|
||||||
><Icon name={expanded ? 'expand_more' : 'chevron_right'} size={12} /></button>
|
><Icon name={expanded ? 'expand_more' : 'chevron_right'} size={11} /></button>
|
||||||
) : (
|
) : (
|
||||||
<span style={{ width: 14, flexShrink: 0 }} />
|
<span style={{ width: 12, flexShrink: 0 }} />
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
|
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
|
||||||
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
|
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
|
||||||
title={eyeTitle}
|
title={eyeTitle}
|
||||||
style={{ opacity: eyeOpacity }}
|
style={{ opacity: eyeOpacity, width: 16, height: 16 }}
|
||||||
><Icon name={eyeIcon} size={14} /></button>
|
><Icon name={eyeIcon} size={12} /></button>
|
||||||
|
|
||||||
<EditableText
|
<EditableText
|
||||||
value={e.code}
|
value={e.code}
|
||||||
onCommit={onCodeChange}
|
onCommit={onCodeChange}
|
||||||
autoEditTrigger={autoEditCode}
|
autoEditTrigger={autoEditCode}
|
||||||
fontSize={9}
|
fontSize={9}
|
||||||
style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', width: 24, textAlign: 'left', flexShrink: 0 }}
|
style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', width: 22, textAlign: 'left', flexShrink: 0 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ColorPicker color={e.color} onChange={onColorChange} />
|
<ColorPicker color={e.color} onChange={onColorChange} />
|
||||||
@@ -300,6 +301,7 @@ function EbeneRow({ e, depth, hasChildren, expanded, onToggleExpand, active, mod
|
|||||||
: 'var(--text-muted)',
|
: 'var(--text-muted)',
|
||||||
display: 'inline-block', width: '100%',
|
display: 'inline-block', width: '100%',
|
||||||
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||||
|
lineHeight: 1.2,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,14 +312,15 @@ function EbeneRow({ e, depth, hasChildren, expanded, onToggleExpand, active, mod
|
|||||||
className="btn-icon-xs"
|
className="btn-icon-xs"
|
||||||
onClick={(ev) => { ev.stopPropagation(); onToggleLock() }}
|
onClick={(ev) => { ev.stopPropagation(); onToggleLock() }}
|
||||||
title={e.locked ? 'Entsperren' : 'Sperren'}
|
title={e.locked ? 'Entsperren' : 'Sperren'}
|
||||||
style={{ color: e.locked ? 'var(--warn)' : undefined }}
|
style={{ color: e.locked ? 'var(--warn)' : undefined, width: 14, height: 14 }}
|
||||||
><Icon name={e.locked ? 'lock' : 'lock_open'} size={12} /></button>
|
><Icon name={e.locked ? 'lock' : 'lock_open'} size={11} /></button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn-icon-xs"
|
className="btn-icon-xs"
|
||||||
onClick={(ev) => { ev.stopPropagation(); onDelete() }}
|
onClick={(ev) => { ev.stopPropagation(); onDelete() }}
|
||||||
title="Löschen"
|
title="Löschen"
|
||||||
><Icon name="close" size={12} /></button>
|
style={{ width: 14, height: 14 }}
|
||||||
|
><Icon name="close" size={11} /></button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -547,16 +550,17 @@ export default function EbenenManager({
|
|||||||
}}>
|
}}>
|
||||||
<span className="label-xs">Sichtbarkeit</span>
|
<span className="label-xs">Sichtbarkeit</span>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<select
|
<div style={{ flex: 1, minWidth: 0, display: 'flex' }}>
|
||||||
|
<BarCombo
|
||||||
|
icon="visibility"
|
||||||
value={mode}
|
value={mode}
|
||||||
onChange={ev => onModeChange(ev.target.value)}
|
onChange={onModeChange}
|
||||||
style={{ flex: 1, minWidth: 0 }}
|
title="Sichtbarkeits-Modus"
|
||||||
>
|
>
|
||||||
{MODES.map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
|
{MODES.map(m => <option key={m.value} value={m.value}>{m.label}</option>)}
|
||||||
</select>
|
</BarCombo>
|
||||||
<button className="btn-icon-sm" onClick={addNew} title="Ebene hinzufügen">
|
</div>
|
||||||
<Icon name="add" size={14} />
|
<BarButton icon="add" onClick={addNew} title="Ebene hinzufügen" />
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Icon from './Icon'
|
import Icon from './Icon'
|
||||||
import ContextMenu from './ContextMenu'
|
import ContextMenu from './ContextMenu'
|
||||||
|
import { BarCombo, BarButton } from './BarControls'
|
||||||
import { openGeschossSettings, openGeschossDialog } from '../lib/rhinoBridge'
|
import { openGeschossSettings, openGeschossDialog } from '../lib/rhinoBridge'
|
||||||
|
|
||||||
function GeschossBadge({ name }) {
|
function GeschossBadge({ name }) {
|
||||||
@@ -46,31 +47,32 @@ function ZeichnungsebeneRow({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex', alignItems: 'center', gap: 6,
|
display: 'flex', alignItems: 'center', gap: 4,
|
||||||
padding: '4px 12px',
|
padding: '1px 12px',
|
||||||
margin: active ? '1px 6px' : '0',
|
margin: 0,
|
||||||
background: active ? 'var(--active-dim)' : 'var(--bg-item)',
|
background: active ? 'var(--active-dim)' : 'var(--bg-item)',
|
||||||
borderRadius: active ? 999 : 0,
|
borderRadius: 0,
|
||||||
borderLeft: active ? 'none' : '3px solid transparent',
|
borderLeft: '3px solid ' + (active ? 'var(--accent)' : 'transparent'),
|
||||||
borderBottom: active ? 'none' : '1px solid var(--border-light)',
|
borderBottom: '1px solid var(--border-light)',
|
||||||
boxShadow: active ? 'inset 0 0 0 1px var(--active-light)' : 'none',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
|
minHeight: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
|
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
|
||||||
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
|
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
|
||||||
title={eyeTitle}
|
title={eyeTitle}
|
||||||
style={{ opacity: eyeOpacity }}
|
style={{ opacity: eyeOpacity, width: 16, height: 16 }}
|
||||||
><Icon name={eyeIcon} size={14} /></button>
|
><Icon name={eyeIcon} size={12} /></button>
|
||||||
|
|
||||||
<span style={{
|
<span style={{
|
||||||
fontWeight: active ? 700 : 500,
|
fontWeight: active ? 700 : 500,
|
||||||
fontSize: 12,
|
fontSize: 11,
|
||||||
color: active ? 'var(--active-light)' : 'var(--text-label)',
|
color: active ? 'var(--active-light)' : 'var(--text-label)',
|
||||||
flex: 1, minWidth: 0,
|
flex: 1, minWidth: 0,
|
||||||
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||||
|
lineHeight: 1.2,
|
||||||
}}>{z.name}</span>
|
}}>{z.name}</span>
|
||||||
|
|
||||||
{isGeschoss && (
|
{isGeschoss && (
|
||||||
@@ -88,24 +90,25 @@ function ZeichnungsebeneRow({
|
|||||||
title={z.hasClipping
|
title={z.hasClipping
|
||||||
? 'Clipping Plane ausschalten'
|
? 'Clipping Plane ausschalten'
|
||||||
: 'Clipping Plane einschalten (Schnitt auf Schnitthöhe)'}
|
: 'Clipping Plane einschalten (Schnitt auf Schnitthöhe)'}
|
||||||
style={{ color: z.hasClipping ? 'var(--accent)' : undefined }}
|
style={{ color: z.hasClipping ? 'var(--accent)' : undefined, width: 14, height: 14 }}
|
||||||
><Icon name="content_cut" size={12} /></button>
|
><Icon name="content_cut" size={11} /></button>
|
||||||
) : (
|
) : (
|
||||||
<span style={{ width: 18, flexShrink: 0 }} />
|
<span style={{ width: 14, flexShrink: 0 }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn-icon-xs"
|
className="btn-icon-xs"
|
||||||
onClick={(ev) => { ev.stopPropagation(); onToggleLock() }}
|
onClick={(ev) => { ev.stopPropagation(); onToggleLock() }}
|
||||||
title={z.locked ? 'Entsperren' : 'Sperren'}
|
title={z.locked ? 'Entsperren' : 'Sperren'}
|
||||||
style={{ color: z.locked ? 'var(--warn)' : undefined }}
|
style={{ color: z.locked ? 'var(--warn)' : undefined, width: 14, height: 14 }}
|
||||||
><Icon name={z.locked ? 'lock' : 'lock_open'} size={12} /></button>
|
><Icon name={z.locked ? 'lock' : 'lock_open'} size={11} /></button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn-icon-xs"
|
className="btn-icon-xs"
|
||||||
onClick={(ev) => { ev.stopPropagation(); onDelete() }}
|
onClick={(ev) => { ev.stopPropagation(); onDelete() }}
|
||||||
title="Löschen"
|
title="Löschen"
|
||||||
><Icon name="close" size={12} /></button>
|
style={{ width: 14, height: 14 }}
|
||||||
|
><Icon name="close" size={11} /></button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -215,21 +218,20 @@ export default function GeschossManager({
|
|||||||
}}>
|
}}>
|
||||||
<span className="label-xs">Sichtbarkeit</span>
|
<span className="label-xs">Sichtbarkeit</span>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<select
|
<div style={{ flex: 1, minWidth: 0, display: 'flex' }}>
|
||||||
|
<BarCombo
|
||||||
|
icon="visibility"
|
||||||
value={mode}
|
value={mode}
|
||||||
onChange={ev => onModeChange(ev.target.value)}
|
onChange={onModeChange}
|
||||||
style={{ flex: 1, minWidth: 0 }}
|
title="Sichtbarkeits-Modus"
|
||||||
>
|
>
|
||||||
{MODES.map(m => (
|
{MODES.map(m => (
|
||||||
<option key={m.value} value={m.value}>{m.label}</option>
|
<option key={m.value} value={m.value}>{m.label}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</BarCombo>
|
||||||
<button className="btn-icon-sm" onClick={addQuick} title="Zeichnungsebene hinzufügen">
|
</div>
|
||||||
<Icon name="add" size={14} />
|
<BarButton icon="add" onClick={addQuick} title="Zeichnungsebene hinzufügen" />
|
||||||
</button>
|
<BarButton icon="settings" onClick={() => openGeschossDialog(zeichnungsebenen)} title="Einstellungen" />
|
||||||
<button className="btn-icon-sm" onClick={() => openGeschossDialog(zeichnungsebenen)} title="Bearbeiten">
|
|
||||||
<Icon name="edit" size={13} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user