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.
|
// 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>
|
||||||
|
|||||||
Reference in New Issue
Block a user