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:
+172
-98
@@ -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.
|
||||
// joinedLeft = linke Kante flach (dockt rechts an einen BarSelect-joinedRight).
|
||||
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}
|
||||
style={{
|
||||
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)',
|
||||
borderTopLeftRadius: joinedLeft ? 0 : 999,
|
||||
borderBottomLeftRadius: joinedLeft ? 0 : 999,
|
||||
@@ -365,38 +442,118 @@ export default function OberleisteApp() {
|
||||
|
||||
<div style={sep} />
|
||||
|
||||
{/* ====== DISPLAY-MODE ====== */}
|
||||
<BarSelect
|
||||
{/* ====== 2-Reihen Preset-Block ======
|
||||
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"
|
||||
value={state.displayMode || ''}
|
||||
onChange={(v) => setDisplayMode(v)}
|
||||
title="Display-Mode (Wireframe / Shaded / Rendered / etc.)"
|
||||
width={160}
|
||||
width={PRESET_W}
|
||||
>
|
||||
{!state.displayMode && <option value="">—</option>}
|
||||
{(state.displayModes || []).map(dm => (
|
||||
<option key={dm.id} value={dm.name}>{dm.name}</option>
|
||||
))}
|
||||
</BarSelect>
|
||||
|
||||
<div style={sep} />
|
||||
|
||||
{/* ====== MASSE (Preset-Picker + Settings) ====== */}
|
||||
<BarSelect
|
||||
</BarCombo>
|
||||
{/* Reihe 1, Spalte 2: Ebenenkombination */}
|
||||
<BarCombo
|
||||
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={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"
|
||||
value={state.masseActiveId || ''}
|
||||
onChange={(v) => setMasseActive(v)}
|
||||
title="Aktives Mass — Raum-Rundung + Mass-Linien-Format"
|
||||
width={140}
|
||||
joinedRight
|
||||
width={PRESET_W}
|
||||
onGear={openMasseSettings}
|
||||
gearTitle="Masse bearbeiten / neues anlegen"
|
||||
>
|
||||
{(state.massePresets || []).length === 0 && <option value="">—</option>}
|
||||
{(state.massePresets || []).map(p => (
|
||||
<option key={p.id} value={p.id}>{p.name}</option>
|
||||
))}
|
||||
</BarSelect>
|
||||
<BarButton icon="settings" onClick={() => openMasseSettings()}
|
||||
title="Masse bearbeiten / neues anlegen" joinedLeft />
|
||||
</BarCombo>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
|
||||
<div style={sep} />
|
||||
|
||||
@@ -473,92 +630,9 @@ export default function OberleisteApp() {
|
||||
: 'Strichstärken anzeigen (Print-View)'}
|
||||
/>
|
||||
|
||||
<div style={sep} />
|
||||
|
||||
{/* Snap-Toggles (Ortho/Grid/OSnap) sind in Rhinos eigener Footer-Bar
|
||||
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 */}
|
||||
<div style={{ flex: 1 }} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user