Massstab 2x2-Grid: Live | Dropdown / Ratio | Buttons
User-Korrektur: Layout sollte 2x2 sein: Reihe 1: [Aktueller Massstab] [Dropdown gesetzter Massstab] Reihe 2: [Zoom-Verhaeltnis %] [Buttons] Restrukturiert auf CSS-Grid mit 2 Spalten, 2 Reihen. - Live + Ratio-Chips links (80px fixe Breite, monospace) - Dropdown + Buttons rechts - Stat-Chip-Helper statChipStyle(accentTint): accentTint=true bei 100% match Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+79
-77
@@ -560,10 +560,9 @@ export default function OberleisteApp() {
|
|||||||
|
|
||||||
<div style={sep} />
|
<div style={sep} />
|
||||||
|
|
||||||
{/* ====== MASSSTAB 2-Reihen ======
|
{/* ====== MASSSTAB 2x2 ======
|
||||||
Links: Segmented-Pill mit 4 Tools (zentriert ueber beide Reihen)
|
Reihe 1: [Aktueller Massstab] [Massstab-Dropdown (gruen wenn gesetzt)]
|
||||||
Rechts oben: Massstab-Dropdown (gesetzt = green)
|
Reihe 2: [Zoom-Verhaeltnis %] [Buttons]
|
||||||
Rechts unten: Zoom-Verhaeltnis zum gesetzten Massstab (% Anzeige)
|
|
||||||
*/}
|
*/}
|
||||||
{(() => {
|
{(() => {
|
||||||
const SegBtn = ({ icon, onClick, title, disabled, active, isFirst, isLast }) => (
|
const SegBtn = ({ icon, onClick, title, disabled, active, isFirst, isLast }) => (
|
||||||
@@ -582,8 +581,6 @@ export default function OberleisteApp() {
|
|||||||
style={{ color: active ? 'var(--bg-panel)' : 'var(--text-muted)' }} />
|
style={{ color: active ? 'var(--bg-panel)' : 'var(--text-muted)' }} />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
// Zoom-Verhaeltnis: setScale / liveScale → %
|
|
||||||
// 100% = perfekt am gesetzten Massstab; >100 = reingezoomt; <100 = rausgezoomt
|
|
||||||
const ratio = (!isPerspective && appliedScale && scaleVal)
|
const ratio = (!isPerspective && appliedScale && scaleVal)
|
||||||
? appliedScale / scaleVal
|
? appliedScale / scaleVal
|
||||||
: null
|
: null
|
||||||
@@ -593,13 +590,85 @@ export default function OberleisteApp() {
|
|||||||
? Math.round(ratio * 100) + '%'
|
? Math.round(ratio * 100) + '%'
|
||||||
: (ratio * 100).toFixed(ratio < 0.1 ? 1 : 0) + '%'
|
: (ratio * 100).toFixed(ratio < 0.1 ? 1 : 0) + '%'
|
||||||
const atScale = ratio != null && Math.abs(ratio - 1) < 0.005
|
const atScale = ratio != null && Math.abs(ratio - 1) < 0.005
|
||||||
|
const STAT_W = 80 // Breite der linken Stat-Chips (1:N / %)
|
||||||
|
const statChipStyle = (accentTint) => ({
|
||||||
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
height: BAR_H, width: STAT_W,
|
||||||
|
background: accentTint ? 'var(--accent-dim)' : 'var(--bg-input)',
|
||||||
|
color: accentTint ? 'var(--accent-light)' : 'var(--text-muted)',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
borderRadius: 999,
|
||||||
|
fontFamily: 'DM Mono, monospace', fontSize: 11,
|
||||||
|
fontWeight: accentTint ? 600 : 500,
|
||||||
|
flexShrink: 0,
|
||||||
|
})
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', gap: 6, alignItems: 'flex-start', flexShrink: 0 }}>
|
<div style={{
|
||||||
{/* Buttons-Pill — sitzt auf Reihe 1, vertikal zentriert ueber beide Reihen */}
|
display: 'grid', gridTemplateColumns: 'auto auto', gap: '4px 6px',
|
||||||
|
alignItems: 'center', flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
{/* Reihe 1, Spalte 1: Aktueller Live-Massstab */}
|
||||||
|
<div style={statChipStyle(false)}
|
||||||
|
title={isPerspective ? 'Perspektive — kein Massstab' : 'Aktueller Live-Massstab'}>
|
||||||
|
{isPerspective ? '—' : fmtScale(scaleVal)}
|
||||||
|
</div>
|
||||||
|
{/* Reihe 1, Spalte 2: Gesetzter Massstab Dropdown */}
|
||||||
|
{customMode ? (
|
||||||
|
<input
|
||||||
|
ref={customInputRef}
|
||||||
|
disabled={isPerspective}
|
||||||
|
type="text" placeholder="1:N"
|
||||||
|
value={draft}
|
||||||
|
onChange={(e) => setDraft(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') applyDraft()
|
||||||
|
else if (e.key === 'Escape') cancelDraft()
|
||||||
|
}}
|
||||||
|
onBlur={applyDraft}
|
||||||
|
style={{
|
||||||
|
height: BAR_H, width: 158,
|
||||||
|
background: 'var(--bg-input)',
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
borderRadius: 999,
|
||||||
|
padding: '0 12px', fontSize: 11,
|
||||||
|
fontFamily: 'DM Mono, monospace',
|
||||||
|
outline: 'none',
|
||||||
|
}}
|
||||||
|
title="Massstab eingeben (Enter = uebernehmen, Esc = abbrechen)"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BarCombo
|
||||||
|
icon="straighten"
|
||||||
|
value={dropdownValue}
|
||||||
|
onChange={(v) => applyDropdown(v)}
|
||||||
|
disabled={isPerspective}
|
||||||
|
width={140}
|
||||||
|
valueAccent={appliedScale != null}
|
||||||
|
title="Gesetzter Massstab"
|
||||||
|
>
|
||||||
|
<option value="__none__">—</option>
|
||||||
|
{PRESETS.map(p => (
|
||||||
|
<option key={p.value} value={String(p.value)}>{p.label}</option>
|
||||||
|
))}
|
||||||
|
{appliedScale != null && !PRESETS.some(p => p.value === appliedScale) && (
|
||||||
|
<option value={String(appliedScale)}>1:{appliedScale}</option>
|
||||||
|
)}
|
||||||
|
<option value="__custom__">Eigener…</option>
|
||||||
|
</BarCombo>
|
||||||
|
)}
|
||||||
|
{/* Reihe 2, Spalte 1: Zoom-Verhaeltnis zum gesetzten Massstab */}
|
||||||
|
<div style={statChipStyle(atScale)}
|
||||||
|
title={ratio != null
|
||||||
|
? `Aktueller Zoom = ${ratioText} des gesetzten Massstabs`
|
||||||
|
: (isPerspective ? 'Perspektive' : 'Kein Massstab gesetzt')}>
|
||||||
|
{ratioText}
|
||||||
|
</div>
|
||||||
|
{/* Reihe 2, Spalte 2: Buttons-Pill */}
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'inline-flex', alignSelf: 'center',
|
display: 'inline-flex',
|
||||||
border: '1px solid var(--border)', borderRadius: 999,
|
border: '1px solid var(--border)', borderRadius: 999,
|
||||||
overflow: 'hidden', flexShrink: 0,
|
overflow: 'hidden', flexShrink: 0, justifySelf: 'start',
|
||||||
}}>
|
}}>
|
||||||
<SegBtn icon="percent" onClick={apply100} isFirst
|
<SegBtn icon="percent" onClick={apply100} isFirst
|
||||||
disabled={isPerspective || !appliedScale}
|
disabled={isPerspective || !appliedScale}
|
||||||
@@ -617,73 +686,6 @@ export default function OberleisteApp() {
|
|||||||
? 'Print-View aktiv — klick zum Ausschalten'
|
? 'Print-View aktiv — klick zum Ausschalten'
|
||||||
: 'Strichstärken anzeigen (Print-View)'} />
|
: 'Strichstärken anzeigen (Print-View)'} />
|
||||||
</div>
|
</div>
|
||||||
{/* Dropdown + Zoom-Ratio gestapelt */}
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
||||||
{/* Reihe 1: Set-Massstab Dropdown (grün wenn gesetzt) */}
|
|
||||||
{customMode ? (
|
|
||||||
<input
|
|
||||||
ref={customInputRef}
|
|
||||||
disabled={isPerspective}
|
|
||||||
type="text" placeholder="1:N"
|
|
||||||
value={draft}
|
|
||||||
onChange={(e) => setDraft(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') applyDraft()
|
|
||||||
else if (e.key === 'Escape') cancelDraft()
|
|
||||||
}}
|
|
||||||
onBlur={applyDraft}
|
|
||||||
style={{
|
|
||||||
height: BAR_H, width: 158,
|
|
||||||
background: 'var(--bg-input)',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
border: '1px solid var(--border)',
|
|
||||||
borderRadius: 999,
|
|
||||||
padding: '0 12px', fontSize: 11,
|
|
||||||
fontFamily: 'DM Mono, monospace',
|
|
||||||
outline: 'none',
|
|
||||||
}}
|
|
||||||
title="Massstab eingeben (Enter = uebernehmen, Esc = abbrechen)"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<BarCombo
|
|
||||||
icon="straighten"
|
|
||||||
value={dropdownValue}
|
|
||||||
onChange={(v) => applyDropdown(v)}
|
|
||||||
disabled={isPerspective}
|
|
||||||
width={140}
|
|
||||||
valueAccent={appliedScale != null}
|
|
||||||
title="Gesetzter Massstab"
|
|
||||||
>
|
|
||||||
<option value="__none__">—</option>
|
|
||||||
{PRESETS.map(p => (
|
|
||||||
<option key={p.value} value={String(p.value)}>{p.label}</option>
|
|
||||||
))}
|
|
||||||
{appliedScale != null && !PRESETS.some(p => p.value === appliedScale) && (
|
|
||||||
<option value={String(appliedScale)}>1:{appliedScale}</option>
|
|
||||||
)}
|
|
||||||
<option value="__custom__">Eigener…</option>
|
|
||||||
</BarCombo>
|
|
||||||
)}
|
|
||||||
{/* Reihe 2: Zoom-Verhaeltnis zum gesetzten Massstab.
|
|
||||||
Aligned mit dem Pill (nicht mit dem Icon links davon). */}
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
||||||
<div style={{
|
|
||||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
||||||
height: BAR_H, padding: '0 10px', minWidth: 140,
|
|
||||||
background: atScale ? 'var(--accent-dim)' : 'var(--bg-input)',
|
|
||||||
color: atScale ? 'var(--accent-light)' : 'var(--text-muted)',
|
|
||||||
border: '1px solid var(--border)',
|
|
||||||
borderRadius: 999,
|
|
||||||
fontFamily: 'DM Mono, monospace', fontSize: 11,
|
|
||||||
fontWeight: atScale ? 600 : 500,
|
|
||||||
flexShrink: 0,
|
|
||||||
}} title={ratio != null
|
|
||||||
? `Aktueller Zoom = ${ratioText} des gesetzten Massstabs (${atScale ? 'auf Massstab' : 'zoom mit % um zu snappen'})`
|
|
||||||
: (isPerspective ? 'Perspektive — kein Massstab' : 'Kein Massstab gesetzt')}>
|
|
||||||
{ratioText}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
Reference in New Issue
Block a user