@tanstack/query-devtools 5.0.0-alpha.31 → 5.0.0-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1485 +0,0 @@
1
- import { For } from 'solid-js';
2
- import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show, } from 'solid-js';
3
- import { rankItem } from '@tanstack/match-sorter-utils';
4
- import { css, cx } from '@emotion/css';
5
- import { tokens } from './theme';
6
- import { getQueryStatusLabel, getQueryStatusColor, displayValue, getQueryStatusColorByLabel, sortFns, convertRemToPixels, } from './utils';
7
- import { ArrowDown, ArrowUp, ChevronDown, Offline, Search, Settings, TanstackLogo, Wifi, } from './icons';
8
- import Explorer from './Explorer';
9
- import { QueryDevtoolsContext, useQueryDevtoolsContext } from './Context';
10
- import { TransitionGroup } from 'solid-transition-group';
11
- import { loadFonts } from './fonts';
12
- import { Key } from '@solid-primitives/keyed';
13
- import { createLocalStorage } from '@solid-primitives/storage';
14
- import { createResizeObserver } from '@solid-primitives/resize-observer';
15
- const firstBreakpoint = 1024;
16
- const secondBreakpoint = 796;
17
- const thirdBreakpoint = 700;
18
- const BUTTON_POSITION = 'bottom-right';
19
- const POSITION = 'bottom';
20
- const INITIAL_IS_OPEN = false;
21
- const DEFAULT_HEIGHT = 500;
22
- const DEFAULT_WIDTH = 500;
23
- const DEFAULT_SORT_FN_NAME = Object.keys(sortFns)[0];
24
- const DEFAULT_SORT_ORDER = 1;
25
- const [selectedQueryHash, setSelectedQueryHash] = createSignal(null);
26
- const [panelWidth, setPanelWidth] = createSignal(0);
27
- export const DevtoolsComponent = (props) => {
28
- return (<QueryDevtoolsContext.Provider value={props}>
29
- <Devtools />
30
- </QueryDevtoolsContext.Provider>);
31
- };
32
- export const Devtools = () => {
33
- loadFonts();
34
- const styles = getStyles();
35
- const [localStore, setLocalStore] = createLocalStorage({
36
- prefix: 'TanstackQueryDevtools',
37
- });
38
- const buttonPosition = createMemo(() => {
39
- return useQueryDevtoolsContext().buttonPosition || BUTTON_POSITION;
40
- });
41
- const isOpen = createMemo(() => {
42
- return localStore.open === 'true'
43
- ? true
44
- : localStore.open === 'false'
45
- ? false
46
- : useQueryDevtoolsContext().initialIsOpen || INITIAL_IS_OPEN;
47
- });
48
- const position = createMemo(() => {
49
- return localStore.position || useQueryDevtoolsContext().position || POSITION;
50
- });
51
- return (<div
52
- // styles for animating the panel in and out
53
- class={css `
54
- & .TSQD-panel-exit-active,
55
- & .TSQD-panel-enter-active {
56
- transition: opacity 0.3s, transform 0.3s;
57
- }
58
-
59
- & .TSQD-panel-exit-to,
60
- & .TSQD-panel-enter {
61
- ${position() === 'top'
62
- ? `transform: translateY(-${Number(localStore.height || DEFAULT_HEIGHT)}px);`
63
- : position() === 'left'
64
- ? `transform: translateX(-${Number(localStore.width || DEFAULT_WIDTH)}px);`
65
- : position() === 'right'
66
- ? `transform: translateX(${Number(localStore.width || DEFAULT_WIDTH)}px);`
67
- : `transform: translateY(${Number(localStore.height || DEFAULT_HEIGHT)}px);`}
68
- }
69
-
70
- & .TSQD-button-exit-active,
71
- & .TSQD-button-enter-active {
72
- transition: opacity 0.3s, transform 0.3s;
73
- }
74
-
75
- & .TSQD-button-exit-to,
76
- & .TSQD-button-enter {
77
- transform: ${buttonPosition() === 'top-left'
78
- ? `translateX(-72px);`
79
- : buttonPosition() === 'top-right'
80
- ? `translateX(72px);`
81
- : `translateY(72px);`};
82
- }
83
- `}>
84
- <TransitionGroup name="TSQD-panel">
85
- <Show when={isOpen()}>
86
- <DevtoolsPanel localStore={localStore} setLocalStore={setLocalStore}/>
87
- </Show>
88
- </TransitionGroup>
89
- <TransitionGroup name="TSQD-button">
90
- <Show when={!isOpen()}>
91
- <div class={cx(styles.devtoolsBtn, styles[`devtoolsBtn-position-${buttonPosition()}`])}>
92
- <div aria-hidden="true">
93
- <TanstackLogo />
94
- </div>
95
- <button aria-label="Open Tanstack query devtools" onClick={() => setLocalStore('open', 'true')}>
96
- <TanstackLogo />
97
- </button>
98
- </div>
99
- </Show>
100
- </TransitionGroup>
101
- </div>);
102
- };
103
- export const DevtoolsPanel = (props) => {
104
- const styles = getStyles();
105
- const [isResizing, setIsResizing] = createSignal(false);
106
- const sort = createMemo(() => props.localStore.sort || DEFAULT_SORT_FN_NAME);
107
- const sortOrder = createMemo(() => Number(props.localStore.sortOrder) || DEFAULT_SORT_ORDER);
108
- const [offline, setOffline] = createSignal(false);
109
- const [settingsOpen, setSettingsOpen] = createSignal(false);
110
- const position = createMemo(() => (props.localStore.position ||
111
- useQueryDevtoolsContext().position ||
112
- POSITION));
113
- const sortFn = createMemo(() => sortFns[sort()]);
114
- const onlineManager = createMemo(() => useQueryDevtoolsContext().onlineManager);
115
- const cache = createMemo(() => {
116
- return useQueryDevtoolsContext().client.getQueryCache();
117
- });
118
- const queryCount = createSubscribeToQueryCacheBatcher((queryCache) => {
119
- return queryCache().getAll().length;
120
- }, false);
121
- const queries = createMemo(on(() => [queryCount(), props.localStore.filter, sort(), sortOrder()], () => {
122
- const curr = cache().getAll();
123
- const filtered = props.localStore.filter
124
- ? curr.filter((item) => rankItem(item.queryHash, props.localStore.filter || '').passed)
125
- : [...curr];
126
- const sorted = sortFn()
127
- ? filtered.sort((a, b) => sortFn()(a, b) * sortOrder())
128
- : filtered;
129
- return sorted;
130
- }));
131
- const handleDragStart = (event) => {
132
- const panelElement = event.currentTarget.parentElement;
133
- if (!panelElement)
134
- return;
135
- setIsResizing(true);
136
- const { height, width } = panelElement.getBoundingClientRect();
137
- const startX = event.clientX;
138
- const startY = event.clientY;
139
- let newSize = 0;
140
- const minHeight = convertRemToPixels(3.5);
141
- const minWidth = convertRemToPixels(12);
142
- const runDrag = (moveEvent) => {
143
- moveEvent.preventDefault();
144
- if (position() === 'left' || position() === 'right') {
145
- const valToAdd = position() === 'right'
146
- ? startX - moveEvent.clientX
147
- : moveEvent.clientX - startX;
148
- newSize = Math.round(width + valToAdd);
149
- if (newSize < minWidth) {
150
- newSize = minWidth;
151
- }
152
- props.setLocalStore('width', String(Math.round(newSize)));
153
- const newWidth = panelElement.getBoundingClientRect().width;
154
- // If the panel size didn't decrease, this means we have reached the minimum width
155
- // of the panel so we restore the original width in local storage
156
- // Restoring the width helps in smooth open/close transitions
157
- if (Number(props.localStore.width) < newWidth) {
158
- props.setLocalStore('width', String(newWidth));
159
- }
160
- }
161
- else {
162
- const valToAdd = position() === 'bottom'
163
- ? startY - moveEvent.clientY
164
- : moveEvent.clientY - startY;
165
- newSize = Math.round(height + valToAdd);
166
- // If the panel size is less than the minimum height,
167
- // we set the size to the minimum height
168
- if (newSize < minHeight) {
169
- newSize = minHeight;
170
- setSelectedQueryHash(null);
171
- }
172
- props.setLocalStore('height', String(Math.round(newSize)));
173
- }
174
- };
175
- const unsub = () => {
176
- if (isResizing()) {
177
- setIsResizing(false);
178
- }
179
- document.removeEventListener('mousemove', runDrag, false);
180
- document.removeEventListener('mouseUp', unsub, false);
181
- };
182
- document.addEventListener('mousemove', runDrag, false);
183
- document.addEventListener('mouseup', unsub, false);
184
- };
185
- setupQueryCacheSubscription();
186
- let queriesContainerRef;
187
- let panelRef;
188
- onMount(() => {
189
- createResizeObserver(panelRef, ({ width }, el) => {
190
- if (el === panelRef) {
191
- setPanelWidth(width);
192
- }
193
- });
194
- });
195
- const setDevtoolsPosition = (pos) => {
196
- props.setLocalStore('position', pos);
197
- setSettingsOpen(false);
198
- };
199
- return (<aside
200
- // Some context for styles here
201
- // background-color - Changes to a lighter color create a harder contrast
202
- // between the queries and query detail panel
203
- // -
204
- // min-width - When the panel is in the left or right position, the panel
205
- // width is set to min-content to allow the panel to shrink to the lowest possible width
206
- class={`${styles.panel} ${styles[`panel-position-${position()}`]} ${css `
207
- flex-direction: ${panelWidth() < secondBreakpoint ? 'column' : 'row'};
208
- background-color: ${panelWidth() < secondBreakpoint
209
- ? tokens.colors.gray[600]
210
- : tokens.colors.darkGray[900]};
211
- ${panelWidth() < thirdBreakpoint &&
212
- (position() === 'right' || position() === 'left')
213
- ? `
214
- min-width: min-content;
215
- `
216
- : ''}
217
- `}`} style={{
218
- height: position() === 'bottom' || position() === 'top'
219
- ? `${props.localStore.height || DEFAULT_HEIGHT}px`
220
- : 'auto',
221
- width: position() === 'right' || position() === 'left'
222
- ? `${props.localStore.width || DEFAULT_WIDTH}px`
223
- : 'auto',
224
- }} ref={panelRef} aria-label="Tanstack query devtools">
225
- <div class={cx(styles.dragHandle, styles[`dragHandle-position-${position()}`])} onMouseDown={handleDragStart}></div>
226
- <button aria-label="Close tanstack query devtools" class={cx(styles.closeBtn, styles[`closeBtn-position-${position()}`])} onClick={() => props.setLocalStore('open', 'false')}>
227
- <ChevronDown />
228
- </button>
229
- <div ref={queriesContainerRef}
230
- // When the panels are stacked we use the height style
231
- // to divide the panels into two equal parts
232
- class={`${styles.queriesContainer} ${css `
233
- ${panelWidth() < secondBreakpoint && selectedQueryHash()
234
- ? `
235
- height: 50%;
236
- max-height: 50%;
237
- `
238
- : ''}
239
- `}`}>
240
- <div class={cx(styles.row)}>
241
- <button class={styles.logo} onClick={() => props.setLocalStore('open', 'false')} aria-label="Close Tanstack query devtools">
242
- <span class={styles.tanstackLogo}>TANSTACK</span>
243
- <span class={styles.queryFlavorLogo}>
244
- {useQueryDevtoolsContext().queryFlavor} v
245
- {useQueryDevtoolsContext().version}
246
- </span>
247
- </button>
248
- <QueryStatusCount />
249
- </div>
250
- <div class={cx(styles.row, css `
251
- gap: ${tokens.size[2.5]};
252
- `)}>
253
- <div class={styles.filtersContainer}>
254
- <div class={styles.filterInput}>
255
- <Search />
256
- <input aria-label="Filter queries by query key" type="text" placeholder="Filter" onInput={(e) => props.setLocalStore('filter', e.currentTarget.value)} value={props.localStore.filter || ''}/>
257
- </div>
258
- <div class={styles.filterSelect}>
259
- <select value={sort()} onChange={(e) => props.setLocalStore('sort', e.currentTarget.value)}>
260
- {Object.keys(sortFns).map((key) => (<option value={key}>Sort by {key}</option>))}
261
- </select>
262
- <ChevronDown />
263
- </div>
264
- <button onClick={() => {
265
- props.setLocalStore('sortOrder', String(sortOrder() * -1));
266
- }} aria-label={`Sort order ${sortOrder() === -1 ? 'descending' : 'ascending'}`} aria-pressed={sortOrder() === -1}>
267
- <Show when={sortOrder() === 1}>
268
- <span>Asc</span>
269
- <ArrowUp />
270
- </Show>
271
- <Show when={sortOrder() === -1}>
272
- <span>Desc</span>
273
- <ArrowDown />
274
- </Show>
275
- </button>
276
- </div>
277
-
278
- <div class={styles.actionsContainer}>
279
- <button onClick={() => {
280
- if (offline()) {
281
- onlineManager().setOnline(undefined);
282
- setOffline(false);
283
- window.dispatchEvent(new Event('online'));
284
- }
285
- else {
286
- onlineManager().setOnline(false);
287
- setOffline(true);
288
- }
289
- }} class={styles.actionsBtn} aria-label={`${offline()
290
- ? 'Unset offline mocking behavior'
291
- : 'Mock offline behavior'}`} aria-pressed={offline()}>
292
- {offline() ? <Offline /> : <Wifi />}
293
- </button>
294
- <div style={{ position: 'relative' }}>
295
- <button onClick={() => setSettingsOpen((prev) => !prev)} class={styles.actionsBtn} id="TSQD-settings-menu-btn" aria-label={`${settingsOpen() ? 'Close' : 'Open'} settings menu`} aria-haspopup="true" aria-controls="TSQD-settings-menu">
296
- <Settings />
297
- </button>
298
- <Show when={settingsOpen()}>
299
- <div role="menu" tabindex="-1" aria-labelledby="TSQD-settings-menu-btn" id="TSQD-settings-menu" class={styles.settingsMenu}>
300
- <div class={styles.settingsMenuHeader}>Position</div>
301
- <div class={styles.settingsMenuSection}>
302
- <button onClick={() => {
303
- setDevtoolsPosition('top');
304
- }} aria-label="Position top">
305
- <ArrowUp />
306
- <span>Top</span>
307
- </button>
308
- <button onClick={() => {
309
- setDevtoolsPosition('bottom');
310
- }} aria-label="Position bottom">
311
- <ArrowDown />
312
- <span>Bottom</span>
313
- </button>
314
- <button onClick={() => {
315
- setDevtoolsPosition('left');
316
- }} aria-label="Position left">
317
- <ArrowDown />
318
- <span>Left</span>
319
- </button>
320
- <button onClick={() => {
321
- setDevtoolsPosition('right');
322
- }} aria-label="Position right">
323
- <ArrowDown />
324
- <span>Right</span>
325
- </button>
326
- </div>
327
- </div>
328
- </Show>
329
- </div>
330
- </div>
331
- </div>
332
- <div class={styles.overflowQueryContainer}>
333
- <div>
334
- <Key by={(q) => q.queryHash} each={queries()}>
335
- {(query) => <QueryRow query={query()}/>}
336
- </Key>
337
- </div>
338
- </div>
339
- </div>
340
- <Show when={selectedQueryHash()}>
341
- <QueryDetails />
342
- </Show>
343
- </aside>);
344
- };
345
- export const QueryRow = (props) => {
346
- const styles = getStyles();
347
- const queryState = createSubscribeToQueryCacheBatcher((queryCache) => queryCache().find({
348
- queryKey: props.query.queryKey,
349
- })?.state);
350
- const isDisabled = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
351
- .find({
352
- queryKey: props.query.queryKey,
353
- })
354
- ?.isDisabled() ?? false);
355
- const isStale = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
356
- .find({
357
- queryKey: props.query.queryKey,
358
- })
359
- ?.isStale() ?? false);
360
- const observers = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
361
- .find({
362
- queryKey: props.query.queryKey,
363
- })
364
- ?.getObserversCount() ?? 0);
365
- const color = createMemo(() => getQueryStatusColor({
366
- queryState: queryState(),
367
- observerCount: observers(),
368
- isStale: isStale(),
369
- }));
370
- return (<Show when={queryState()}>
371
- <button onClick={() => setSelectedQueryHash(props.query.queryHash === selectedQueryHash()
372
- ? null
373
- : props.query.queryHash)} class={cx(styles.queryRow, selectedQueryHash() === props.query.queryHash &&
374
- styles.selectedQueryRow)} aria-label={`Query key ${props.query.queryHash}`}>
375
- <div class={cx('TSQDObserverCount', color() === 'gray'
376
- ? css `
377
- background-color: ${tokens.colors[color()][700]};
378
- color: ${tokens.colors[color()][300]};
379
- `
380
- : css `
381
- background-color: ${tokens.colors[color()][900]};
382
- color: ${tokens.colors[color()][300]} !important;
383
- `)}>
384
- {observers()}
385
- </div>
386
- <code class="TSQDQueryHash">{props.query.queryHash}</code>
387
- <Show when={isDisabled()}>
388
- <div class="TSQDQueryDisabled">disabled</div>
389
- </Show>
390
- </button>
391
- </Show>);
392
- };
393
- export const QueryStatusCount = () => {
394
- const stale = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
395
- .getAll()
396
- .filter((q) => getQueryStatusLabel(q) === 'stale').length);
397
- const fresh = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
398
- .getAll()
399
- .filter((q) => getQueryStatusLabel(q) === 'fresh').length);
400
- const fetching = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
401
- .getAll()
402
- .filter((q) => getQueryStatusLabel(q) === 'fetching').length);
403
- const paused = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
404
- .getAll()
405
- .filter((q) => getQueryStatusLabel(q) === 'paused').length);
406
- const inactive = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
407
- .getAll()
408
- .filter((q) => getQueryStatusLabel(q) === 'inactive').length);
409
- const styles = getStyles();
410
- return (<div class={styles.queryStatusContainer}>
411
- <QueryStatus label="Fresh" color="green" count={fresh()}/>
412
- <QueryStatus label="Fetching" color="blue" count={fetching()}/>
413
- <QueryStatus label="Paused" color="purple" count={paused()}/>
414
- <QueryStatus label="Stale" color="yellow" count={stale()}/>
415
- <QueryStatus label="Inactive" color="gray" count={inactive()}/>
416
- </div>);
417
- };
418
- export const QueryStatus = (props) => {
419
- const styles = getStyles();
420
- let tagRef;
421
- const [mouseOver, setMouseOver] = createSignal(false);
422
- const [focused, setFocused] = createSignal(false);
423
- const showLabel = createMemo(() => {
424
- if (selectedQueryHash()) {
425
- if (panelWidth() < firstBreakpoint && panelWidth() > secondBreakpoint) {
426
- return false;
427
- }
428
- }
429
- if (panelWidth() < thirdBreakpoint) {
430
- return false;
431
- }
432
- return true;
433
- });
434
- return (<button onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} onMouseEnter={() => setMouseOver(true)} onMouseLeave={() => {
435
- setMouseOver(false);
436
- setFocused(false);
437
- }} disabled={showLabel()} ref={tagRef} class={cx(styles.queryStatusTag, !showLabel()
438
- ? css `
439
- cursor: pointer;
440
- &:hover {
441
- background: ${tokens.colors.darkGray[400]}${tokens.alpha[80]};
442
- }
443
- `
444
- : null)} {...(mouseOver() || focused()
445
- ? {
446
- 'aria-describedby': 'TSQD-status-tooltip',
447
- }
448
- : {})}>
449
- <Show when={!showLabel() && (mouseOver() || focused())}>
450
- <div role="tooltip" id="TSQD-status-tooltip" class={cx(styles.statusTooltip)}>
451
- {props.label}
452
- </div>
453
- </Show>
454
- <span class={css `
455
- width: ${tokens.size[2]};
456
- height: ${tokens.size[2]};
457
- border-radius: ${tokens.border.radius.full};
458
- background-color: ${tokens.colors[props.color][500]};
459
- `}/>
460
- <Show when={showLabel()}>
461
- <span>{props.label}</span>
462
- </Show>
463
- <span class={cx(styles.queryStatusCount, props.count > 0 && props.color !== 'gray'
464
- ? css `
465
- background-color: ${tokens.colors[props.color][900]};
466
- color: ${tokens.colors[props.color][300]} !important;
467
- `
468
- : css `
469
- color: ${tokens.colors['gray'][400]} !important;
470
- `)}>
471
- {props.count}
472
- </span>
473
- </button>);
474
- };
475
- const QueryDetails = () => {
476
- const styles = getStyles();
477
- const queryClient = useQueryDevtoolsContext().client;
478
- const [restoringLoading, setRestoringLoading] = createSignal(false);
479
- const errorTypes = createMemo(() => {
480
- return useQueryDevtoolsContext().errorTypes || [];
481
- });
482
- const activeQuery = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
483
- .getAll()
484
- .find((query) => query.queryHash === selectedQueryHash()), false);
485
- const activeQueryFresh = createSubscribeToQueryCacheBatcher((queryCache) => {
486
- return queryCache()
487
- .getAll()
488
- .find((query) => query.queryHash === selectedQueryHash());
489
- }, false);
490
- const activeQueryState = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
491
- .getAll()
492
- .find((query) => query.queryHash === selectedQueryHash())?.state, false);
493
- const activeQueryStateData = createSubscribeToQueryCacheBatcher((queryCache) => {
494
- return queryCache()
495
- .getAll()
496
- .find((query) => query.queryHash === selectedQueryHash())?.state.data;
497
- }, false);
498
- const statusLabel = createSubscribeToQueryCacheBatcher((queryCache) => {
499
- const query = queryCache()
500
- .getAll()
501
- .find((q) => q.queryHash === selectedQueryHash());
502
- if (!query)
503
- return 'inactive';
504
- return getQueryStatusLabel(query);
505
- });
506
- const queryStatus = createSubscribeToQueryCacheBatcher((queryCache) => {
507
- const query = queryCache()
508
- .getAll()
509
- .find((q) => q.queryHash === selectedQueryHash());
510
- if (!query)
511
- return 'pending';
512
- return query.state.status;
513
- });
514
- const observerCount = createSubscribeToQueryCacheBatcher((queryCache) => queryCache()
515
- .getAll()
516
- .find((query) => query.queryHash === selectedQueryHash())
517
- ?.getObserversCount() ?? 0);
518
- const color = createMemo(() => getQueryStatusColorByLabel(statusLabel()));
519
- const handleRefetch = () => {
520
- const promise = activeQuery()?.fetch();
521
- // eslint-disable-next-line @typescript-eslint/no-empty-function
522
- promise?.catch(() => { });
523
- };
524
- const triggerError = (errorType) => {
525
- const error = errorType?.initializer(activeQuery()) ??
526
- new Error('Unknown error from devtools');
527
- const __previousQueryOptions = activeQuery().options;
528
- activeQuery().setState({
529
- status: 'error',
530
- error,
531
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
532
- fetchMeta: {
533
- ...activeQuery().state.fetchMeta,
534
- __previousQueryOptions,
535
- },
536
- });
537
- };
538
- const restoreQueryAfterLoadingOrError = () => {
539
- activeQuery()?.fetch(activeQuery()?.state.fetchMeta.__previousQueryOptions, {
540
- // Make sure this fetch will cancel the previous one
541
- cancelRefetch: true,
542
- });
543
- };
544
- createEffect(() => {
545
- if (statusLabel() !== 'fetching') {
546
- setRestoringLoading(false);
547
- }
548
- });
549
- return (<Show when={activeQuery() && activeQueryState()}>
550
- <div class={styles.detailsContainer}>
551
- <div class={styles.detailsHeader}>Query Details</div>
552
- <div class={styles.detailsBody}>
553
- <div>
554
- <pre>
555
- <code>{displayValue(activeQuery().queryKey, true)}</code>
556
- </pre>
557
- <span class={cx(styles.queryDetailsStatus, color() === 'gray'
558
- ? css `
559
- background-color: ${tokens.colors[color()][700]};
560
- color: ${tokens.colors[color()][300]};
561
- border-color: ${tokens.colors[color()][600]};
562
- `
563
- : css `
564
- background-color: ${tokens.colors[color()][900]};
565
- color: ${tokens.colors[color()][300]};
566
- border-color: ${tokens.colors[color()][600]};
567
- `)}>
568
- {statusLabel()}
569
- </span>
570
- </div>
571
- <div>
572
- <span>Observers:</span>
573
- <span>{observerCount()}</span>
574
- </div>
575
- <div>
576
- <span>Last Updated:</span>
577
- <span>
578
- {new Date(activeQueryState().dataUpdatedAt).toLocaleTimeString()}
579
- </span>
580
- </div>
581
- </div>
582
- <div class={styles.detailsHeader}>Actions</div>
583
- <div class={styles.actionsBody}>
584
- <button class={css `
585
- color: ${tokens.colors.blue[400]};
586
- `} onClick={handleRefetch} disabled={statusLabel() === 'fetching'}>
587
- <span class={css `
588
- background-color: ${tokens.colors.blue[400]};
589
- `}></span>
590
- Refetch
591
- </button>
592
- <button class={css `
593
- color: ${tokens.colors.yellow[400]};
594
- `} onClick={() => queryClient.invalidateQueries(activeQuery())}>
595
- <span class={css `
596
- background-color: ${tokens.colors.yellow[400]};
597
- `}></span>
598
- Invalidate
599
- </button>
600
- <button class={css `
601
- color: ${tokens.colors.gray[300]};
602
- `} onClick={() => queryClient.resetQueries(activeQuery())}>
603
- <span class={css `
604
- background-color: ${tokens.colors.gray[400]};
605
- `}></span>
606
- Reset
607
- </button>
608
- <button class={css `
609
- color: ${tokens.colors.cyan[400]};
610
- `} disabled={restoringLoading()} onClick={() => {
611
- if (activeQuery()?.state.data === undefined) {
612
- setRestoringLoading(true);
613
- restoreQueryAfterLoadingOrError();
614
- }
615
- else {
616
- const activeQueryVal = activeQuery();
617
- if (!activeQueryVal)
618
- return;
619
- const __previousQueryOptions = activeQueryVal.options;
620
- // Trigger a fetch in order to trigger suspense as well.
621
- activeQueryVal.fetch({
622
- ...__previousQueryOptions,
623
- queryFn: () => {
624
- return new Promise(() => {
625
- // Never resolve
626
- });
627
- },
628
- gcTime: -1,
629
- });
630
- activeQueryVal.setState({
631
- data: undefined,
632
- status: 'pending',
633
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
634
- fetchMeta: {
635
- ...activeQueryVal.state.fetchMeta,
636
- __previousQueryOptions,
637
- },
638
- });
639
- }
640
- }}>
641
- <span class={css `
642
- background-color: ${tokens.colors.cyan[400]};
643
- `}></span>
644
- {statusLabel() === 'fetching' ? 'Restore' : 'Trigger'} Loading
645
- </button>
646
- <Show when={errorTypes().length === 0 || queryStatus() === 'error'}>
647
- <button class={css `
648
- color: ${tokens.colors.red[400]};
649
- `} onClick={() => {
650
- if (!activeQuery().state.error) {
651
- triggerError();
652
- }
653
- else {
654
- queryClient.resetQueries(activeQuery());
655
- }
656
- }}>
657
- <span class={css `
658
- background-color: ${tokens.colors.red[400]};
659
- `}></span>
660
- {queryStatus() === 'error' ? 'Restore' : 'Trigger'} Error
661
- </button>
662
- </Show>
663
- <Show when={!(errorTypes().length === 0 || queryStatus() === 'error')}>
664
- <div class={styles.actionsSelect}>
665
- <span class={css `
666
- background-color: ${tokens.colors.red[400]};
667
- `}></span>
668
- Trigger Error
669
- <select disabled={queryStatus() === 'pending'} onChange={(e) => {
670
- const errorType = errorTypes().find((t) => t.name === e.currentTarget.value);
671
- triggerError(errorType);
672
- }}>
673
- <option value="" disabled selected></option>
674
- <For each={errorTypes()}>
675
- {(errorType) => (<option value={errorType.name}>{errorType.name}</option>)}
676
- </For>
677
- </select>
678
- <ChevronDown />
679
- </div>
680
- </Show>
681
- </div>
682
- <div class={styles.detailsHeader}>Data Explorer</div>
683
- <div style={{
684
- padding: '0.5rem',
685
- }}>
686
- <Explorer label="Data" defaultExpanded={['Data']} value={activeQueryStateData()} copyable={true}/>
687
- </div>
688
- <div class={styles.detailsHeader}>Query Explorer</div>
689
- <div style={{
690
- padding: '0.5rem',
691
- }}>
692
- <Explorer label="Query" defaultExpanded={['Query', 'queryKey']} value={activeQueryFresh()}/>
693
- </div>
694
- </div>
695
- </Show>);
696
- };
697
- const signalsMap = new Map();
698
- const setupQueryCacheSubscription = () => {
699
- const queryCache = createMemo(() => {
700
- const client = useQueryDevtoolsContext().client;
701
- return client.getQueryCache();
702
- });
703
- const unsub = queryCache().subscribe(() => {
704
- for (const [callback, setter] of signalsMap.entries()) {
705
- queueMicrotask(() => {
706
- setter(callback(queryCache));
707
- });
708
- }
709
- });
710
- onCleanup(() => {
711
- signalsMap.clear();
712
- unsub();
713
- });
714
- return unsub;
715
- };
716
- const createSubscribeToQueryCacheBatcher = (callback, equalityCheck = true) => {
717
- const queryCache = createMemo(() => {
718
- const client = useQueryDevtoolsContext().client;
719
- return client.getQueryCache();
720
- });
721
- const [value, setValue] = createSignal(callback(queryCache), !equalityCheck ? { equals: false } : undefined);
722
- createEffect(() => {
723
- setValue(callback(queryCache));
724
- });
725
- // @ts-ignore
726
- signalsMap.set(callback, setValue);
727
- onCleanup(() => {
728
- // @ts-ignore
729
- signalsMap.delete(callback);
730
- });
731
- return value;
732
- };
733
- const getStyles = () => {
734
- const { colors, font, size, alpha, shadow, border } = tokens;
735
- return {
736
- devtoolsBtn: css `
737
- z-index: 100000;
738
- position: fixed;
739
- padding: 4px;
740
-
741
- display: flex;
742
- align-items: center;
743
- justify-content: center;
744
- border-radius: 9999px;
745
- box-shadow: ${shadow.md()};
746
- overflow: hidden;
747
-
748
- & div {
749
- position: absolute;
750
- top: -8px;
751
- left: -8px;
752
- right: -8px;
753
- bottom: -8px;
754
- border-radius: 9999px;
755
-
756
- & svg {
757
- position: absolute;
758
- width: 100%;
759
- height: 100%;
760
- }
761
- filter: blur(6px) saturate(1.2) contrast(1.1);
762
- }
763
-
764
- &:focus-within {
765
- outline-offset: 2px;
766
- outline: 3px solid ${colors.green[600]};
767
- }
768
-
769
- & button {
770
- position: relative;
771
- z-index: 1;
772
- padding: 0;
773
- border-radius: 9999px;
774
- background-color: transparent;
775
- border: none;
776
- height: 40px;
777
- display: flex;
778
- width: 40px;
779
- overflow: hidden;
780
- cursor: pointer;
781
- outline: none;
782
- & svg {
783
- position: absolute;
784
- width: 100%;
785
- height: 100%;
786
- }
787
- }
788
- `,
789
- panel: css `
790
- position: fixed;
791
- z-index: 9999;
792
- display: flex;
793
- gap: ${tokens.size[0.5]};
794
- & * {
795
- font-family: 'Inter', sans-serif;
796
- color: ${colors.gray[300]};
797
- box-sizing: border-box;
798
- }
799
- `,
800
- 'devtoolsBtn-position-bottom-right': css `
801
- bottom: 12px;
802
- right: 12px;
803
- `,
804
- 'devtoolsBtn-position-bottom-left': css `
805
- bottom: 12px;
806
- left: 12px;
807
- `,
808
- 'devtoolsBtn-position-top-left': css `
809
- top: 12px;
810
- left: 12px;
811
- `,
812
- 'devtoolsBtn-position-top-right': css `
813
- top: 12px;
814
- right: 12px;
815
- `,
816
- 'panel-position-top': css `
817
- top: 0;
818
- right: 0;
819
- left: 0;
820
- max-height: 90%;
821
- min-height: 3.5rem;
822
- border-bottom: ${colors.darkGray[300]} 1px solid;
823
- `,
824
- 'panel-position-bottom': css `
825
- bottom: 0;
826
- right: 0;
827
- left: 0;
828
- max-height: 90%;
829
- min-height: 3.5rem;
830
- border-top: ${colors.darkGray[300]} 1px solid;
831
- `,
832
- 'panel-position-right': css `
833
- bottom: 0;
834
- right: 0;
835
- top: 0;
836
- border-left: ${colors.darkGray[300]} 1px solid;
837
- max-width: 90%;
838
- `,
839
- 'panel-position-left': css `
840
- bottom: 0;
841
- left: 0;
842
- top: 0;
843
- border-right: ${colors.darkGray[300]} 1px solid;
844
- max-width: 90%;
845
- `,
846
- closeBtn: css `
847
- position: absolute;
848
- cursor: pointer;
849
- z-index: 5;
850
- display: flex;
851
- align-items: center;
852
- justify-content: center;
853
- outline: none;
854
- &:hover {
855
- background-color: ${colors.darkGray[500]};
856
- }
857
- &:focus-visible {
858
- outline: 2px solid ${colors.blue[600]};
859
- }
860
- `,
861
- 'closeBtn-position-top': css `
862
- bottom: 0;
863
- right: ${size[3]};
864
- transform: translate(0, 100%);
865
- background-color: ${colors.darkGray[700]};
866
- border-right: ${colors.darkGray[300]} 1px solid;
867
- border-left: ${colors.darkGray[300]} 1px solid;
868
- border-top: none;
869
- border-bottom: ${colors.darkGray[300]} 1px solid;
870
- border-radius: 0px 0px ${border.radius.sm} ${border.radius.sm};
871
- padding: ${size[1.5]} ${size[2.5]} ${size[2]} ${size[2.5]};
872
-
873
- &::after {
874
- content: ' ';
875
- position: absolute;
876
- bottom: 100%;
877
- left: -${size[2.5]};
878
- height: ${size[1.5]};
879
- width: calc(100% + ${size[5]});
880
- }
881
-
882
- & svg {
883
- transform: rotate(180deg);
884
- }
885
- `,
886
- 'closeBtn-position-bottom': css `
887
- top: 0;
888
- right: ${size[3]};
889
- transform: translate(0, -100%);
890
- background-color: ${colors.darkGray[700]};
891
- border-right: ${colors.darkGray[300]} 1px solid;
892
- border-left: ${colors.darkGray[300]} 1px solid;
893
- border-top: ${colors.darkGray[300]} 1px solid;
894
- border-bottom: none;
895
- border-radius: ${border.radius.sm} ${border.radius.sm} 0px 0px;
896
- padding: ${size[2]} ${size[2.5]} ${size[1.5]} ${size[2.5]};
897
-
898
- &::after {
899
- content: ' ';
900
- position: absolute;
901
- top: 100%;
902
- left: -${size[2.5]};
903
- height: ${size[1.5]};
904
- width: calc(100% + ${size[5]});
905
- }
906
- `,
907
- 'closeBtn-position-right': css `
908
- bottom: ${size[3]};
909
- left: 0;
910
- transform: translate(-100%, 0);
911
- background-color: ${colors.darkGray[700]};
912
- border-right: none;
913
- border-left: ${colors.darkGray[300]} 1px solid;
914
- border-top: ${colors.darkGray[300]} 1px solid;
915
- border-bottom: ${colors.darkGray[300]} 1px solid;
916
- border-radius: ${border.radius.sm} 0px 0px ${border.radius.sm};
917
- padding: ${size[2.5]} ${size[1]} ${size[2.5]} ${size[1.5]};
918
-
919
- &::after {
920
- content: ' ';
921
- position: absolute;
922
- left: 100%;
923
- height: calc(100% + ${size[5]});
924
- width: ${size[1.5]};
925
- }
926
-
927
- & svg {
928
- transform: rotate(-90deg);
929
- }
930
- `,
931
- 'closeBtn-position-left': css `
932
- bottom: ${size[3]};
933
- right: 0;
934
- transform: translate(100%, 0);
935
- background-color: ${colors.darkGray[700]};
936
- border-left: none;
937
- border-right: ${colors.darkGray[300]} 1px solid;
938
- border-top: ${colors.darkGray[300]} 1px solid;
939
- border-bottom: ${colors.darkGray[300]} 1px solid;
940
- border-radius: 0px ${border.radius.sm} ${border.radius.sm} 0px;
941
- padding: ${size[2.5]} ${size[1.5]} ${size[2.5]} ${size[1]};
942
-
943
- &::after {
944
- content: ' ';
945
- position: absolute;
946
- right: 100%;
947
- height: calc(100% + ${size[5]});
948
- width: ${size[1.5]};
949
- }
950
-
951
- & svg {
952
- transform: rotate(90deg);
953
- }
954
- `,
955
- queriesContainer: css `
956
- flex: 1 1 700px;
957
- background-color: ${colors.darkGray[700]};
958
- display: flex;
959
- flex-direction: column;
960
- `,
961
- dragHandle: css `
962
- position: absolute;
963
- transition: background-color 0.125s ease;
964
- &:hover {
965
- background-color: ${colors.gray[400]}${alpha[90]};
966
- }
967
- z-index: 4;
968
- `,
969
- 'dragHandle-position-top': css `
970
- bottom: 0;
971
- width: 100%;
972
- height: ${tokens.size[1]};
973
- cursor: ns-resize;
974
- `,
975
- 'dragHandle-position-bottom': css `
976
- top: 0;
977
- width: 100%;
978
- height: ${tokens.size[1]};
979
- cursor: ns-resize;
980
- `,
981
- 'dragHandle-position-right': css `
982
- left: 0;
983
- width: ${tokens.size[1]};
984
- height: 100%;
985
- cursor: ew-resize;
986
- `,
987
- 'dragHandle-position-left': css `
988
- right: 0;
989
- width: ${tokens.size[1]};
990
- height: 100%;
991
- cursor: ew-resize;
992
- `,
993
- row: css `
994
- display: flex;
995
- justify-content: space-between;
996
- padding: ${tokens.size[2.5]} ${tokens.size[3]};
997
- gap: ${tokens.size[4]};
998
- border-bottom: ${colors.darkGray[500]} 1px solid;
999
- align-items: center;
1000
- & > button {
1001
- padding: 0;
1002
- background: transparent;
1003
- border: none;
1004
- display: flex;
1005
- flex-direction: column;
1006
- }
1007
- `,
1008
- logo: css `
1009
- cursor: pointer;
1010
- &:hover {
1011
- opacity: 0.7;
1012
- }
1013
- &:focus-visible {
1014
- outline-offset: 4px;
1015
- border-radius: ${border.radius.xs};
1016
- outline: 2px solid ${colors.blue[800]};
1017
- }
1018
- `,
1019
- tanstackLogo: css `
1020
- font-size: ${font.size.lg};
1021
- font-weight: ${font.weight.extrabold};
1022
- line-height: ${font.lineHeight.sm};
1023
- white-space: nowrap;
1024
- `,
1025
- queryFlavorLogo: css `
1026
- font-weight: ${font.weight.semibold};
1027
- font-size: ${font.size.sm};
1028
- background: linear-gradient(to right, #dd524b, #e9a03b);
1029
- background-clip: text;
1030
- line-height: ${font.lineHeight.xs};
1031
- -webkit-text-fill-color: transparent;
1032
- white-space: nowrap;
1033
- `,
1034
- queryStatusContainer: css `
1035
- display: flex;
1036
- gap: ${tokens.size[2]};
1037
- height: min-content;
1038
- `,
1039
- queryStatusTag: css `
1040
- display: flex;
1041
- gap: ${tokens.size[1.5]};
1042
- background: ${colors.darkGray[500]};
1043
- border-radius: ${tokens.border.radius.md};
1044
- font-size: ${font.size.sm};
1045
- padding: ${tokens.size[1]};
1046
- padding-left: ${tokens.size[2.5]};
1047
- align-items: center;
1048
- line-height: ${font.lineHeight.md};
1049
- font-weight: ${font.weight.medium};
1050
- border: none;
1051
- user-select: none;
1052
- position: relative;
1053
- &:focus-visible {
1054
- outline-offset: 2px;
1055
- outline: 2px solid ${colors.blue[800]};
1056
- }
1057
- & span:nth-child(2) {
1058
- color: ${colors.gray[300]}${alpha[80]};
1059
- }
1060
- `,
1061
- statusTooltip: css `
1062
- position: absolute;
1063
- z-index: 1;
1064
- background-color: ${colors.darkGray[500]};
1065
- top: 100%;
1066
- left: 50%;
1067
- transform: translate(-50%, calc(${tokens.size[2]}));
1068
- padding: ${tokens.size[0.5]} ${tokens.size[3]};
1069
- border-radius: ${tokens.border.radius.md};
1070
- font-size: ${font.size.sm};
1071
- border: 2px solid ${colors.gray[600]};
1072
- color: ${tokens.colors['gray'][300]};
1073
-
1074
- &::before {
1075
- top: 0px;
1076
- content: ' ';
1077
- display: block;
1078
- left: 50%;
1079
- transform: translate(-50%, -100%);
1080
- position: absolute;
1081
- border-color: transparent transparent ${colors.gray[600]} transparent;
1082
- border-style: solid;
1083
- border-width: 7px;
1084
- /* transform: rotate(180deg); */
1085
- }
1086
-
1087
- &::after {
1088
- top: 0px;
1089
- content: ' ';
1090
- display: block;
1091
- left: 50%;
1092
- transform: translate(-50%, calc(-100% + 2.5px));
1093
- position: absolute;
1094
- border-color: transparent transparent ${colors.darkGray[500]}
1095
- transparent;
1096
- border-style: solid;
1097
- border-width: 7px;
1098
- }
1099
- `,
1100
- selectedQueryRow: css `
1101
- background-color: ${colors.darkGray[500]};
1102
- `,
1103
- queryStatusCount: css `
1104
- padding: 0 8px;
1105
- display: flex;
1106
- align-items: center;
1107
- justify-content: center;
1108
- color: ${colors.gray[400]};
1109
- background-color: ${colors.darkGray[300]};
1110
- border-radius: 3px;
1111
- font-variant-numeric: tabular-nums;
1112
- `,
1113
- filtersContainer: css `
1114
- display: flex;
1115
- gap: ${tokens.size[2.5]};
1116
- & > button {
1117
- cursor: pointer;
1118
- padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1119
- padding-right: ${tokens.size[1.5]};
1120
- border-radius: ${tokens.border.radius.md};
1121
- background-color: ${colors.darkGray[400]};
1122
- font-size: ${font.size.sm};
1123
- display: flex;
1124
- align-items: center;
1125
- line-height: ${font.lineHeight.sm};
1126
- gap: ${tokens.size[1.5]};
1127
- max-width: 160px;
1128
- border: 1px solid ${colors.darkGray[200]};
1129
- &:focus-visible {
1130
- outline-offset: 2px;
1131
- border-radius: ${border.radius.xs};
1132
- outline: 2px solid ${colors.blue[800]};
1133
- }
1134
- }
1135
- `,
1136
- filterInput: css `
1137
- padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1138
- border-radius: ${tokens.border.radius.md};
1139
- background-color: ${colors.darkGray[400]};
1140
- display: flex;
1141
- box-sizing: content-box;
1142
- align-items: center;
1143
- gap: ${tokens.size[1.5]};
1144
- max-width: 160px;
1145
- min-width: 100px;
1146
- border: 1px solid ${colors.darkGray[200]};
1147
- height: min-content;
1148
- & > svg {
1149
- width: ${tokens.size[3.5]};
1150
- height: ${tokens.size[3.5]};
1151
- }
1152
- & input {
1153
- font-size: ${font.size.sm};
1154
- width: 100%;
1155
- background-color: ${colors.darkGray[400]};
1156
- border: none;
1157
- padding: 0;
1158
- line-height: ${font.lineHeight.sm};
1159
- color: ${colors.gray[300]};
1160
- &::placeholder {
1161
- color: ${colors.gray[300]};
1162
- }
1163
- &:focus {
1164
- outline: none;
1165
- }
1166
- }
1167
-
1168
- &:focus-within {
1169
- outline-offset: 2px;
1170
- border-radius: ${border.radius.xs};
1171
- outline: 2px solid ${colors.blue[800]};
1172
- }
1173
- `,
1174
- filterSelect: css `
1175
- padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1176
- border-radius: ${tokens.border.radius.md};
1177
- background-color: ${colors.darkGray[400]};
1178
- display: flex;
1179
- align-items: center;
1180
- gap: ${tokens.size[1.5]};
1181
- box-sizing: content-box;
1182
- max-width: 160px;
1183
- border: 1px solid ${colors.darkGray[200]};
1184
- height: min-content;
1185
- & > svg {
1186
- width: ${tokens.size[3]};
1187
- height: ${tokens.size[3]};
1188
- }
1189
- & > select {
1190
- appearance: none;
1191
- min-width: 100px;
1192
- line-height: ${font.lineHeight.sm};
1193
- font-size: ${font.size.sm};
1194
- background-color: ${colors.darkGray[400]};
1195
- border: none;
1196
- &:focus {
1197
- outline: none;
1198
- }
1199
- }
1200
- &:focus-within {
1201
- outline-offset: 2px;
1202
- border-radius: ${border.radius.xs};
1203
- outline: 2px solid ${colors.blue[800]};
1204
- }
1205
- `,
1206
- actionsContainer: css `
1207
- display: flex;
1208
- gap: ${tokens.size[2.5]};
1209
- `,
1210
- actionsBtn: css `
1211
- border-radius: ${tokens.border.radius.md};
1212
- background-color: ${colors.darkGray[400]};
1213
- width: 2.125rem; // 34px
1214
- height: 2.125rem; // 34px
1215
- justify-content: center;
1216
- display: flex;
1217
- align-items: center;
1218
- gap: ${tokens.size[1.5]};
1219
- max-width: 160px;
1220
- border: 1px solid ${colors.darkGray[200]};
1221
- cursor: pointer;
1222
- &:hover {
1223
- background-color: ${colors.darkGray[500]};
1224
- }
1225
- & svg {
1226
- width: ${tokens.size[4]};
1227
- height: ${tokens.size[4]};
1228
- }
1229
- &:focus-visible {
1230
- outline-offset: 2px;
1231
- border-radius: ${border.radius.xs};
1232
- outline: 2px solid ${colors.blue[800]};
1233
- }
1234
- `,
1235
- overflowQueryContainer: css `
1236
- flex: 1;
1237
- overflow-y: auto;
1238
- & > div {
1239
- display: flex;
1240
- flex-direction: column;
1241
- }
1242
- `,
1243
- queryRow: css `
1244
- display: flex;
1245
- align-items: center;
1246
- padding: 0;
1247
- background-color: inherit;
1248
- border: none;
1249
- cursor: pointer;
1250
- &:focus-visible {
1251
- outline-offset: -2px;
1252
- border-radius: ${border.radius.xs};
1253
- outline: 2px solid ${colors.blue[800]};
1254
- }
1255
- &:hover .TSQDQueryHash {
1256
- background-color: ${colors.darkGray[600]};
1257
- }
1258
-
1259
- & .TSQDObserverCount {
1260
- padding: 0 ${tokens.size[1]};
1261
- user-select: none;
1262
- min-width: ${tokens.size[8]};
1263
- align-self: stretch !important;
1264
- display: flex;
1265
- align-items: center;
1266
- justify-content: center;
1267
- font-size: ${font.size.sm};
1268
- font-weight: ${font.weight.medium};
1269
- border-bottom: 1px solid ${colors.darkGray[700]};
1270
- }
1271
- & .TSQDQueryHash {
1272
- user-select: text;
1273
- font-size: ${font.size.sm};
1274
- display: flex;
1275
- align-items: center;
1276
- min-height: ${tokens.size[8]};
1277
- flex: 1;
1278
- padding: ${tokens.size[1]} ${tokens.size[2]};
1279
- font-family: 'Menlo', 'Fira Code', monospace !important;
1280
- border-bottom: 1px solid ${colors.darkGray[400]};
1281
- text-align: left;
1282
- text-overflow: clip;
1283
- word-break: break-word;
1284
- }
1285
-
1286
- & .TSQDQueryDisabled {
1287
- align-self: stretch;
1288
- align-self: stretch !important;
1289
- display: flex;
1290
- align-items: center;
1291
- padding: 0 ${tokens.size[3]};
1292
- color: ${colors.gray[300]};
1293
- background-color: ${colors.darkGray[600]};
1294
- border-bottom: 1px solid ${colors.darkGray[400]};
1295
- font-size: ${font.size.sm};
1296
- }
1297
- `,
1298
- detailsContainer: css `
1299
- flex: 1 1 700px;
1300
- background-color: ${colors.darkGray[700]};
1301
- display: flex;
1302
- flex-direction: column;
1303
- overflow-y: auto;
1304
- display: flex;
1305
- `,
1306
- detailsHeader: css `
1307
- position: sticky;
1308
- top: 0;
1309
- z-index: 2;
1310
- background-color: ${colors.darkGray[600]};
1311
- padding: ${tokens.size[2]} ${tokens.size[2]};
1312
- font-weight: ${font.weight.medium};
1313
- font-size: ${font.size.sm};
1314
- `,
1315
- detailsBody: css `
1316
- margin: ${tokens.size[2]} 0px ${tokens.size[3]} 0px;
1317
- & > div {
1318
- display: flex;
1319
- align-items: stretch;
1320
- padding: 0 ${tokens.size[2]};
1321
- line-height: ${font.lineHeight.sm};
1322
- justify-content: space-between;
1323
- & > span {
1324
- font-size: ${font.size.sm};
1325
- }
1326
- & > span:nth-child(2) {
1327
- font-variant-numeric: tabular-nums;
1328
- }
1329
- }
1330
-
1331
- & > div:first-child {
1332
- margin-bottom: ${tokens.size[2]};
1333
- }
1334
-
1335
- & code {
1336
- font-family: 'Menlo', 'Fira Code', monospace !important;
1337
- margin: 0;
1338
- font-size: ${font.size.sm};
1339
- line-height: ${font.lineHeight.sm};
1340
- }
1341
- `,
1342
- queryDetailsStatus: css `
1343
- border: 1px solid ${colors.darkGray[200]};
1344
- border-radius: ${tokens.border.radius.md};
1345
- font-weight: ${font.weight.medium};
1346
- padding: ${tokens.size[1]} ${tokens.size[2.5]};
1347
- `,
1348
- actionsBody: css `
1349
- flex-wrap: wrap;
1350
- margin: ${tokens.size[3]} 0px ${tokens.size[3]} 0px;
1351
- display: flex;
1352
- gap: ${tokens.size[2]};
1353
- padding: 0px ${tokens.size[2]};
1354
- & > button {
1355
- font-size: ${font.size.sm};
1356
- padding: ${tokens.size[2]} ${tokens.size[2]};
1357
- display: flex;
1358
- border-radius: ${tokens.border.radius.md};
1359
- border: 1px solid ${colors.darkGray[400]};
1360
- background-color: ${colors.darkGray[600]};
1361
- align-items: center;
1362
- gap: ${tokens.size[2]};
1363
- font-weight: ${font.weight.medium};
1364
- line-height: ${font.lineHeight.sm};
1365
- cursor: pointer;
1366
- &:focus-visible {
1367
- outline-offset: 2px;
1368
- border-radius: ${border.radius.xs};
1369
- outline: 2px solid ${colors.blue[800]};
1370
- }
1371
- &:hover {
1372
- background-color: ${colors.darkGray[500]};
1373
- }
1374
-
1375
- &:disabled {
1376
- opacity: 0.6;
1377
- cursor: not-allowed;
1378
- }
1379
-
1380
- & > span {
1381
- width: ${size[2]};
1382
- height: ${size[2]};
1383
- border-radius: ${tokens.border.radius.full};
1384
- }
1385
- }
1386
- `,
1387
- actionsSelect: css `
1388
- font-size: ${font.size.sm};
1389
- padding: ${tokens.size[2]} ${tokens.size[2]};
1390
- display: flex;
1391
- border-radius: ${tokens.border.radius.md};
1392
- overflow: hidden;
1393
- border: 1px solid ${colors.darkGray[400]};
1394
- background-color: ${colors.darkGray[600]};
1395
- align-items: center;
1396
- gap: ${tokens.size[2]};
1397
- font-weight: ${font.weight.medium};
1398
- line-height: ${font.lineHeight.sm};
1399
- color: ${tokens.colors.red[400]};
1400
- cursor: pointer;
1401
- position: relative;
1402
- &:hover {
1403
- background-color: ${colors.darkGray[500]};
1404
- }
1405
- & > span {
1406
- width: ${size[2]};
1407
- height: ${size[2]};
1408
- border-radius: ${tokens.border.radius.full};
1409
- }
1410
- &:focus-within {
1411
- outline-offset: 2px;
1412
- border-radius: ${border.radius.xs};
1413
- outline: 2px solid ${colors.blue[800]};
1414
- }
1415
- & select {
1416
- position: absolute;
1417
- top: 0;
1418
- left: 0;
1419
- width: 100%;
1420
- height: 100%;
1421
- appearance: none;
1422
- background-color: transparent;
1423
- border: none;
1424
- color: transparent;
1425
- outline: none;
1426
- }
1427
-
1428
- & svg path {
1429
- stroke: ${tokens.colors.red[400]} !important;
1430
- }
1431
- `,
1432
- settingsMenu: css `
1433
- position: absolute;
1434
- top: calc(100% + ${tokens.size[2]});
1435
- border-radius: ${tokens.border.radius.lg};
1436
- border: 1px solid ${colors.gray[600]};
1437
- right: 0;
1438
- min-width: ${tokens.size[44]};
1439
- background-color: ${colors.darkGray[400]};
1440
- font-size: ${font.size.sm};
1441
- color: ${colors.gray[500]};
1442
- z-index: 7;
1443
- `,
1444
- settingsMenuHeader: css `
1445
- padding: ${tokens.size[1.5]} ${tokens.size[2.5]};
1446
- color: ${colors.gray[300]};
1447
- font-weight: ${font.weight.medium};
1448
- `,
1449
- settingsMenuSection: css `
1450
- border-top: 1px solid ${colors.gray[600]};
1451
- display: flex;
1452
- flex-direction: column;
1453
- padding: ${tokens.size[1]} ${tokens.size[1]};
1454
-
1455
- & > button {
1456
- cursor: pointer;
1457
- background-color: transparent;
1458
- border: none;
1459
- padding: ${tokens.size[2]} ${tokens.size[1.5]};
1460
- font-size: ${font.size.sm};
1461
- display: flex;
1462
- align-items: center;
1463
- justify-content: flex-start;
1464
- gap: ${tokens.size[2]};
1465
- border-radius: ${tokens.border.radius.md};
1466
- &:hover {
1467
- background-color: ${colors.darkGray[500]};
1468
- }
1469
-
1470
- &:focus-visible {
1471
- outline-offset: 2px;
1472
- outline: 2px solid ${colors.blue[800]};
1473
- }
1474
- }
1475
-
1476
- & button:nth-child(4) svg {
1477
- transform: rotate(-90deg);
1478
- }
1479
-
1480
- & button:nth-child(3) svg {
1481
- transform: rotate(90deg);
1482
- }
1483
- `,
1484
- };
1485
- };