Stempelstile-Tab in ProjectSettings (Manager analog Raumstile)

Beide Stempel-Familien (Raum-Stempel + Bilanz-Stempel) sind jetzt
konsistent verwaltbar in den Project-Settings.

Backend:
- elemente.py: DUPLICATE_STEMPEL_STIL + REORDER_STEMPEL_STILE handlers
- rhinopanel.py ProjectSettings-Bridge:
  - stempel_stile in params (initial)
  - 4 neue dispatch-Handler (SAVE/DELETE/DUPLICATE/REORDER_STEMPEL_STIL)
  - direkter Dispatch zu elemente.load/save_stempel_stile (kein Roundtrip)
  - STEMPEL_STILE_UPDATED-Message nach jeder Op

Frontend:
- rhinoBridge.js: reorderStempelStile + duplicateStempelStil exports
- ProjectSettingsDialog:
  - Neuer Tab "Stempelstile" parallel zu "Raumstile"
  - Drag-Reorder via HTML5 native, Inline-Rename, Duplicate, Delete
  - Mini-Preview: Header + Anzahl aktiver Show-Flags
  - Name wird mit Font/Bold/Italic des Stils gerendert als Vorschau
This commit is contained in:
2026-05-27 00:51:57 +02:00
parent f457db93e7
commit 2a838aee93
4 changed files with 282 additions and 9 deletions
+126 -6
View File
@@ -10,6 +10,7 @@ import {
listLibraryItems, addLibraryFile, updateLibraryItem, deleteLibraryItem,
saveSelectionAsLibrary,
saveRaumStil, deleteRaumStil, duplicateRaumStil, reorderRaumStile,
saveStempelStil, deleteStempelStil, duplicateStempelStil, reorderStempelStile,
} from '../lib/rhinoBridge'
/* Field — Stack-Layout fuer komplexe Inputs (mehrere Felder nebeneinander) */
@@ -651,6 +652,9 @@ export default function ProjectSettingsDialog({
// wenn Backend CRUD-Op fertig ist. Drag-Reorder lokal + commit beim Drop.
const [raumStile, setRaumStile] = useState(initial.raumStempelStile || [])
const [dragStilIdx, setDragStilIdx] = useState(null)
// Stempel-Stile (Bilanz-Stempel-Presets) — parallele Liste
const [stempelStile, setStempelStile] = useState(initial.stempelStile || [])
const [dragStempelIdx, setDragStempelIdx] = useState(null)
const fontsList = initial.fonts || []
// Aktuell ausgewaehltes Material aus Selection ableiten
@@ -691,6 +695,9 @@ export default function ProjectSettingsDialog({
onMessage('STILE_UPDATED', ({ raumStempelStile }) => {
if (Array.isArray(raumStempelStile)) setRaumStile(raumStempelStile)
})
onMessage('STEMPEL_STILE_UPDATED', ({ stempelStile: ss }) => {
if (Array.isArray(ss)) setStempelStile(ss)
})
}, [])
// Backend-File-Picker-Antwort: aktualisiert das Slot im aktuell
@@ -760,12 +767,13 @@ export default function ProjectSettingsDialog({
<div style={{ display: 'flex', flexDirection: 'column', flex: 1,
minHeight: 0, overflow: 'hidden' }}>
<TabBar tabs={[
{ key: 'defaults', label: 'Voreinstellungen' },
{ key: 'materials', label: 'Materialien' },
{ key: 'linetypes', label: 'Linientypen' },
{ key: 'hatches', label: 'Schraffuren' },
{ key: 'symbols', label: 'Symbole' },
{ key: 'raumstile', label: 'Raumstile' },
{ key: 'defaults', label: 'Voreinstellungen' },
{ key: 'materials', label: 'Materialien' },
{ key: 'linetypes', label: 'Linientypen' },
{ key: 'hatches', label: 'Schraffuren' },
{ key: 'symbols', label: 'Symbole' },
{ key: 'raumstile', label: 'Raumstile' },
{ key: 'stempelstile', label: 'Stempelstile' },
]} active={tab} onChange={setTab} />
{/* Body */}
@@ -1461,6 +1469,118 @@ export default function ProjectSettingsDialog({
)}
</div>
)}
{tab === 'stempelstile' && (
<div style={{ maxWidth: 760 }}>
<div style={{ fontSize: 10, color: 'var(--text-muted)',
padding: '6px 0 10px', lineHeight: 1.5 }}>
Stempel-Stile (Presets) für SIA-Bilanz-Stempel. Werden via
Stempel-Properties erstellt ("+ Aktuelle Settings als Stil
speichern"), hier nur verwaltet. Drag zum Umsortieren
Reihenfolge entspricht dem Dropdown in den Stempel-Properties.
</div>
{stempelStile.length === 0 ? (
<div style={{ padding: 30, textAlign: 'center',
color: 'var(--text-muted)', fontSize: 11 }}>
Noch keine Stempel-Stile gespeichert. Im Elemente-Panel
einen Stempel platzieren, konfigurieren und im Stempel-
Properties "+ Aktuelle Settings als Stil speichern".
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{stempelStile.map((s, idx) => {
const isDrag = dragStempelIdx === idx
// Show-Flags zaehlen fuer ein Mini-Preview
const onCount = [
'showHnf','showNnf','showNf','showVf','showFf',
'showNgf','showGf','showAgf','showCount','showScope',
].filter(k => s[k] !== false).length
return (
<div key={s.id}
draggable
onDragStart={(e) => {
setDragStempelIdx(idx)
try { e.dataTransfer.effectAllowed = 'move' } catch (_) {}
}}
onDragOver={(e) => { e.preventDefault() }}
onDrop={(e) => {
e.preventDefault()
if (dragStempelIdx == null || dragStempelIdx === idx) return
const next = [...stempelStile]
const [moved] = next.splice(dragStempelIdx, 1)
next.splice(idx, 0, moved)
setStempelStile(next)
reorderStempelStile(next.map(x => x.id))
setDragStempelIdx(null)
}}
onDragEnd={() => setDragStempelIdx(null)}
style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '8px 10px',
background: isDrag ? 'var(--bg-item-active)' : 'var(--bg-input)',
border: '1px solid var(--border)',
borderRadius: 'var(--r)',
cursor: 'grab',
opacity: isDrag ? 0.5 : 1,
transition: 'opacity 0.15s',
}}>
<Icon name="drag_indicator" size={14}
style={{ color: 'var(--text-muted)' }} />
<input type="text" value={s.name}
onChange={(e) => {
const next = stempelStile.map(x =>
x.id === s.id ? { ...x, name: e.target.value } : x)
setStempelStile(next)
}}
onBlur={(e) => {
const newName = (e.target.value || '').trim()
if (newName && newName !== s.name) {
const { id, name: _n, ...rest } = s
saveStempelStil(id, newName, rest)
}
}}
onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
style={{ flex: 1, fontSize: 11, height: BAR_H,
padding: '0 10px',
fontFamily: s.font ? `"${s.font}", monospace` : 'inherit',
fontWeight: s.bold ? 700 : 400,
fontStyle: s.italic ? 'italic' : 'normal' }} />
<span style={{ fontSize: 9,
color: 'var(--text-muted)',
fontFamily: 'DM Mono, monospace',
minWidth: 140, textAlign: 'right' }}>
{s.header ? `"${s.header}"` : '—'} · {onCount} Felder
</span>
<button onClick={() => duplicateStempelStil(s.id)}
title="Duplizieren"
style={{
background: 'transparent', border: '1px solid var(--border)',
borderRadius: 'var(--r)', cursor: 'pointer',
padding: '4px 8px', fontSize: 10,
color: 'var(--text-primary)',
}}>
<Icon name="content_copy" size={11} />
</button>
<button onClick={() => {
if (window.confirm(`Stil "${s.name}" löschen?`))
deleteStempelStil(s.id)
}}
title="Löschen"
style={{
background: 'transparent', border: '1px solid var(--border)',
borderRadius: 'var(--r)', cursor: 'pointer',
padding: '4px 8px', fontSize: 10,
color: 'var(--text-danger, #c44)',
}}>
<Icon name="delete" size={11} />
</button>
</div>
)
})}
</div>
)}
</div>
)}
</div>
{/* Footer — Pill-Buttons */}