Skip to content

Commit 4b6dd17

Browse files
committed
wip
1 parent d93a353 commit 4b6dd17

File tree

2 files changed

+204
-53
lines changed

2 files changed

+204
-53
lines changed

packages/db-devtools/src/BaseTanStackDbDevtoolsPanel.tsx

Lines changed: 148 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,67 @@ function Logo(props: any) {
5050
)
5151
}
5252

53+
function formatTime(ms: number): string {
54+
if (ms === 0) return '0s'
55+
56+
const units = ['s', 'min', 'h', 'd']
57+
const values = [ms / 1000, ms / 60000, ms / 3600000, ms / 86400000]
58+
59+
let chosenUnitIndex = 0
60+
for (let i = 1; i < values.length; i++) {
61+
if (values[i]! < 1) break
62+
chosenUnitIndex = i
63+
}
64+
65+
const formatter = new Intl.NumberFormat(navigator.language, {
66+
compactDisplay: 'short',
67+
notation: 'compact',
68+
maximumFractionDigits: 0,
69+
})
70+
71+
return formatter.format(values[chosenUnitIndex]!) + units[chosenUnitIndex]
72+
}
73+
74+
function CollectionStats({ collection }: { collection: CollectionMetadata }) {
75+
const styles = useStyles()
76+
77+
if (collection.type === 'collection') {
78+
// Standard collection stats
79+
return (
80+
<div class={styles().collectionStats}>
81+
<div>{collection.size}</div>
82+
<div>/</div>
83+
<div>{collection.transactionCount}</div>
84+
<div>/</div>
85+
<div>{formatTime(collection.gcTime || 0)}</div>
86+
<div>/</div>
87+
<div class={cx(
88+
styles().collectionStatus,
89+
collection.status === 'error' ? styles().collectionStatusError : ''
90+
)}>
91+
{collection.status}
92+
</div>
93+
</div>
94+
)
95+
} else {
96+
// Live query collection stats
97+
return (
98+
<div class={styles().collectionStats}>
99+
<div>{collection.size}</div>
100+
<div>/</div>
101+
<div>{formatTime(collection.gcTime || 0)}</div>
102+
<div>/</div>
103+
<div class={cx(
104+
styles().collectionStatus,
105+
collection.status === 'error' ? styles().collectionStatusError : ''
106+
)}>
107+
{collection.status}
108+
</div>
109+
</div>
110+
)
111+
}
112+
}
113+
53114
function CollectionItem({
54115
collection,
55116
isActive,
@@ -70,13 +131,7 @@ function CollectionItem({
70131
onClick={() => onSelect(collection)}
71132
>
72133
<div class={styles().collectionName}>{collection.id}</div>
73-
<div class={styles().collectionCount}>({collection.size})</div>
74-
<div class={cx(
75-
styles().collectionStatus,
76-
collection.status === 'error' ? styles().collectionStatusError : ''
77-
)}>
78-
{collection.status}
79-
</div>
134+
<CollectionStats collection={collection} />
80135
</div>
81136
)
82137
}
@@ -112,8 +167,13 @@ export const BaseTanStackDbDevtoolsPanel = function BaseTanStackDbDevtoolsPanel(
112167
const updateCollections = () => {
113168
if (typeof window === 'undefined') return
114169
try {
115-
const collections = registry().getAllCollectionMetadata()
116-
setCollections(collections)
170+
const newCollections = registry().getAllCollectionMetadata()
171+
const currentCollections = collections()
172+
173+
// Only update if collections data actually changed
174+
if (hasCollectionsChanged(currentCollections, newCollections)) {
175+
setCollections(newCollections)
176+
}
117177
} catch (error) {
118178
// Silently handle errors when fetching collections metadata
119179
}
@@ -179,6 +239,14 @@ export const BaseTanStackDbDevtoolsPanel = function BaseTanStackDbDevtoolsPanel(
179239
)
180240
})
181241

242+
// Group collections by type
243+
const standardCollections = createMemo(() =>
244+
sortedCollections().filter(c => c.type === 'collection')
245+
)
246+
247+
const liveCollections = createMemo(() =>
248+
sortedCollections().filter(c => c.type === 'live-query')
249+
)
182250

183251

184252
return (
@@ -224,39 +292,36 @@ export const BaseTanStackDbDevtoolsPanel = function BaseTanStackDbDevtoolsPanel(
224292

225293
<div class={styles().firstContainer}>
226294
<div class={styles().row}>
227-
<Logo />
295+
<div class={styles().headerContainer}>
296+
<Logo />
297+
{/* Tab Navigation */}
298+
<div class={styles().tabNav}>
299+
<button
300+
onClick={() => setSelectedView('collections')}
301+
class={cx(
302+
styles().tabBtn,
303+
selectedView() === 'collections' && styles().tabBtnActive
304+
)}
305+
>
306+
Collections ({collections().length})
307+
</button>
308+
<button
309+
onClick={() => setSelectedView('transactions')}
310+
class={cx(
311+
styles().tabBtn,
312+
selectedView() === 'transactions' && styles().tabBtnActive
313+
)}
314+
>
315+
Transactions ({registry().getTransactions().length})
316+
</button>
317+
</div>
318+
</div>
228319
</div>
229320
<div class={styles().collectionsExplorerContainer}>
230-
{/* Tab Navigation */}
231-
<div class={styles().tabNav}>
232-
<button
233-
onClick={() => setSelectedView('collections')}
234-
class={cx(
235-
styles().tabBtn,
236-
selectedView() === 'collections' && styles().tabBtnActive
237-
)}
238-
>
239-
Collections
240-
</button>
241-
<button
242-
onClick={() => setSelectedView('transactions')}
243-
class={cx(
244-
styles().tabBtn,
245-
selectedView() === 'transactions' && styles().tabBtnActive
246-
)}
247-
>
248-
Transactions ({registry().getTransactions().length})
249-
</button>
250-
</div>
251-
252321
{/* Content based on selected view */}
253322
<div class={styles().sidebarContent}>
254323
<Show when={selectedView() === 'collections'}>
255324
<div class={styles().collectionsExplorer}>
256-
<div class={styles().collectionsHeader}>
257-
<div>Collections ({collections().length})</div>
258-
</div>
259-
260325
<div class={styles().collectionsList}>
261326
<Show
262327
when={sortedCollections().length > 0}
@@ -266,13 +331,53 @@ export const BaseTanStackDbDevtoolsPanel = function BaseTanStackDbDevtoolsPanel(
266331
</div>
267332
}
268333
>
269-
<For each={sortedCollections()}>{(collection) =>
270-
<CollectionItem
271-
collection={collection}
272-
isActive={() => collection.id === activeCollectionId()}
273-
onSelect={(c) => setActiveCollectionId(c.id)}
274-
/>
275-
}</For>
334+
{/* Standard Collections */}
335+
<Show when={standardCollections().length > 0}>
336+
<div class={styles().collectionGroup}>
337+
<div class={styles().collectionGroupHeader}>
338+
<div>Standard Collections ({standardCollections().length})</div>
339+
<div class={styles().collectionGroupStats}>
340+
<span>Items</span>
341+
<span>/</span>
342+
<span>Txn</span>
343+
<span>/</span>
344+
<span>GC</span>
345+
<span>/</span>
346+
<span>Status</span>
347+
</div>
348+
</div>
349+
<For each={standardCollections()}>{(collection) =>
350+
<CollectionItem
351+
collection={collection}
352+
isActive={() => collection.id === activeCollectionId()}
353+
onSelect={(c) => setActiveCollectionId(c.id)}
354+
/>
355+
}</For>
356+
</div>
357+
</Show>
358+
359+
{/* Live Collections */}
360+
<Show when={liveCollections().length > 0}>
361+
<div class={styles().collectionGroup}>
362+
<div class={styles().collectionGroupHeader}>
363+
<div>Live Collections ({liveCollections().length})</div>
364+
<div class={styles().collectionGroupStats}>
365+
<span>Items</span>
366+
<span>/</span>
367+
<span>GC</span>
368+
<span>/</span>
369+
<span>Status</span>
370+
</div>
371+
</div>
372+
<For each={liveCollections()}>{(collection) =>
373+
<CollectionItem
374+
collection={collection}
375+
isActive={() => collection.id === activeCollectionId()}
376+
onSelect={(c) => setActiveCollectionId(c.id)}
377+
/>
378+
}</For>
379+
</div>
380+
</Show>
276381
</Show>
277382
</div>
278383
</div>

packages/db-devtools/src/useStyles.tsx

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
121121
}
122122
`,
123123
firstContainer: css`
124-
flex: 1 1 500px;
124+
flex: 0 0 35%;
125125
min-height: 40%;
126126
max-height: 100%;
127127
overflow: auto;
@@ -303,7 +303,7 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
303303
`,
304304
collectionStatus: css`
305305
font-size: ${fontSize.xs};
306-
padding: ${size[1]} ${size[2]};
306+
padding: ${size[0.5]} ${size[1]};
307307
border-radius: ${border.radius.sm};
308308
font-weight: ${font.weight.medium};
309309
background-color: ${colors.green[900]};
@@ -320,6 +320,15 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
320320
color: ${colors.gray[400]};
321321
margin-left: ${size[2]};
322322
`,
323+
collectionStats: css`
324+
display: flex;
325+
gap: ${size[1]};
326+
font-size: ${fontSize.xs};
327+
color: ${colors.gray[400]};
328+
font-variant-numeric: tabular-nums;
329+
line-height: ${font.lineHeight.xs};
330+
align-items: center;
331+
`,
323332
detailsPanel: css`
324333
display: flex;
325334
flex-direction: column;
@@ -354,41 +363,78 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
354363
align-items: center;
355364
padding: ${size[2]} ${size[2.5]};
356365
gap: ${size[2.5]};
357-
border-bottom: ${colors.darkGray[500]} 1px solid;
366+
border-bottom: ${colors.gray[700]} 1px solid;
358367
align-items: center;
359368
`,
369+
headerContainer: css`
370+
display: flex;
371+
align-items: center;
372+
justify-content: space-between;
373+
width: 100%;
374+
padding: ${size[0.5]} ${size[2]};
375+
`,
360376
collectionsExplorerContainer: css`
361377
overflow-y: auto;
362378
flex: 1;
363379
`,
364380
collectionsExplorer: css`
365-
padding: ${size[2]};
381+
/* Removed padding to use full width and height */
382+
`,
383+
collectionGroup: css`
384+
/* Removed margin to eliminate extra spacing */
385+
`,
386+
collectionGroupHeader: css`
387+
padding: ${size[1.5]} ${size[2]};
388+
font-size: ${fontSize.xs};
389+
font-weight: ${font.weight.semibold};
390+
color: ${colors.gray[400]};
391+
background: ${colors.darkGray[600]};
392+
border-bottom: 1px solid ${colors.gray[700]};
393+
text-transform: uppercase;
394+
letter-spacing: 0.5px;
395+
display: flex;
396+
justify-content: space-between;
397+
align-items: center;
398+
`,
399+
collectionGroupStats: css`
400+
display: flex;
401+
gap: ${size[1]};
402+
font-size: ${fontSize.xs};
403+
color: ${colors.gray[500]};
404+
font-variant-numeric: tabular-nums;
405+
line-height: ${font.lineHeight.xs};
406+
align-items: center;
407+
font-weight: ${font.weight.normal};
408+
text-transform: none;
409+
letter-spacing: normal;
366410
`,
367411
tabNav: css`
368412
display: flex;
369-
border-bottom: 1px solid ${colors.gray[700]};
370-
background: ${colors.darkGray[600]};
413+
gap: ${size[1]};
371414
`,
372415
tabBtn: css`
373-
flex: 1;
374-
padding: ${size[2]} ${size[3]};
416+
padding: ${size[1]} ${size[2]};
375417
background: transparent;
376-
border: none;
418+
border: 1px solid ${colors.gray[600]};
419+
border-radius: ${border.radius.sm};
377420
color: ${colors.gray[400]};
378421
cursor: pointer;
379-
font-size: ${fontSize.sm};
422+
font-size: ${fontSize.xs};
380423
font-weight: ${font.weight.medium};
381424
382425
&:hover {
383426
background: ${colors.darkGray[500]};
427+
border-color: ${colors.gray[500]};
384428
}
385429
`,
386430
tabBtnActive: css`
387431
background: ${colors.blue[500]};
388432
color: ${colors.white};
433+
border-color: ${colors.blue[400]};
389434
390435
&:hover {
391436
background: ${colors.blue[600]};
437+
border-color: ${colors.blue[500]};
392438
}
393439
`,
394440
sidebarContent: css`

0 commit comments

Comments
 (0)