1
+ import { clsx as cx } from 'clsx'
2
+ import { Show , createMemo , createSignal , onMount } from 'solid-js'
3
+ import { useDevtoolsOnClose } from './contexts'
4
+ import { useStyles } from './useStyles'
5
+ import { useLocalStorage } from './useLocalStorage'
6
+ import { multiSortBy } from './utils'
7
+ import type { Accessor , JSX } from 'solid-js'
8
+ import type { DbDevtoolsRegistry , CollectionMetadata } from './types'
9
+
10
+ export interface BaseDbDevtoolsPanelOptions {
11
+ /**
12
+ * The standard React style object used to style a component with inline styles
13
+ */
14
+ style ?: Accessor < JSX . CSSProperties >
15
+ /**
16
+ * The standard React class property used to style a component with classes
17
+ */
18
+ className ?: Accessor < string >
19
+ /**
20
+ * A boolean variable indicating whether the panel is open or closed
21
+ */
22
+ isOpen ?: boolean
23
+ /**
24
+ * A function that toggles the open and close state of the panel
25
+ */
26
+ setIsOpen ?: ( isOpen : boolean ) => void
27
+ /**
28
+ * Handles the opening and closing the devtools panel
29
+ */
30
+ handleDragStart ?: ( e : any ) => void
31
+ /**
32
+ * The DB devtools registry instance
33
+ */
34
+ registry : Accessor < DbDevtoolsRegistry >
35
+ /**
36
+ * Use this to attach the devtool's styles to specific element in the DOM.
37
+ */
38
+ shadowDOMTarget ?: ShadowRoot
39
+ }
40
+
41
+ function Logo ( props : any ) {
42
+ const { className, ...rest } = props
43
+ const styles = useStyles ( )
44
+ return (
45
+ < button { ...rest } class = { cx ( styles ( ) . logo , className ? className ( ) : '' ) } >
46
+ < div class = { styles ( ) . tanstackLogo } > TANSTACK</ div >
47
+ < div class = { styles ( ) . dbLogo } > TanStack DB v0</ div >
48
+ </ button >
49
+ )
50
+ }
51
+
52
+ function CollectionItem ( {
53
+ collection,
54
+ isActive,
55
+ onSelect,
56
+ } : {
57
+ collection : CollectionMetadata
58
+ isActive : boolean
59
+ onSelect : ( collection : CollectionMetadata ) => void
60
+ } ) {
61
+ const styles = useStyles ( )
62
+
63
+ return (
64
+ < div
65
+ class = { cx (
66
+ styles ( ) . collectionItem ,
67
+ isActive ? styles ( ) . collectionItemActive : ''
68
+ ) }
69
+ onClick = { ( ) => onSelect ( collection ) }
70
+ >
71
+ < div class = { styles ( ) . collectionName } > { collection . id } </ div >
72
+ < div class = { styles ( ) . collectionCount } > ({ collection . size } )</ div >
73
+ < div class = { cx (
74
+ styles ( ) . collectionStatus ,
75
+ collection . status === 'error' ? styles ( ) . collectionStatusError : ''
76
+ ) } >
77
+ { collection . status }
78
+ </ div >
79
+ </ div >
80
+ )
81
+ }
82
+
83
+ export const BaseTanStackDbDevtoolsPanel = function BaseTanStackDbDevtoolsPanel ( {
84
+ ...props
85
+ } : BaseDbDevtoolsPanelOptions ) : JSX . Element {
86
+ const {
87
+ isOpen = true ,
88
+ setIsOpen,
89
+ handleDragStart,
90
+ registry,
91
+ shadowDOMTarget,
92
+ ...panelProps
93
+ } = props
94
+
95
+ const { onCloseClick } = useDevtoolsOnClose ( )
96
+ const styles = useStyles ( )
97
+ const { className, style, ...otherPanelProps } = panelProps
98
+
99
+ const [ activeCollectionId , setActiveCollectionId ] = useLocalStorage (
100
+ 'tanstackDbDevtoolsActiveCollectionId' ,
101
+ '' ,
102
+ )
103
+
104
+ const [ collections , setCollections ] = createSignal < CollectionMetadata [ ] > ( [ ] )
105
+
106
+ // Poll for collections data
107
+ onMount ( ( ) => {
108
+ const updateCollections = ( ) => {
109
+ if ( typeof window === 'undefined' ) return
110
+
111
+ try {
112
+ const metadata = registry ( ) . getAllCollectionMetadata ( )
113
+ setCollections ( metadata )
114
+ } catch ( error ) {
115
+ console . warn ( 'Error fetching collections metadata:' , error )
116
+ }
117
+ }
118
+
119
+ updateCollections ( )
120
+ const intervalId = setInterval ( updateCollections , 1000 )
121
+
122
+ return ( ) => clearInterval ( intervalId )
123
+ } )
124
+
125
+ const activeCollection = createMemo ( ( ) => {
126
+ const active = collections ( ) . find ( c => c . id === activeCollectionId ( ) )
127
+ return active || collections ( ) [ 0 ]
128
+ } )
129
+
130
+ const sortedCollections = createMemo ( ( ) => {
131
+ return multiSortBy (
132
+ collections ( ) ,
133
+ [
134
+ ( c ) => c . status === 'error' ? 0 : 1 , // Errors first
135
+ ( c ) => c . id . toLowerCase ( ) , // Then alphabetically by ID
136
+ ]
137
+ )
138
+ } )
139
+
140
+ const collectionDetails = createMemo ( ( ) => {
141
+ const active = activeCollection ( )
142
+ if ( ! active ) return null
143
+
144
+ try {
145
+ const collection = registry ( ) . getCollection ( active . id )
146
+ const metadata = registry ( ) . getCollectionMetadata ( active . id )
147
+
148
+ return {
149
+ collection,
150
+ metadata,
151
+ transactions : registry ( ) . getTransactions ( active . id ) ,
152
+ }
153
+ } catch ( error ) {
154
+ console . warn ( 'Error getting collection details:' , error )
155
+ return null
156
+ }
157
+ } )
158
+
159
+ return (
160
+ < div
161
+ class = { cx (
162
+ styles ( ) . devtoolsPanel ,
163
+ 'TanStackDbDevtoolsPanel' ,
164
+ className ? className ( ) : '' ,
165
+ ) }
166
+ style = { style ? style ( ) : '' }
167
+ { ...otherPanelProps }
168
+ >
169
+ { handleDragStart ? (
170
+ < div class = { styles ( ) . dragHandle } onMouseDown = { handleDragStart } > </ div >
171
+ ) : null }
172
+
173
+ < button
174
+ class = { styles ( ) . panelCloseBtn }
175
+ onClick = { ( e : any ) => {
176
+ if ( setIsOpen ) {
177
+ setIsOpen ( false )
178
+ }
179
+ onCloseClick ( e )
180
+ } }
181
+ >
182
+ < svg
183
+ xmlns = "http://www.w3.org/2000/svg"
184
+ width = "10"
185
+ height = "6"
186
+ fill = "none"
187
+ viewBox = "0 0 10 6"
188
+ class = { styles ( ) . panelCloseBtnIcon }
189
+ >
190
+ < path
191
+ stroke = "currentColor"
192
+ stroke-linecap = "round"
193
+ stroke-linejoin = "round"
194
+ stroke-width = "1.667"
195
+ d = "M1 1l4 4 4-4"
196
+ > </ path >
197
+ </ svg >
198
+ </ button >
199
+
200
+ < div class = { styles ( ) . firstContainer } >
201
+ < div class = { styles ( ) . row } >
202
+ < Logo />
203
+ </ div >
204
+ < div class = { styles ( ) . collectionsExplorerContainer } >
205
+ < div class = { styles ( ) . collectionsExplorer } >
206
+ < div class = { styles ( ) . collectionsHeader } >
207
+ < div > Collections ({ collections ( ) . length } )</ div >
208
+ </ div >
209
+
210
+ < div class = { styles ( ) . collectionsList } >
211
+ < Show
212
+ when = { sortedCollections ( ) . length > 0 }
213
+ fallback = {
214
+ < div style = { { padding : '16px' , color : '#666' } } >
215
+ No collections found
216
+ </ div >
217
+ }
218
+ >
219
+ { sortedCollections ( ) . map ( ( collection ) => (
220
+ < CollectionItem
221
+ collection = { collection }
222
+ isActive = { collection . id === activeCollectionId ( ) }
223
+ onSelect = { ( c ) => setActiveCollectionId ( c . id ) }
224
+ />
225
+ ) ) }
226
+ </ Show >
227
+ </ div >
228
+ </ div >
229
+ </ div >
230
+ </ div >
231
+
232
+ < div class = { styles ( ) . secondContainer } >
233
+ < Show
234
+ when = { activeCollection ( ) }
235
+ fallback = {
236
+ < div class = { styles ( ) . detailsPanel } >
237
+ < div class = { styles ( ) . detailsHeader } >
238
+ Select a collection to view details
239
+ </ div >
240
+ </ div >
241
+ }
242
+ >
243
+ { ( collection ) => (
244
+ < div class = { styles ( ) . detailsPanel } >
245
+ < div class = { styles ( ) . detailsHeader } >
246
+ { collection ( ) . id }
247
+ </ div >
248
+ < div class = { styles ( ) . detailsContent } >
249
+ < pre > { JSON . stringify ( collection ( ) , null , 2 ) } </ pre >
250
+ </ div >
251
+ </ div >
252
+ ) }
253
+ </ Show >
254
+ </ div >
255
+ </ div >
256
+ )
257
+ }
258
+
259
+ export default BaseTanStackDbDevtoolsPanel
0 commit comments