Oberleiste 2-Reihen-Block: Display|Kombi oben, Overrides|Masse unten

User-Wunsch: Overrides unter Wireframe, Kombi neben Wireframe, gleiche
Pill-Breiten + gleiche X-Achse, Boxen zusammenhaengend (kein Split
zwischen Select und Gear), dunkler wie Elemente.

Neue Komponente BarCombo:
- Icon roh links (18px fixe Breite → X-Alignment zwischen Reihen)
- iconClickable=true macht Icon zum Toggle-Button (fuer Overrides AN/AUS)
- Combined pill: ein gemeinsamer Container (bg-input — dunkler statt
  bg-item) mit select + optional gear in einem nahtlosen Rahmen
- Gear sitzt im selben Pill, kein border-left, transparent bg
- Caret-Position verschoben (right 30px) wenn gear vorhanden — Caret
  bleibt innerhalb des sichtbaren Select-Bereichs

Layout:
- CSS Grid 2x2 mit fester Pill-Breite (PRESET_W = 150)
- Reihe 1: Display | Kombi
- Reihe 2: Overrides | Masse
- Gleicher Spalten-Track in beiden Reihen → identische X-Positionen

Entfernt: BarSelect/BarButton im Display/Masse/Overrides/Kombi-Pfad,
alte Sektionen am Ende der Toolbar. BarButton bleibt fuer Camera + Zoom-
Buttons + Print/Edit. View-Toggle bleibt segmented-pill am Anfang.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 22:54:43 +02:00
parent 700cc11956
commit 1d1cd10a0b
+172 -98
View File
@@ -125,6 +125,83 @@ function BarSelect({ icon, value, onChange, title, disabled, width, children, jo
) )
} }
// 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 (Overrides etc.).
function BarCombo({
icon, iconActive, iconClickable, onIconClick, iconTitle,
value, onChange, width, title, children, disabled,
onGear, gearTitle,
}) {
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 */}
{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} style={{
display: 'inline-flex', alignItems: 'stretch',
height: BAR_H, width,
background: 'var(--bg-input)',
border: '1px solid var(--border)',
borderRadius: 999,
overflow: 'hidden',
}}>
<select
value={value || ''}
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
style={{
flex: 1, minWidth: 0,
background: 'transparent', color: 'var(--text-primary)',
border: 'none', outline: 'none',
padding: '0 22px 0 12px',
fontSize: 11, fontFamily: 'var(--font)',
appearance: 'none', WebkitAppearance: 'none',
backgroundImage: 'var(--select-arrow)',
backgroundRepeat: 'no-repeat',
backgroundPosition: onGear ? 'right 30px center' : 'right 9px 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. // BarButton: pill-foermiger Icon-Button im selben Stil wie BarSelect.
// joinedLeft = linke Kante flach (dockt rechts an einen BarSelect-joinedRight). // joinedLeft = linke Kante flach (dockt rechts an einen BarSelect-joinedRight).
function BarButton({ icon, onClick, title, disabled, active, joinedLeft }) { function BarButton({ icon, onClick, title, disabled, active, joinedLeft }) {
@@ -132,7 +209,7 @@ function BarButton({ icon, onClick, title, disabled, active, joinedLeft }) {
<button onClick={onClick} disabled={disabled} title={title} <button onClick={onClick} disabled={disabled} title={title}
style={{ style={{
height: BAR_H, width: BAR_H, height: BAR_H, width: BAR_H,
background: active ? 'var(--accent)' : 'var(--bg-item)', background: active ? 'var(--accent)' : 'var(--bg-input)',
border: '1px solid var(--border)', border: '1px solid var(--border)',
borderTopLeftRadius: joinedLeft ? 0 : 999, borderTopLeftRadius: joinedLeft ? 0 : 999,
borderBottomLeftRadius: joinedLeft ? 0 : 999, borderBottomLeftRadius: joinedLeft ? 0 : 999,
@@ -365,38 +442,118 @@ export default function OberleisteApp() {
<div style={sep} /> <div style={sep} />
{/* ====== DISPLAY-MODE ====== */} {/* ====== 2-Reihen Preset-Block ======
<BarSelect Oben: Display | Kombi
Unten: Overrides | Masse
Gleiche Pill-Breiten, identische X-Positionen (Grid-Layout). */}
{(() => {
const PRESET_W = 150
return (
<div style={{
display: 'grid', gridTemplateColumns: 'auto auto',
gap: '4px 6px', flexShrink: 0,
}}>
{/* Reihe 1, Spalte 1: Display */}
<BarCombo
icon="lightbulb" icon="lightbulb"
value={state.displayMode || ''} value={state.displayMode || ''}
onChange={(v) => setDisplayMode(v)} onChange={(v) => setDisplayMode(v)}
title="Display-Mode (Wireframe / Shaded / Rendered / etc.)" title="Display-Mode (Wireframe / Shaded / Rendered / etc.)"
width={160} width={PRESET_W}
> >
{!state.displayMode && <option value=""></option>} {!state.displayMode && <option value=""></option>}
{(state.displayModes || []).map(dm => ( {(state.displayModes || []).map(dm => (
<option key={dm.id} value={dm.name}>{dm.name}</option> <option key={dm.id} value={dm.name}>{dm.name}</option>
))} ))}
</BarSelect> </BarCombo>
{/* Reihe 1, Spalte 2: Ebenenkombination */}
<div style={sep} /> <BarCombo
icon="layers"
{/* ====== MASSE (Preset-Picker + Settings) ====== */} value={state.layerCombinationActive || '__none__'}
<BarSelect onChange={(v) => {
if (v === '__configure__') { openLayerCombinationsDialog(); return }
if (v === '__save__') {
const suggested = state.layerCombinationActive
|| `Kombi ${(state.layerCombinations || []).length + 1}`
const name = (window.prompt('Name für Ebenenkombination:', suggested) || '').trim()
if (!name) return
if ((state.layerCombinations || []).includes(name) &&
!window.confirm(`"${name}" überschreiben?`)) return
saveLayerCombination(name)
return
}
if (v === '__delete__') {
if (state.layerCombinationActive &&
window.confirm(`Kombination "${state.layerCombinationActive}" löschen?`))
deleteLayerCombination(state.layerCombinationActive)
return
}
pickLayerCombination(v === '__none__' ? null : v)
}}
title={state.layerCombinationActive
? `Aktive Kombi: ${state.layerCombinationActive}`
: 'Keine Kombination — manuelle Sichtbarkeit'}
width={PRESET_W}
onGear={openLayerCombinationsDialog}
gearTitle="Ebenenkombinationen bearbeiten"
>
<option value="__none__"> Eigene </option>
{(state.layerCombinations || []).map(name => (
<option key={name} value={name}>{name}</option>
))}
<option disabled></option>
<option value="__save__">+ Aktuelle speichern</option>
{state.layerCombinationActive && (
<option value="__delete__">🗑 Aktuelle löschen</option>
)}
<option value="__configure__">Bearbeiten</option>
</BarCombo>
{/* Reihe 2, Spalte 1: Overrides (Toggle als Icon links) */}
<BarCombo
icon="auto_fix_high"
iconClickable
iconActive={state.overridesEnabled}
onIconClick={() => toggleOverrides(!state.overridesEnabled)}
iconTitle={state.overridesEnabled
? 'Grafische Overrides aktiv — klick zum Ausschalten'
: 'Grafische Overrides ausgeschaltet'}
value={state.overridesActivePreset || '__none__'}
onChange={(v) => {
if (v === '__configure__') { openOverridesPanel(); return }
setOverridesPreset(v === '__none__' ? null : v)
}}
title={state.overridesActivePreset
? `Aktives Preset: ${state.overridesActivePreset} (${state.overridesCount} Regeln)`
: `Kein Preset aktiv (${state.overridesCount} Regeln, frei editiert)`}
width={PRESET_W}
onGear={openOverridesPanel}
gearTitle="Overrides-Regel-Editor öffnen"
>
<option value="__none__">{state.overridesCount > 0 ? `— (${state.overridesCount} Regeln)` : '—'}</option>
{(state.overridesPresets || []).map(name => (
<option key={name} value={name}>{name}</option>
))}
<option disabled></option>
<option value="__configure__">Konfigurieren</option>
</BarCombo>
{/* Reihe 2, Spalte 2: Masse */}
<BarCombo
icon="straighten" icon="straighten"
value={state.masseActiveId || ''} value={state.masseActiveId || ''}
onChange={(v) => setMasseActive(v)} onChange={(v) => setMasseActive(v)}
title="Aktives Mass — Raum-Rundung + Mass-Linien-Format" title="Aktives Mass — Raum-Rundung + Mass-Linien-Format"
width={140} width={PRESET_W}
joinedRight onGear={openMasseSettings}
gearTitle="Masse bearbeiten / neues anlegen"
> >
{(state.massePresets || []).length === 0 && <option value=""></option>} {(state.massePresets || []).length === 0 && <option value=""></option>}
{(state.massePresets || []).map(p => ( {(state.massePresets || []).map(p => (
<option key={p.id} value={p.id}>{p.name}</option> <option key={p.id} value={p.id}>{p.name}</option>
))} ))}
</BarSelect> </BarCombo>
<BarButton icon="settings" onClick={() => openMasseSettings()} </div>
title="Masse bearbeiten / neues anlegen" joinedLeft /> )
})()}
<div style={sep} /> <div style={sep} />
@@ -473,92 +630,9 @@ export default function OberleisteApp() {
: 'Strichstärken anzeigen (Print-View)'} : 'Strichstärken anzeigen (Print-View)'}
/> />
<div style={sep} />
{/* Snap-Toggles (Ortho/Grid/OSnap) sind in Rhinos eigener Footer-Bar {/* Snap-Toggles (Ortho/Grid/OSnap) sind in Rhinos eigener Footer-Bar
schon vorhanden — hier rausgenommen um Doppelung zu vermeiden. */} schon vorhanden — hier rausgenommen um Doppelung zu vermeiden. */}
{/* ====== STACK: Overrides + Kombi uebereinander ======
Beide Zeilen haben identisches Spalten-Layout (Label-Spalte fix,
Dropdown gleich breit), damit Dropdowns vertikal aligned sind. */}
{/* ====== OVERRIDES ====== */}
<BarButton
icon="auto_fix_high"
active={state.overridesEnabled}
onClick={() => toggleOverrides(!state.overridesEnabled)}
title={state.overridesEnabled
? 'Grafische Overrides aktiv — klick zum Ausschalten'
: 'Grafische Overrides ausgeschaltet'}
/>
<BarSelect
icon="palette"
value={state.overridesActivePreset || '__none__'}
onChange={(v) => {
if (v === '__configure__') { openOverridesPanel(); return }
setOverridesPreset(v === '__none__' ? null : v)
}}
title={state.overridesActivePreset
? `Aktives Preset: ${state.overridesActivePreset} (${state.overridesCount} Regeln)`
: `Kein Preset aktiv (${state.overridesCount} Regeln, frei editiert)`}
width={140}
joinedRight
>
<option value="__none__">{state.overridesCount > 0 ? `— (${state.overridesCount} Regeln)` : '—'}</option>
{(state.overridesPresets || []).map(name => (
<option key={name} value={name}>{name}</option>
))}
<option disabled></option>
<option value="__configure__">Konfigurieren</option>
</BarSelect>
<BarButton icon="settings" onClick={openOverridesPanel}
title="Overrides-Regel-Editor öffnen" joinedLeft />
<div style={sep} />
{/* ====== EBENEN-KOMBINATIONEN ====== */}
<BarSelect
icon="layers"
value={state.layerCombinationActive || '__none__'}
onChange={(v) => {
if (v === '__configure__') { openLayerCombinationsDialog(); return }
if (v === '__save__') {
const suggested = state.layerCombinationActive
|| `Kombi ${(state.layerCombinations || []).length + 1}`
const name = (window.prompt('Name für Ebenenkombination:', suggested) || '').trim()
if (!name) return
if ((state.layerCombinations || []).includes(name) &&
!window.confirm(`"${name}" überschreiben?`)) return
saveLayerCombination(name)
return
}
if (v === '__delete__') {
if (state.layerCombinationActive &&
window.confirm(`Kombination "${state.layerCombinationActive}" löschen?`))
deleteLayerCombination(state.layerCombinationActive)
return
}
pickLayerCombination(v === '__none__' ? null : v)
}}
title={state.layerCombinationActive
? `Aktive Kombi: ${state.layerCombinationActive}`
: 'Keine Kombination — manuelle Sichtbarkeit'}
width={150}
joinedRight
>
<option value="__none__"> Eigene </option>
{(state.layerCombinations || []).map(name => (
<option key={name} value={name}>{name}</option>
))}
<option disabled></option>
<option value="__save__">+ Aktuelle speichern</option>
{state.layerCombinationActive && (
<option value="__delete__">🗑 Aktuelle löschen</option>
)}
<option value="__configure__">Bearbeiten</option>
</BarSelect>
<BarButton icon="edit" onClick={openLayerCombinationsDialog}
title="Ebenenkombinationen bearbeiten" joinedLeft />
{/* Spacer am rechten Rand */} {/* Spacer am rechten Rand */}
<div style={{ flex: 1 }} /> <div style={{ flex: 1 }} />
</div> </div>