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:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user