Library Phase A.2 (Symbol/Object-Import) + Oberleiste-Pill-Restyle
Library Phase A.2: - import_symbol/import_object via File3dm.Read + InstanceDefinitions.Add - Stabile Block-Namen 'dossier_lib_<libraryId>' fuer Dedupe - Seed-Manifest erweitert um Nordpfeil (symbol) + Laubbaum (object) - ItemCard rendert type-spezifische Preview (Color-Swatch fuer material, Material-Icon fuer symbol/object) Oberleiste-Pill-Restyle: - OberleisteApp: Version unter DOSSIER-Logo, Settings-Icons vertikal gestapelt - ProjectSettingsDialog: Pill-Tabs, BarToggle-Footer, MaterialRow mit Hover-Highlight, Header entfernt (Eto.Form hat eigenen) - LibraryBrowser: BarButton-Reload, Pill-Typ-Filter, MaterialCard mit BarToggle-Pill, Header entfernt - Globaler select-Stil: bg-input statt bg-item (dunkler im Dark-Mode, konsistent zu Oberleiste-BarCombo) Routing: - OberleisteBridge delegiert OPEN_PROJECT_SETTINGS + OPEN_LIBRARY an EbenenBridge (sticky ebenen_bridge_ref) — vorher kamen die Messages an der falschen Bridge an und wurden verschluckt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,27 +1,67 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import Icon from './Icon'
|
||||
import { BarToggle, BarButton, BAR_H } from './BarControls'
|
||||
|
||||
/* MaterialCard — Preview-Swatch + Name + Tags + Import-Button. */
|
||||
function MaterialCard({ item, imported, onImport }) {
|
||||
const color = item.data?.color || '#888888'
|
||||
/* Preview — abhaengig vom Typ:
|
||||
material → Color-Swatch
|
||||
symbol/object → Typ-Icon-Placeholder (spaeter: PNG-Thumbnail aus
|
||||
library/previews/) */
|
||||
function ItemPreview({ item }) {
|
||||
if (item.type === 'material') {
|
||||
return (
|
||||
<div style={{
|
||||
height: 64,
|
||||
background: item.data?.color || '#888888',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}} />
|
||||
)
|
||||
}
|
||||
const iconName = item.type === 'symbol' ? 'navigation' : 'forest'
|
||||
return (
|
||||
<div style={{
|
||||
height: 64,
|
||||
background: 'var(--bg-input)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Icon name={iconName} size={28}
|
||||
style={{ color: 'var(--text-muted)' }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ItemCard — Preview + Name + Tags + Import-Pill */
|
||||
function ItemCard({ item, imported, onImport }) {
|
||||
// material wird "importiert" gezeigt wenn schon in Project-Settings.
|
||||
// symbol/object koennen N-mal eingefuegt werden → "Importiert" wird
|
||||
// nicht persistent, jeder Klick legt eine weitere Instanz an.
|
||||
const isMaterial = item.type === 'material'
|
||||
const ctaLabel = isMaterial
|
||||
? (imported ? 'Importiert' : 'Importieren')
|
||||
: 'Einfügen'
|
||||
const ctaDisabled = isMaterial && imported
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column',
|
||||
border: '1px solid var(--border)', borderRadius: 6,
|
||||
border: '1px solid var(--border)', borderRadius: 8,
|
||||
background: 'var(--bg-section)',
|
||||
overflow: 'hidden',
|
||||
cursor: imported ? 'default' : 'pointer',
|
||||
opacity: imported ? 0.75 : 1,
|
||||
cursor: ctaDisabled ? 'default' : 'pointer',
|
||||
opacity: ctaDisabled ? 0.75 : 1,
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
}}
|
||||
onDoubleClick={() => { if (!imported) onImport(item.id) }}
|
||||
title={imported ? 'Bereits importiert' : 'Doppelklick = importieren'}>
|
||||
<div style={{
|
||||
height: 64,
|
||||
background: color,
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}} />
|
||||
<div style={{ padding: '6px 8px', display: 'flex',
|
||||
flexDirection: 'column', gap: 4 }}>
|
||||
onMouseEnter={(e) => {
|
||||
if (ctaDisabled) return
|
||||
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.borderColor = 'var(--border)'
|
||||
}}
|
||||
onDoubleClick={() => { if (!ctaDisabled) onImport(item.id) }}
|
||||
title={ctaDisabled ? 'Bereits importiert' : 'Doppelklick = importieren'}>
|
||||
<ItemPreview item={item} />
|
||||
<div style={{ padding: '8px 10px', display: 'flex',
|
||||
flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<span style={{ flex: 1, fontSize: 11, fontWeight: 600,
|
||||
color: 'var(--text-primary)',
|
||||
@@ -29,7 +69,7 @@ function MaterialCard({ item, imported, onImport }) {
|
||||
whiteSpace: 'nowrap' }}>
|
||||
{item.name}
|
||||
</span>
|
||||
{imported && (
|
||||
{ctaDisabled && (
|
||||
<Icon name="check" size={12}
|
||||
style={{ color: 'var(--accent)' }} />
|
||||
)}
|
||||
@@ -40,23 +80,17 @@ function MaterialCard({ item, imported, onImport }) {
|
||||
<span key={t} style={{
|
||||
fontSize: 9, color: 'var(--text-muted)',
|
||||
background: 'var(--bg-input)',
|
||||
padding: '1px 5px', borderRadius: 999,
|
||||
padding: '1px 6px', borderRadius: 999,
|
||||
border: '1px solid var(--border-light)',
|
||||
}}>{t}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => { if (!imported) onImport(item.id) }}
|
||||
disabled={imported}
|
||||
style={{
|
||||
marginTop: 2, padding: '3px 6px', fontSize: 10,
|
||||
background: imported ? 'transparent' : 'var(--accent)',
|
||||
color: imported ? 'var(--text-muted)' : '#fff',
|
||||
border: imported ? '1px solid var(--border)' : 'none',
|
||||
borderRadius: 999, cursor: imported ? 'default' : 'pointer',
|
||||
}}>
|
||||
{imported ? 'Importiert' : 'Importieren'}
|
||||
</button>
|
||||
<BarToggle
|
||||
label={ctaLabel}
|
||||
active={!ctaDisabled}
|
||||
onClick={() => { if (!ctaDisabled) onImport(item.id) }}
|
||||
disabled={ctaDisabled} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -97,30 +131,7 @@ export default function LibraryBrowser({
|
||||
overflow: 'hidden',
|
||||
fontFamily: 'var(--font)', color: 'var(--text-primary)', fontSize: 11,
|
||||
}}>
|
||||
{/* Header */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 6,
|
||||
padding: '10px 12px',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
}}>
|
||||
<Icon name="inventory_2" size={14}
|
||||
style={{ color: 'var(--accent)', flexShrink: 0 }} />
|
||||
<span style={{ flex: 1, fontWeight: 600, fontSize: 11 }}>
|
||||
{manifest?.name || 'Dossier-Library'}
|
||||
</span>
|
||||
<button onClick={onReload}
|
||||
title="Manifest neu laden"
|
||||
style={{ background: 'transparent', border: 'none',
|
||||
cursor: 'pointer', color: 'var(--text-muted)',
|
||||
padding: '0 4px' }}>
|
||||
<Icon name="refresh" size={14} />
|
||||
</button>
|
||||
<button onClick={onClose}
|
||||
style={{ color: 'var(--text-muted)', fontSize: 16,
|
||||
padding: '0 4px', lineHeight: 1 }}>×</button>
|
||||
</div>
|
||||
|
||||
{/* Toolbar */}
|
||||
{/* Toolbar — Suche + Pill-Filter + Reload */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 6,
|
||||
padding: '8px 12px',
|
||||
@@ -129,24 +140,18 @@ export default function LibraryBrowser({
|
||||
<input type="text" value={search}
|
||||
onChange={(ev) => setSearch(ev.target.value)}
|
||||
placeholder="Suchen (Name oder Tag)…"
|
||||
style={{ flex: 1, fontSize: 11, padding: '4px 8px',
|
||||
borderRadius: 999 }} />
|
||||
<div style={{ display: 'flex', gap: 0,
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 999, overflow: 'hidden' }}>
|
||||
style={{ flex: 1, height: BAR_H, padding: '0 12px',
|
||||
fontSize: 11 }} />
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
{types.map(t => (
|
||||
<button key={t}
|
||||
onClick={() => setTypeFilter(t)}
|
||||
style={{
|
||||
background: typeFilter === t ? 'var(--accent)' : 'transparent',
|
||||
color: typeFilter === t ? '#fff' : 'var(--text-muted)',
|
||||
border: 'none', padding: '4px 10px', fontSize: 10,
|
||||
cursor: 'pointer',
|
||||
}}>
|
||||
{t === 'all' ? 'Alle' : t}
|
||||
</button>
|
||||
<BarToggle key={t}
|
||||
label={t === 'all' ? 'Alle' : t}
|
||||
active={typeFilter === t}
|
||||
onClick={() => setTypeFilter(t)} />
|
||||
))}
|
||||
</div>
|
||||
<BarButton icon="refresh" onClick={onReload}
|
||||
title="Manifest neu laden" />
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
@@ -171,7 +176,7 @@ export default function LibraryBrowser({
|
||||
gap: 8,
|
||||
}}>
|
||||
{filtered.map(it => (
|
||||
<MaterialCard key={it.id}
|
||||
<ItemCard key={it.id}
|
||||
item={it}
|
||||
imported={importedSet.has(it.id)}
|
||||
onImport={onImport} />
|
||||
|
||||
Reference in New Issue
Block a user