Symbol-Picker als Satellite-Fenster (statt Modal im Elemente-Panel)

UX-Verbesserung: Modal-Overlay im engen Elemente-Panel war unpraktisch.
Symbol-Picker oeffnet sich jetzt als eigenstaendiges Eto.Form-Fenster
(wie Library/Project-Settings).

Frontend:
- SymbolPicker bekommt embedded-Prop (Satellite-Mount vs Modal-Overlay)
- Neuer SymbolPickerApp Satellite-Wrapper (PANEL_PARAMS lesen + Bridge)
- main.jsx: 'symbol_picker' Mode-Routing
- ElementeApp: Symbol-Button ruft nur noch listLibrary() — Backend
  oeffnet das Fenster

Backend:
- _cmd_list_library oeffnet jetzt das Satellite-Window mit eigener
  Bridge (PICK -> CREATE_SYMBOL, CANCEL -> Close)
- PICK schliesst Fenster + triggert interactive GetPoint im Viewport

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 03:39:03 +02:00
parent 8184f559fc
commit de57c320c2
5 changed files with 168 additions and 115 deletions
+99 -85
View File
@@ -63,7 +63,7 @@ function ItemCard({ item, onPick }) {
)
}
export default function SymbolPicker({ items, onPick, onClose }) {
export default function SymbolPicker({ items, onPick, onClose, embedded = false }) {
const [search, setSearch] = useState('')
const [typeFilter, setTypeFilter] = useState('all')
@@ -84,94 +84,108 @@ export default function SymbolPicker({ items, onPick, onClose }) {
})
}, [placable, search, typeFilter])
return (
<div style={{
position: 'fixed', inset: 0, zIndex: 200,
background: 'var(--bg-overlay)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}} onClick={onClose}>
<div onClick={(e) => e.stopPropagation()}
style={{
width: '90%', maxWidth: 640, maxHeight: '80vh',
background: 'var(--bg-dialog)',
border: '1px solid var(--border)',
borderRadius: 12,
display: 'flex', flexDirection: 'column',
overflow: 'hidden',
color: 'var(--text-primary)',
fontFamily: 'var(--font)', fontSize: 11,
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
}}>
{/* Header */}
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 14px',
borderBottom: '1px solid var(--border)',
}}>
<Icon name="inventory_2" size={14}
style={{ color: 'var(--accent)' }} />
<span style={{ flex: 1, fontWeight: 600, fontSize: 12 }}>
Symbol / Objekt einfuegen
</span>
<BarButton icon="close" onClick={onClose} title="Schliessen" />
</div>
const innerStyle = embedded ? {
width: '100%', height: '100%',
background: 'var(--bg-dialog)',
display: 'flex', flexDirection: 'column',
overflow: 'hidden',
color: 'var(--text-primary)',
fontFamily: 'var(--font)', fontSize: 11,
} : {
width: '90%', maxWidth: 640, maxHeight: '80vh',
background: 'var(--bg-dialog)',
border: '1px solid var(--border)',
borderRadius: 12,
display: 'flex', flexDirection: 'column',
overflow: 'hidden',
color: 'var(--text-primary)',
fontFamily: 'var(--font)', fontSize: 11,
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
}
{/* Toolbar */}
<div style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '8px 14px',
borderBottom: '1px solid var(--border)',
}}>
<input type="text" value={search}
onChange={(ev) => setSearch(ev.target.value)}
placeholder="Suchen (Name oder Tag)…"
autoFocus
style={{ flex: 1, height: BAR_H, padding: '0 12px',
fontSize: 11 }} />
<BarToggle label="Alle" active={typeFilter === 'all'}
onClick={() => setTypeFilter('all')} />
<BarToggle label="Symbole" active={typeFilter === 'symbol'}
onClick={() => setTypeFilter('symbol')} />
<BarToggle label="Objekte" active={typeFilter === 'object'}
onClick={() => setTypeFilter('object')} />
</div>
const content = (
<div style={innerStyle}>
{/* Header */}
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 14px',
borderBottom: '1px solid var(--border)',
}}>
<Icon name="inventory_2" size={14}
style={{ color: 'var(--accent)' }} />
<span style={{ flex: 1, fontWeight: 600, fontSize: 12 }}>
Symbol / Objekt einfuegen
</span>
<BarButton icon="close" onClick={onClose} title="Schliessen" />
</div>
{/* Grid */}
<div style={{ flex: 1, minHeight: 200, overflowY: 'auto',
padding: 12 }}>
{filtered.length === 0 ? (
<div style={{ padding: 40, textAlign: 'center',
color: 'var(--text-muted)' }}>
{placable.length === 0
? 'Keine Symbole/Objekte in der Library.'
: 'Nichts gefunden.'}
</div>
) : (
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(130px, 1fr))',
gap: 8,
}}>
{filtered.map(it => (
<ItemCard key={it.id} item={it} onPick={onPick} />
))}
</div>
)}
</div>
{/* Toolbar */}
<div style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '8px 14px',
borderBottom: '1px solid var(--border)',
}}>
<input type="text" value={search}
onChange={(ev) => setSearch(ev.target.value)}
placeholder="Suchen (Name oder Tag)…"
autoFocus
style={{ flex: 1, height: BAR_H, padding: '0 12px',
fontSize: 11 }} />
<BarToggle label="Alle" active={typeFilter === 'all'}
onClick={() => setTypeFilter('all')} />
<BarToggle label="Symbole" active={typeFilter === 'symbol'}
onClick={() => setTypeFilter('symbol')} />
<BarToggle label="Objekte" active={typeFilter === 'object'}
onClick={() => setTypeFilter('object')} />
</div>
{/* Footer */}
<div style={{
padding: '8px 14px',
borderTop: '1px solid var(--border)',
background: 'var(--bg-section)',
fontSize: 10, color: 'var(--text-muted)',
}}>
Klick auf Item im Viewport Punkt waehlen zum Platzieren.
{filtered.length > 0 && (
<span> · {filtered.length} / {placable.length}</span>
)}
</div>
{/* Grid */}
<div style={{ flex: 1, minHeight: 200, overflowY: 'auto',
padding: 12 }}>
{filtered.length === 0 ? (
<div style={{ padding: 40, textAlign: 'center',
color: 'var(--text-muted)' }}>
{placable.length === 0
? 'Keine Symbole/Objekte in der Library.'
: 'Nichts gefunden.'}
</div>
) : (
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(130px, 1fr))',
gap: 8,
}}>
{filtered.map(it => (
<ItemCard key={it.id} item={it} onPick={onPick} />
))}
</div>
)}
</div>
{/* Footer */}
<div style={{
padding: '8px 14px',
borderTop: '1px solid var(--border)',
background: 'var(--bg-section)',
fontSize: 10, color: 'var(--text-muted)',
}}>
Klick auf Item im Viewport Punkt waehlen zum Platzieren.
{filtered.length > 0 && (
<span> · {filtered.length} / {placable.length}</span>
)}
</div>
</div>
)
if (embedded) return content
return (
<div onClick={onClose}
style={{
position: 'fixed', inset: 0, zIndex: 200,
background: 'var(--bg-overlay)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<div onClick={(e) => e.stopPropagation()}>{content}</div>
</div>
)
}