Oberleiste: Icon roh + Pill-Dropdowns, About-Modal, Overrides/Kombi migriert
User-Feedback: Icon-Box war zu plakativ, lieber roh daneben + Pill-Form. Versionsnummer raus, Logo-Klick → About-Fenster. BarSelect umgebaut: - Icon roh links (vorher: separates Icon-Kompartiment mit Border) - Select-Container pill-foermig (border-radius 999, vorher 4) - joinedRight wird zu flachen rechten Pill-Kanten (statt rechteck) BarButton: pill-foermig + joinedLeft mit flacher linker Kante. View-Toggle: Pill-Container statt Rechteck. Massstab Live-Zoom-Chip: Pill-Form. Custom-Scale-Input: Pill-Form. Logo: - Versionsnummer-Span entfernt - ganzes Logo wird zu Klick-Button → AboutModal-State - AboutModal: zentrierter Backdrop-Dialog mit Launcher/Plugin-Versionen, Autor (Karim Gabriele Varano), Website (gabrielevarano.ch), Lizenz (Proprietaer) Overrides + Kombi migriert (war noch im alten Stack-Layout): - Overrides: BarButton (Toggle) + BarSelect (Preset) joinedRight + BarButton (Settings) joinedLeft, palette-Icon fuer Preset-Picker - Kombi: BarSelect (layers-Icon) mit allen Aktionen im Dropdown (Speichern, Loeschen, Bearbeiten) + BarButton (edit) joinedLeft - Stack-Layout (label-Spalte + 2 Reihen) ist weg, jetzt inline Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+152
-100
@@ -83,51 +83,53 @@ const pillSelect = {
|
||||
fontSize: 10,
|
||||
}
|
||||
|
||||
// BarSelect: Icon-Kompartiment links | Native-Select Mitte | Caret rechts
|
||||
// BarSelect: Icon roh links (kein Container) + pill-shaped Native-Select.
|
||||
// joinedRight: rechte Pill-Kante flach, fuer Verkettung mit BarButton.
|
||||
function BarSelect({ icon, value, onChange, title, disabled, width, children, joinedRight }) {
|
||||
return (
|
||||
<div title={title} style={{
|
||||
display: 'inline-flex', alignItems: 'center',
|
||||
display: 'inline-flex', alignItems: 'center', gap: 5,
|
||||
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
||||
}}>
|
||||
{icon && (
|
||||
<Icon name={icon} size={13}
|
||||
style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
|
||||
)}
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
height: BAR_H, width,
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border-light)',
|
||||
borderRadius: joinedRight ? '4px 0 0 4px' : 4,
|
||||
borderTopLeftRadius: 999, borderBottomLeftRadius: 999,
|
||||
borderTopRightRadius: joinedRight ? 0 : 999,
|
||||
borderBottomRightRadius: joinedRight ? 0 : 999,
|
||||
borderRight: joinedRight ? 'none' : '1px solid var(--border-light)',
|
||||
overflow: 'hidden', position: 'relative',
|
||||
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
||||
overflow: 'hidden', flexShrink: 0,
|
||||
}}>
|
||||
<div style={{
|
||||
width: 26, height: '100%',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
background: 'var(--bg-item)',
|
||||
borderRight: '1px solid var(--border-light)',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Icon name={icon} size={13} style={{ color: 'var(--text-muted)' }} />
|
||||
</div>
|
||||
<select
|
||||
value={value || ''}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
style={{
|
||||
flex: 1, height: '100%', minWidth: 0,
|
||||
width: '100%', height: '100%', minWidth: 0,
|
||||
background: 'transparent', border: 'none', outline: 'none',
|
||||
padding: '0 18px 0 8px',
|
||||
padding: '0 22px 0 12px',
|
||||
fontSize: 11, color: 'var(--text-primary)',
|
||||
appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
>{children}</select>
|
||||
<Icon name="arrow_drop_down" size={14}
|
||||
style={{ position: 'absolute', right: 4, top: '50%',
|
||||
style={{ position: 'absolute', right: 6, top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: 'var(--text-muted)', pointerEvents: 'none' }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// BarButton: quadratischer Icon-Button, gleiche Hoehe wie BarSelect.
|
||||
// joinedLeft: wenn rechts von einem BarSelect sitzt (kein doppelter Border).
|
||||
// BarButton: pill-foermiger Icon-Button. joinedLeft = linke Kante flach,
|
||||
// so dass er nahtlos an einen BarSelect-joinedRight andockt.
|
||||
function BarButton({ icon, onClick, title, disabled, joinedLeft, active }) {
|
||||
return (
|
||||
<button onClick={onClick} disabled={disabled} title={title}
|
||||
@@ -135,7 +137,9 @@ function BarButton({ icon, onClick, title, disabled, joinedLeft, active }) {
|
||||
height: BAR_H, width: BAR_H,
|
||||
background: active ? 'var(--accent)' : 'var(--bg-input)',
|
||||
border: '1px solid var(--border-light)',
|
||||
borderRadius: joinedLeft ? '0 4px 4px 0' : 4,
|
||||
borderTopLeftRadius: joinedLeft ? 0 : 999,
|
||||
borderBottomLeftRadius: joinedLeft ? 0 : 999,
|
||||
borderTopRightRadius: 999, borderBottomRightRadius: 999,
|
||||
borderLeft: joinedLeft ? 'none' : '1px solid var(--border-light)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
@@ -204,6 +208,7 @@ export default function OberleisteApp() {
|
||||
const [draft, setDraft] = useState('')
|
||||
const [customMode, setCustomMode] = useState(false) // Dropdown -> Custom-Input switch
|
||||
const customInputRef = useRef(null)
|
||||
const [aboutOpen, setAboutOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
onMessage('STATE', (s) => {
|
||||
@@ -291,13 +296,16 @@ export default function OberleisteApp() {
|
||||
overflowX: 'auto', overflowY: 'hidden',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{/* Logo: DOSSIER. (Petrol-Punkt) + Launcher-Version */}
|
||||
<div
|
||||
{/* Logo: DOSSIER. (Petrol-Punkt) — Klick = About-Fenster */}
|
||||
<button
|
||||
onClick={() => setAboutOpen(true)}
|
||||
title="Über Dossier"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'baseline', gap: 8,
|
||||
flexShrink: 0, userSelect: 'none',
|
||||
background: 'transparent', border: 'none', padding: 0,
|
||||
cursor: 'pointer', color: 'inherit',
|
||||
}}
|
||||
title={`Dossier ${__LAUNCHER_VERSION__} (Plugin ${__APP_VERSION__}) — Teil von OpenStudio`}
|
||||
>
|
||||
<span style={{
|
||||
fontFamily: "Krungthep, 'Archivo Black', sans-serif",
|
||||
@@ -308,16 +316,7 @@ export default function OberleisteApp() {
|
||||
}}>
|
||||
DOSSIER<span style={{ color: 'var(--accent)' }}>.</span>
|
||||
</span>
|
||||
<span style={{
|
||||
fontFamily: 'DM Mono, monospace',
|
||||
fontSize: 9,
|
||||
letterSpacing: '0.14em',
|
||||
color: 'var(--text-muted)',
|
||||
textTransform: 'uppercase',
|
||||
}}>
|
||||
v{__LAUNCHER_VERSION__}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openDossierSettings()}
|
||||
title="Dossier-Einstellungen"
|
||||
@@ -333,9 +332,9 @@ export default function OberleisteApp() {
|
||||
</button>
|
||||
<div style={sep} />
|
||||
{/* ====== VIEW (Top/Front/Right/Iso/Persp + Kamera) ======
|
||||
Kein Group-Label — die Buttons selber kommunizieren ihren Zweck. */}
|
||||
Pill-foermige Toggle-Gruppe, kein Group-Label. */}
|
||||
<div style={{ display: 'inline-flex', gap: 0,
|
||||
border: '1px solid var(--border-light)', borderRadius: 4,
|
||||
border: '1px solid var(--border-light)', borderRadius: 999,
|
||||
overflow: 'hidden', flexShrink: 0 }}>
|
||||
{VIEWS.map((v, idx) => (
|
||||
<button
|
||||
@@ -343,7 +342,7 @@ export default function OberleisteApp() {
|
||||
onClick={() => setView(v.value)}
|
||||
title={`Ansicht ${v.label}`}
|
||||
style={{
|
||||
height: BAR_H, padding: '0 8px',
|
||||
height: BAR_H, padding: '0 10px',
|
||||
background: matchView(v.value) ? 'var(--accent)' : 'var(--bg-input)',
|
||||
color: matchView(v.value) ? 'var(--bg-panel)' : 'var(--text-primary)',
|
||||
border: 'none',
|
||||
@@ -399,14 +398,14 @@ export default function OberleisteApp() {
|
||||
<div style={sep} />
|
||||
|
||||
{/* ====== MASSSTAB ====== */}
|
||||
{/* Live-Zoom Chip mit Mass-Icon */}
|
||||
{/* Live-Zoom Chip — pill, accent wenn Massstab anwendbar */}
|
||||
<div style={{
|
||||
display: 'inline-flex', alignItems: 'center',
|
||||
height: BAR_H, padding: '0 10px',
|
||||
height: BAR_H, padding: '0 12px',
|
||||
background: isPerspective ? 'var(--bg-input)' : 'var(--accent)',
|
||||
color: isPerspective ? 'var(--text-muted)' : 'var(--bg-panel)',
|
||||
border: '1px solid var(--border-light)',
|
||||
borderRadius: 4,
|
||||
borderRadius: 999,
|
||||
fontFamily: 'DM Mono, monospace', fontSize: 11, fontWeight: 600,
|
||||
minWidth: 64, justifyContent: 'center', flexShrink: 0,
|
||||
}} title="Live-Zoom">
|
||||
@@ -428,8 +427,8 @@ export default function OberleisteApp() {
|
||||
height: BAR_H, width: 100,
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border-light)',
|
||||
borderRadius: 4,
|
||||
padding: '0 8px', fontSize: 11,
|
||||
borderRadius: 999,
|
||||
padding: '0 12px', fontSize: 11,
|
||||
fontFamily: 'DM Mono, monospace',
|
||||
color: 'var(--text-primary)', outline: 'none',
|
||||
}}
|
||||
@@ -478,39 +477,27 @@ export default function OberleisteApp() {
|
||||
{/* ====== STACK: Overrides + Kombi uebereinander ======
|
||||
Beide Zeilen haben identisches Spalten-Layout (Label-Spalte fix,
|
||||
Dropdown gleich breit), damit Dropdowns vertikal aligned sind. */}
|
||||
{(() => {
|
||||
const STACK_LABEL_W = 60 // gleich breit fuer beide Zeilen
|
||||
const STACK_DROPDOWN_W = 150
|
||||
const stackLabel = { ...groupLabel, width: STACK_LABEL_W,
|
||||
padding: 0, textAlign: 'left' }
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 4,
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{/* Overrides */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={stackLabel}>Overrides</span>
|
||||
<ToolButton
|
||||
onClick={() => toggleOverrides(!state.overridesEnabled)}
|
||||
active={state.overridesEnabled}
|
||||
{/* ====== OVERRIDES ====== */}
|
||||
<BarButton
|
||||
icon="auto_fix_high"
|
||||
label={state.overridesEnabled ? 'AN' : 'AUS'}
|
||||
active={state.overridesEnabled}
|
||||
onClick={() => toggleOverrides(!state.overridesEnabled)}
|
||||
title={state.overridesEnabled
|
||||
? `Grafische Overrides aktiv — klick zum Ausschalten`
|
||||
: `Grafische Overrides ausgeschaltet`}
|
||||
? 'Grafische Overrides aktiv — klick zum Ausschalten'
|
||||
: 'Grafische Overrides ausgeschaltet'}
|
||||
/>
|
||||
<select
|
||||
<BarSelect
|
||||
icon="palette"
|
||||
value={state.overridesActivePreset || '__none__'}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
onChange={(v) => {
|
||||
if (v === '__configure__') { openOverridesPanel(); return }
|
||||
setOverridesPreset(v === '__none__' ? null : v)
|
||||
}}
|
||||
style={{ ...pillSelect, width: STACK_DROPDOWN_W }}
|
||||
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 => (
|
||||
@@ -518,18 +505,19 @@ export default function OberleisteApp() {
|
||||
))}
|
||||
<option disabled>──────────</option>
|
||||
<option value="__configure__">Konfigurieren…</option>
|
||||
</select>
|
||||
<ToolButton
|
||||
onClick={openOverridesPanel}
|
||||
icon="settings"
|
||||
title="Overrides-Regel-Editor öffnen"
|
||||
/>
|
||||
</div>
|
||||
{/* Kombi */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={stackLabel}>Kombi</span>
|
||||
<ToolButton
|
||||
onClick={() => {
|
||||
</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()
|
||||
@@ -537,15 +525,8 @@ export default function OberleisteApp() {
|
||||
if ((state.layerCombinations || []).includes(name) &&
|
||||
!window.confirm(`"${name}" überschreiben?`)) return
|
||||
saveLayerCombination(name)
|
||||
}}
|
||||
icon="add"
|
||||
title="Aktuelle Sichtbarkeit als neue Kombination speichern"
|
||||
/>
|
||||
<select
|
||||
value={state.layerCombinationActive || '__none__'}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
if (v === '__configure__') { openLayerCombinationsDialog(); return }
|
||||
return
|
||||
}
|
||||
if (v === '__delete__') {
|
||||
if (state.layerCombinationActive &&
|
||||
window.confirm(`Kombination "${state.layerCombinationActive}" löschen?`))
|
||||
@@ -554,37 +535,108 @@ export default function OberleisteApp() {
|
||||
}
|
||||
pickLayerCombination(v === '__none__' ? null : v)
|
||||
}}
|
||||
style={{ ...pillSelect, width: STACK_DROPDOWN_W }}
|
||||
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 disabled>──────────</option>
|
||||
<option value="__delete__">🗑 Aktuelle löschen</option>
|
||||
</>
|
||||
)}
|
||||
<option disabled>──────────</option>
|
||||
<option value="__configure__">Bearbeiten…</option>
|
||||
</select>
|
||||
<ToolButton
|
||||
onClick={openLayerCombinationsDialog}
|
||||
icon="edit"
|
||||
title="Ebenenkombinationen bearbeiten"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</BarSelect>
|
||||
<BarButton icon="edit" onClick={openLayerCombinationsDialog}
|
||||
title="Ebenenkombinationen bearbeiten" joinedLeft />
|
||||
|
||||
{/* Spacer am rechten Rand */}
|
||||
<div style={{ flex: 1 }} />
|
||||
</div>
|
||||
{aboutOpen && (
|
||||
<AboutModal onClose={() => setAboutOpen(false)} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function AboutModal({ onClose }) {
|
||||
return (
|
||||
<div
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: 'fixed', inset: 0, zIndex: 1000,
|
||||
background: 'rgba(0, 0, 0, 0.6)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
minWidth: 360, maxWidth: 420,
|
||||
background: 'var(--bg-panel)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 8,
|
||||
padding: '24px 28px',
|
||||
boxShadow: '0 12px 48px rgba(0, 0, 0, 0.5)',
|
||||
color: 'var(--text-primary)',
|
||||
fontFamily: 'var(--font)',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8,
|
||||
marginBottom: 4 }}>
|
||||
<span style={{
|
||||
fontFamily: "Krungthep, 'Archivo Black', sans-serif",
|
||||
fontSize: 28, letterSpacing: '-0.02em', lineHeight: 1,
|
||||
}}>
|
||||
DOSSIER<span style={{ color: 'var(--accent)' }}>.</span>
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-muted)',
|
||||
letterSpacing: '0.06em', textTransform: 'uppercase',
|
||||
marginBottom: 18 }}>
|
||||
Teil von OpenStudio
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr',
|
||||
gap: '6px 14px', fontSize: 11, marginBottom: 18 }}>
|
||||
<span style={{ color: 'var(--text-muted)' }}>Launcher</span>
|
||||
<span style={{ fontFamily: 'DM Mono, monospace' }}>v{__LAUNCHER_VERSION__}</span>
|
||||
<span style={{ color: 'var(--text-muted)' }}>Plugin</span>
|
||||
<span style={{ fontFamily: 'DM Mono, monospace' }}>v{__APP_VERSION__}</span>
|
||||
<span style={{ color: 'var(--text-muted)' }}>Autor</span>
|
||||
<span>Karim Gabriele Varano</span>
|
||||
<span style={{ color: 'var(--text-muted)' }}>Website</span>
|
||||
<a href="https://gabrielevarano.ch" target="_blank" rel="noreferrer"
|
||||
style={{ color: 'var(--accent)', textDecoration: 'none' }}>
|
||||
gabrielevarano.ch
|
||||
</a>
|
||||
<span style={{ color: 'var(--text-muted)' }}>Lizenz</span>
|
||||
<span>Proprietär — © 2026 Karim Gabriele Varano</span>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 10, color: 'var(--text-muted)',
|
||||
lineHeight: 1.5, marginBottom: 18,
|
||||
paddingTop: 12,
|
||||
borderTop: '1px solid var(--border-light)' }}>
|
||||
Rhino 8 Plugin für architektonische Workflows — Wände, Decken,
|
||||
Öffnungen, Räume, SIA 416, Plan-Layouts. Schwester-App: Rapport.
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="btn-contained"
|
||||
style={{ padding: '6px 18px', fontSize: 11 }}
|
||||
>Schliessen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user