11import { useDraggable } from '@dnd-kit/core' ;
22import { ObjectRegistry } from '../../registry' ;
33import { PALETTE_GROUPS } from './paletteGroups' ;
4- import type { ObjectTypeDefinition } from '../../types/ObjectType' ;
4+ import { VIRTUAL_PALETTE_ENTRIES , type VirtualPaletteEntry } from './virtualEntries' ;
5+ import type { ObjectGroup } from '../../types/ObjectType' ;
56import { useT } from '../../lib/useT' ;
67import { useLabelStore } from '../../store/labelStore' ;
78import { mmToDots } from '../../lib/coordinates' ;
@@ -10,24 +11,33 @@ import { CollapsibleSection } from '../ui/CollapsibleSection';
1011import type { PaletteDragData } from '../../dnd/types' ;
1112
1213interface PaletteEntryProps {
14+ /** Unique within the palette: registry type or virtual entry id. */
15+ id : string ;
16+ /** Registry type to instantiate. Equals `id` for non-virtual entries. */
1317 type : string ;
14- def : ObjectTypeDefinition ;
18+ icon : string ;
19+ label : string ;
20+ defaultSize : { width : number ; height : number } ;
21+ propsOverride ?: object ;
1522}
1623
17- function PaletteEntry ( { type, def } : PaletteEntryProps ) {
18- const t = useT ( ) ;
24+ function PaletteEntry ( { id, type, icon, label, defaultSize, propsOverride } : PaletteEntryProps ) {
1925 const addObject = useLabelStore ( ( s ) => s . addObject ) ;
2026 const { attributes, listeners, setNodeRef, isDragging } = useDraggable ( {
21- id : `palette-${ type } ` ,
22- data : { type } satisfies PaletteDragData ,
27+ id : `palette-${ id } ` ,
28+ data : { type, propsOverride } satisfies PaletteDragData ,
2329 } ) ;
2430
2531 const handleDoubleClick = ( ) => {
26- const { label } = useLabelStore . getState ( ) ;
27- addObject ( type , {
28- x : Math . round ( mmToDots ( label . widthMm , label . dpmm ) / 2 - def . defaultSize . width / 2 ) ,
29- y : Math . round ( mmToDots ( label . heightMm , label . dpmm ) / 2 - def . defaultSize . height / 2 ) ,
30- } ) ;
32+ const { label : labelConfig } = useLabelStore . getState ( ) ;
33+ addObject (
34+ type ,
35+ {
36+ x : Math . round ( mmToDots ( labelConfig . widthMm , labelConfig . dpmm ) / 2 - defaultSize . width / 2 ) ,
37+ y : Math . round ( mmToDots ( labelConfig . heightMm , labelConfig . dpmm ) / 2 - defaultSize . height / 2 ) ,
38+ } ,
39+ propsOverride ,
40+ ) ;
3141 } ;
3242
3343 return (
@@ -47,34 +57,71 @@ function PaletteEntry({ type, def }: PaletteEntryProps) {
4757 ` }
4858 >
4959 < DragHandleIcon className = "w-2 h-3.5 shrink-0 text-muted opacity-0 group-hover:opacity-60 transition-opacity" />
50- < span className = "font-mono text-[11px] text-accent w-6 text-center shrink-0" >
51- { def . icon }
52- </ span >
53- < span className = "text-xs text-text" >
54- { ( t . types as Record < string , string > ) [ type ] ?? def . label }
55- </ span >
60+ < span className = "font-mono text-[11px] text-accent w-6 text-center shrink-0" > { icon } </ span >
61+ < span className = "text-xs text-text" > { label } </ span >
5662 </ div >
5763 ) ;
5864}
5965
66+ interface ResolvedEntry {
67+ id : string ;
68+ type : string ;
69+ icon : string ;
70+ label : string ;
71+ defaultSize : { width : number ; height : number } ;
72+ propsOverride ?: object ;
73+ }
74+
75+ function resolveEntries (
76+ group : ObjectGroup ,
77+ types : Record < string , string > ,
78+ ) : ResolvedEntry [ ] {
79+ const registry = Object . entries ( ObjectRegistry )
80+ . filter ( ( [ , def ] ) => def . group === group )
81+ . map ( ( [ type , def ] ) : ResolvedEntry => ( {
82+ id : type ,
83+ type,
84+ icon : def . icon ,
85+ label : types [ type ] ?? def . label ,
86+ defaultSize : def . defaultSize ,
87+ } ) ) ;
88+ const virtual = VIRTUAL_PALETTE_ENTRIES
89+ . filter ( ( v ) => v . group === group )
90+ . map ( ( v : VirtualPaletteEntry ) : ResolvedEntry => ( {
91+ id : v . id ,
92+ type : v . type ,
93+ icon : v . icon ,
94+ label : types [ v . labelKey ] ?? v . fallbackLabel ,
95+ defaultSize : v . defaultSize ,
96+ propsOverride : v . propsOverride ,
97+ } ) ) ;
98+ return [ ...registry , ...virtual ] ;
99+ }
100+
60101export function ObjectPalette ( ) {
61102 const t = useT ( ) ;
62103
63104 return (
64105 < div className = "p-3 flex flex-col gap-3" >
65106 { PALETTE_GROUPS . map ( ( group ) => {
66- const entries = Object . entries ( ObjectRegistry ) . filter (
67- ( [ , def ] ) => def . group === group . key ,
68- ) ;
107+ const entries = resolveEntries ( group . key , t . types as Record < string , string > ) ;
69108 if ( entries . length === 0 ) return null ;
70109 return (
71110 < CollapsibleSection
72111 key = { group . key }
73112 id = { `palette-${ group . key } ` }
74113 title = { t . palette [ group . labelKey ] }
75114 >
76- { entries . map ( ( [ type , def ] ) => (
77- < PaletteEntry key = { type } type = { type } def = { def } />
115+ { entries . map ( ( e ) => (
116+ < PaletteEntry
117+ key = { e . id }
118+ id = { e . id }
119+ type = { e . type }
120+ icon = { e . icon }
121+ label = { e . label }
122+ defaultSize = { e . defaultSize }
123+ propsOverride = { e . propsOverride }
124+ />
78125 ) ) }
79126 </ CollapsibleSection >
80127 ) ;
0 commit comments