Stempel-Stil-Bulk-Apply + Manager-Tab in ProjectSettings
PHASE 1 — Bulk-Apply auf mehrere selektierte Raeume: - Backend _cmd_apply_raum_stil: leere ids im Patch → nimmt automatisch alle aktuell SELEKTIERTEN raum_outlines aus dem Doc (Bulk-Fallback) - Frontend sendet leere ids fuer die Default-Bulk-Variante. User selektiert N Raeume im Viewport → klickt Stil im Properties-Picker → Stil wird auf alle angewendet. Inkludiert immer den Properties-Raum. PHASE 2 — Stil-Manager als Settings-Tab: - Neuer Tab "Raumstile" in ProjectSettings-Dialog - Liste aller Stile mit Drag-Reorder (HTML5 native), Inline-Rename, Duplicate (content_copy-Icon), Delete (mit Confirm) - Mini-Preview: zeigt Layout-Struktur (z.B. "nummer·name / funktion / area") und der Name wird mit Font/Bold/Italic des Stils gerendert als Vorschau - Backend _cmd_reorder_raum_stile + _cmd_duplicate_raum_stil in elemente.py - ProjectSettings-Bridge dispatcht direkt zu elemente.load/save_raum_stempel_stile (kein Roundtrip via Elemente-Bridge noetig) - STILE_UPDATED-Message nach jeder Op pusht die neue Liste zum Dialog - params bei _open_project_settings enthalten jetzt raumStempelStile + fonts Bridge-Exports: reorderRaumStile / duplicateRaumStil in rhinoBridge.js
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
renameHatch, deleteHatch, importHatchFile,
|
||||
listLibraryItems, addLibraryFile, updateLibraryItem, deleteLibraryItem,
|
||||
saveSelectionAsLibrary,
|
||||
saveRaumStil, deleteRaumStil, duplicateRaumStil, reorderRaumStile,
|
||||
} from '../lib/rhinoBridge'
|
||||
|
||||
/* Field — Stack-Layout fuer komplexe Inputs (mehrere Felder nebeneinander) */
|
||||
@@ -646,6 +647,11 @@ export default function ProjectSettingsDialog({
|
||||
const [libRoot, setLibRoot] = useState(initial.libraryRoot || '')
|
||||
const [selLib, setSelLib] = useState(null)
|
||||
const builtin = initial.builtinMaterials || []
|
||||
// Raumstile-State: kommt von initial + wird per STATE-Refresh aktualisiert
|
||||
// wenn Backend CRUD-Op fertig ist. Drag-Reorder lokal + commit beim Drop.
|
||||
const [raumStile, setRaumStile] = useState(initial.raumStempelStile || [])
|
||||
const [dragStilIdx, setDragStilIdx] = useState(null)
|
||||
const fontsList = initial.fonts || []
|
||||
|
||||
// Aktuell ausgewaehltes Material aus Selection ableiten
|
||||
const selectedMat = (() => {
|
||||
@@ -682,6 +688,9 @@ export default function ProjectSettingsDialog({
|
||||
onMessage('LIBRARY_ERROR', ({ msg }) => {
|
||||
if (msg) alert(msg)
|
||||
})
|
||||
onMessage('STILE_UPDATED', ({ raumStempelStile }) => {
|
||||
if (Array.isArray(raumStempelStile)) setRaumStile(raumStempelStile)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Backend-File-Picker-Antwort: aktualisiert das Slot im aktuell
|
||||
@@ -756,6 +765,7 @@ export default function ProjectSettingsDialog({
|
||||
{ key: 'linetypes', label: 'Linientypen' },
|
||||
{ key: 'hatches', label: 'Schraffuren' },
|
||||
{ key: 'symbols', label: 'Symbole' },
|
||||
{ key: 'raumstile', label: 'Raumstile' },
|
||||
]} active={tab} onChange={setTab} />
|
||||
|
||||
{/* Body */}
|
||||
@@ -1341,6 +1351,116 @@ export default function ProjectSettingsDialog({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'raumstile' && (
|
||||
<div style={{ maxWidth: 760 }}>
|
||||
<div style={{ fontSize: 10, color: 'var(--text-muted)',
|
||||
padding: '6px 0 10px', lineHeight: 1.5 }}>
|
||||
Raumstempel-Stile (Presets). Werden via Raum-Properties
|
||||
erstellt ("+ Aktuelle Settings als Stil speichern"), hier nur
|
||||
verwaltet. Drag zum Umsortieren — die Reihenfolge entspricht
|
||||
dem Dropdown in den Raum-Properties.
|
||||
</div>
|
||||
{raumStile.length === 0 ? (
|
||||
<div style={{ padding: 30, textAlign: 'center',
|
||||
color: 'var(--text-muted)', fontSize: 11 }}>
|
||||
Noch keine Stile gespeichert. Im Raum-Properties einen Raum
|
||||
konfigurieren und "+ Aktuelle Settings als Stil speichern".
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{raumStile.map((s, idx) => {
|
||||
const isDrag = dragStilIdx === idx
|
||||
return (
|
||||
<div key={s.id}
|
||||
draggable
|
||||
onDragStart={(e) => {
|
||||
setDragStilIdx(idx)
|
||||
try { e.dataTransfer.effectAllowed = 'move' } catch (_) {}
|
||||
}}
|
||||
onDragOver={(e) => { e.preventDefault() }}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault()
|
||||
if (dragStilIdx == null || dragStilIdx === idx) return
|
||||
const next = [...raumStile]
|
||||
const [moved] = next.splice(dragStilIdx, 1)
|
||||
next.splice(idx, 0, moved)
|
||||
setRaumStile(next)
|
||||
reorderRaumStile(next.map(x => x.id))
|
||||
setDragStilIdx(null)
|
||||
}}
|
||||
onDragEnd={() => setDragStilIdx(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 = raumStile.map(x =>
|
||||
x.id === s.id ? { ...x, name: e.target.value } : x)
|
||||
setRaumStile(next)
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
const newName = (e.target.value || '').trim()
|
||||
if (newName && newName !== s.name) {
|
||||
// Re-save mit gleicher id → effektiv rename
|
||||
const { id, name: _n, ...rest } = s
|
||||
saveRaumStil(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' }} />
|
||||
{/* Mini-Preview: Felder die in Layout vorkommen */}
|
||||
<span style={{ fontSize: 9,
|
||||
color: 'var(--text-muted)',
|
||||
fontFamily: 'DM Mono, monospace',
|
||||
minWidth: 100, textAlign: 'right' }}>
|
||||
{(s.layout && s.layout.length)
|
||||
? s.layout.map(row => row.join('·')).join(' / ')
|
||||
: '—'}
|
||||
</span>
|
||||
<button onClick={() => duplicateRaumStil(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?`))
|
||||
deleteRaumStil(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 */}
|
||||
|
||||
Reference in New Issue
Block a user