@rozenite/storage-plugin 2.0.0 → 2.1.0
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.
- package/CHANGELOG.md +18 -0
- package/dist/devtools/assets/panel-BlDRXgVW.js +32 -0
- package/dist/devtools/assets/panel-DBweZnW5.css +1 -0
- package/dist/devtools/panel.html +2 -2
- package/dist/react-native/chunks/index.require.js +1 -3
- package/dist/react-native/chunks/secure-storage.require.js +2 -6
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +266 -295
- package/dist/react-native/index.d.ts +14 -2
- package/dist/react-native/index.js +2 -10
- package/dist/rozenite.json +1 -1
- package/package.json +29 -29
- package/react-native.ts +11 -36
- package/rozenite.config.ts +2 -6
- package/sdk.ts +1 -5
- package/src/react-native/__tests__/entry-preview-pagination.test.ts +12 -40
- package/src/react-native/__tests__/export-snapshot.test.ts +2 -6
- package/src/react-native/__tests__/full-entry-request.test.ts +5 -17
- package/src/react-native/__tests__/import.test.ts +9 -8
- package/src/react-native/__tests__/storage-discovery.test.ts +7 -18
- package/src/react-native/__tests__/stress-verification.test.ts +5 -18
- package/src/react-native/__tests__/use-storage-agent-tools.test.ts +25 -35
- package/src/react-native/adapters/__tests__/mmkv.test.ts +20 -30
- package/src/react-native/adapters/async-storage.ts +2 -5
- package/src/react-native/adapters/index.ts +2 -8
- package/src/react-native/adapters/mmkv.ts +7 -24
- package/src/react-native/adapters/secure-storage.ts +2 -6
- package/src/react-native/entry-preview-pagination.ts +11 -27
- package/src/react-native/export-snapshot.ts +3 -9
- package/src/react-native/full-entry-request.ts +3 -8
- package/src/react-native/import.ts +10 -21
- package/src/react-native/storage-discovery.ts +2 -6
- package/src/react-native/storage-view.ts +2 -6
- package/src/react-native/useRozeniteStoragePlugin.ts +107 -119
- package/src/react-native/useStorageAgentTools.ts +8 -24
- package/src/shared/__tests__/entry-preview.test.ts +9 -27
- package/src/shared/__tests__/snapshot.test.ts +10 -32
- package/src/shared/agent-tools.ts +9 -30
- package/src/shared/entry-preview.ts +3 -8
- package/src/shared/messaging.ts +15 -11
- package/src/shared/snapshot.ts +4 -17
- package/src/shared/types.ts +2 -4
- package/src/ui/__tests__/binary-value-editor-state.test.ts +6 -16
- package/src/ui/__tests__/binary.test.ts +13 -33
- package/src/ui/__tests__/large-value-viewer.test.tsx +4 -13
- package/src/ui/__tests__/panel.test.tsx +41 -68
- package/src/ui/__tests__/storage-groups.test.ts +1 -4
- package/src/ui/__tests__/type-conversion.test.ts +8 -24
- package/src/ui/__tests__/use-storage-entries.test.tsx +18 -34
- package/src/ui/add-entry-dialog.tsx +4 -15
- package/src/ui/binary-value-editor-state.ts +6 -24
- package/src/ui/binary-value-editor.tsx +5 -17
- package/src/ui/binary.ts +6 -19
- package/src/ui/edit-entry-dialog.tsx +3 -11
- package/src/ui/editor-switcher.tsx +2 -11
- package/src/ui/entry-detail-dialog.tsx +5 -14
- package/src/ui/format-value.tsx +2 -9
- package/src/ui/import-dialog.tsx +7 -19
- package/src/ui/large-value-viewer-state.ts +2 -9
- package/src/ui/large-value-viewer.tsx +3 -9
- package/src/ui/panel.tsx +65 -153
- package/src/ui/query-client.tsx +4 -15
- package/src/ui/type-conversion.ts +2 -5
- package/src/ui/typed-value-editor.tsx +1 -5
- package/src/ui/use-storage-entries.ts +6 -21
- package/src/ui/utils.ts +1 -4
- package/dist/devtools/assets/panel-B6Wp0eie.css +0 -1
- package/dist/devtools/assets/panel-D7MFc4HG.js +0 -32
package/src/ui/panel.tsx
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
|
-
import type {
|
|
3
|
-
ColumnDef,
|
|
4
|
-
OnChangeFn,
|
|
5
|
-
SortingState,
|
|
6
|
-
} from '@tanstack/react-table';
|
|
2
|
+
import type { ColumnDef, OnChangeFn, SortingState } from '@tanstack/react-table';
|
|
7
3
|
import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
8
4
|
import {
|
|
9
5
|
Badge,
|
|
@@ -18,15 +14,7 @@ import {
|
|
|
18
14
|
VirtualizedDataTable,
|
|
19
15
|
} from '@rozenite/ui';
|
|
20
16
|
import { useEffect, useMemo, useRef, useState, type ChangeEvent } from 'react';
|
|
21
|
-
import {
|
|
22
|
-
Database,
|
|
23
|
-
Download,
|
|
24
|
-
Edit3,
|
|
25
|
-
Plus,
|
|
26
|
-
RefreshCw,
|
|
27
|
-
Trash2,
|
|
28
|
-
Upload,
|
|
29
|
-
} from 'lucide-react';
|
|
17
|
+
import { Database, Download, Edit3, Plus, RefreshCw, Trash2, Upload } from 'lucide-react';
|
|
30
18
|
import type {
|
|
31
19
|
StorageDiscoverStoragesResponseEvent,
|
|
32
20
|
StorageEventMap,
|
|
@@ -66,9 +54,7 @@ type FullEntryInteraction = { key: string; mode: 'detail' | 'edit' };
|
|
|
66
54
|
const sameTarget = (a: StorageTarget, b: StorageTarget) =>
|
|
67
55
|
a.adapterId === b.adapterId && a.storageId === b.storageId;
|
|
68
56
|
|
|
69
|
-
const getEntryTypeFromValue = (
|
|
70
|
-
value: StorageEntryValue,
|
|
71
|
-
): StorageEntry['type'] => {
|
|
57
|
+
const getEntryTypeFromValue = (value: StorageEntryValue): StorageEntry['type'] => {
|
|
72
58
|
if (typeof value === 'string') return 'string';
|
|
73
59
|
if (typeof value === 'number') return 'number';
|
|
74
60
|
if (typeof value === 'boolean') return 'boolean';
|
|
@@ -88,28 +74,18 @@ const useDebouncedValue = (value: string, delay = 250) => {
|
|
|
88
74
|
|
|
89
75
|
function StoragePanelContent() {
|
|
90
76
|
const [descriptors, setDescriptors] = useState<StorageDescriptor[]>([]);
|
|
91
|
-
const [selectedTarget, setSelectedTarget] = useState<StorageTarget | null>(
|
|
92
|
-
null,
|
|
93
|
-
);
|
|
77
|
+
const [selectedTarget, setSelectedTarget] = useState<StorageTarget | null>(null);
|
|
94
78
|
const [searchTerm, setSearchTerm] = useState('');
|
|
95
|
-
const [keySortDirection, setKeySortDirection] = useState<
|
|
96
|
-
|
|
97
|
-
>('ascending');
|
|
98
|
-
const [interaction, setInteraction] = useState<FullEntryInteraction | null>(
|
|
99
|
-
null,
|
|
100
|
-
);
|
|
79
|
+
const [keySortDirection, setKeySortDirection] = useState<'ascending' | 'descending'>('ascending');
|
|
80
|
+
const [interaction, setInteraction] = useState<FullEntryInteraction | null>(null);
|
|
101
81
|
const [showAddDialog, setShowAddDialog] = useState(false);
|
|
102
82
|
const [deleteKey, setDeleteKey] = useState<string | null>(null);
|
|
103
83
|
const [showPurgeDialog, setShowPurgeDialog] = useState(false);
|
|
104
84
|
const [virtualListVersion, setVirtualListVersion] = useState(0);
|
|
105
85
|
const [exportState, setExportState] = useState<
|
|
106
|
-
| { status: '
|
|
107
|
-
| { status: 'loading' }
|
|
108
|
-
| { status: 'error'; message: string }
|
|
86
|
+
{ status: 'idle' } | { status: 'loading' } | { status: 'error'; message: string }
|
|
109
87
|
>({ status: 'idle' });
|
|
110
|
-
const [importFlight, setImportFlight] = useState<ImportFlightState | null>(
|
|
111
|
-
null,
|
|
112
|
-
);
|
|
88
|
+
const [importFlight, setImportFlight] = useState<ImportFlightState | null>(null);
|
|
113
89
|
const [alertState, setAlertState] = useState<AlertState | null>(null);
|
|
114
90
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
115
91
|
const selectedTargetRef = useRef<StorageTarget | null>(null);
|
|
@@ -127,8 +103,7 @@ function StoragePanelContent() {
|
|
|
127
103
|
const queryClient = useQueryClient();
|
|
128
104
|
const debouncedSearch = useDebouncedValue(searchTerm);
|
|
129
105
|
const selectedDescriptor = descriptors.find(
|
|
130
|
-
(descriptor) =>
|
|
131
|
-
selectedTarget && sameTarget(descriptor.target, selectedTarget),
|
|
106
|
+
(descriptor) => selectedTarget && sameTarget(descriptor.target, selectedTarget),
|
|
132
107
|
);
|
|
133
108
|
const previews = useStorageEntryPreviews({
|
|
134
109
|
client,
|
|
@@ -146,10 +121,7 @@ function StoragePanelContent() {
|
|
|
146
121
|
const supportedTypes = selectedDescriptor?.capabilities.supportedTypes ?? [];
|
|
147
122
|
const sidebarGroups = useMemo(() => {
|
|
148
123
|
const storages = new Map(
|
|
149
|
-
descriptors.map((descriptor) => [
|
|
150
|
-
getStorageViewId(descriptor.target),
|
|
151
|
-
descriptor,
|
|
152
|
-
]),
|
|
124
|
+
descriptors.map((descriptor) => [getStorageViewId(descriptor.target), descriptor]),
|
|
153
125
|
);
|
|
154
126
|
return buildStorageSidebarGroups(storages);
|
|
155
127
|
}, [descriptors]);
|
|
@@ -169,17 +141,21 @@ function StoragePanelContent() {
|
|
|
169
141
|
useEffect(() => {
|
|
170
142
|
if (!client) return;
|
|
171
143
|
|
|
144
|
+
const requestDiscovery = () => {
|
|
145
|
+
discoveryRequestIdRef.current += 1;
|
|
146
|
+
client.send('discover-storages', {
|
|
147
|
+
type: 'discover-storages',
|
|
148
|
+
requestId: `discovery-${discoveryRequestIdRef.current}`,
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
172
152
|
const descriptorsSubscription = client.onMessage(
|
|
173
153
|
'storage-descriptors',
|
|
174
154
|
(event: StorageDiscoverStoragesResponseEvent) => {
|
|
175
|
-
if (event.requestId !== `discovery-${discoveryRequestIdRef.current}`)
|
|
176
|
-
return;
|
|
155
|
+
if (event.requestId !== `discovery-${discoveryRequestIdRef.current}`) return;
|
|
177
156
|
setDescriptors(event.storages);
|
|
178
157
|
setSelectedTarget((previous) =>
|
|
179
|
-
previous &&
|
|
180
|
-
event.storages.some((descriptor) =>
|
|
181
|
-
sameTarget(descriptor.target, previous),
|
|
182
|
-
)
|
|
158
|
+
previous && event.storages.some((descriptor) => sameTarget(descriptor.target, previous))
|
|
183
159
|
? previous
|
|
184
160
|
: (event.storages[0]?.target ?? null),
|
|
185
161
|
);
|
|
@@ -234,23 +210,16 @@ function StoragePanelContent() {
|
|
|
234
210
|
'storage-invalidated',
|
|
235
211
|
(event: StorageInvalidatedEvent) => {
|
|
236
212
|
void dropStorageFullEntries(queryClient, event.target, event.key);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
if (
|
|
247
|
-
selectedTargetRef.current &&
|
|
248
|
-
sameTarget(event.target, selectedTargetRef.current)
|
|
249
|
-
) {
|
|
213
|
+
setDescriptors((current) =>
|
|
214
|
+
current.map((descriptor) =>
|
|
215
|
+
sameTarget(descriptor.target, event.target)
|
|
216
|
+
? { ...descriptor, entryCount: event.entryCount }
|
|
217
|
+
: descriptor,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
if (selectedTargetRef.current && sameTarget(event.target, selectedTargetRef.current)) {
|
|
250
221
|
setInteraction((current) =>
|
|
251
|
-
current && (event.key == null || current.key === event.key)
|
|
252
|
-
? null
|
|
253
|
-
: current,
|
|
222
|
+
current && (event.key == null || current.key === event.key) ? null : current,
|
|
254
223
|
);
|
|
255
224
|
}
|
|
256
225
|
|
|
@@ -259,10 +228,7 @@ function StoragePanelContent() {
|
|
|
259
228
|
pendingInvalidationsRef.current.add(targetId);
|
|
260
229
|
queueMicrotask(() => {
|
|
261
230
|
pendingInvalidationsRef.current.delete(targetId);
|
|
262
|
-
if (
|
|
263
|
-
selectedTargetRef.current &&
|
|
264
|
-
sameTarget(event.target, selectedTargetRef.current)
|
|
265
|
-
) {
|
|
231
|
+
if (selectedTargetRef.current && sameTarget(event.target, selectedTargetRef.current)) {
|
|
266
232
|
setVirtualListVersion((version) => version + 1);
|
|
267
233
|
}
|
|
268
234
|
void invalidateStorageEntryPreviews(queryClient, event.target);
|
|
@@ -270,16 +236,21 @@ function StoragePanelContent() {
|
|
|
270
236
|
},
|
|
271
237
|
);
|
|
272
238
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
239
|
+
// The device announces itself once it is listening. It reconnects on every
|
|
240
|
+
// app reload, so discovery has to run again — the descriptors and cached
|
|
241
|
+
// entries the panel holds belong to the previous JS context.
|
|
242
|
+
const deviceReadySubscription = client.onMessage('device-ready', () => {
|
|
243
|
+
void queryClient.resetQueries();
|
|
244
|
+
requestDiscovery();
|
|
277
245
|
});
|
|
246
|
+
|
|
247
|
+
requestDiscovery();
|
|
278
248
|
return () => {
|
|
279
249
|
descriptorsSubscription.remove();
|
|
280
250
|
importProgressSubscription.remove();
|
|
281
251
|
importResultSubscription.remove();
|
|
282
252
|
invalidationSubscription.remove();
|
|
253
|
+
deviceReadySubscription.remove();
|
|
283
254
|
};
|
|
284
255
|
}, [client, queryClient]);
|
|
285
256
|
|
|
@@ -310,11 +281,7 @@ function StoragePanelContent() {
|
|
|
310
281
|
};
|
|
311
282
|
|
|
312
283
|
const handleValueChange = (key: string, newValue: StorageEntryValue) => {
|
|
313
|
-
if (
|
|
314
|
-
!client ||
|
|
315
|
-
!selectedTarget ||
|
|
316
|
-
!supportedTypes.includes(getEntryTypeFromValue(newValue))
|
|
317
|
-
)
|
|
284
|
+
if (!client || !selectedTarget || !supportedTypes.includes(getEntryTypeFromValue(newValue)))
|
|
318
285
|
return;
|
|
319
286
|
const type = getEntryTypeFromValue(newValue);
|
|
320
287
|
const entry =
|
|
@@ -367,8 +334,7 @@ function StoragePanelContent() {
|
|
|
367
334
|
});
|
|
368
335
|
};
|
|
369
336
|
|
|
370
|
-
const showAlert = (title: string, message: string) =>
|
|
371
|
-
setAlertState({ title, message });
|
|
337
|
+
const showAlert = (title: string, message: string) => setAlertState({ title, message });
|
|
372
338
|
|
|
373
339
|
const handleImportClick = () => {
|
|
374
340
|
if (fileInputRef.current) {
|
|
@@ -384,18 +350,12 @@ function StoragePanelContent() {
|
|
|
384
350
|
try {
|
|
385
351
|
raw = JSON.parse(await file.text());
|
|
386
352
|
} catch (error) {
|
|
387
|
-
showAlert(
|
|
388
|
-
'Could not read file',
|
|
389
|
-
error instanceof Error ? error.message : String(error),
|
|
390
|
-
);
|
|
353
|
+
showAlert('Could not read file', error instanceof Error ? error.message : String(error));
|
|
391
354
|
return;
|
|
392
355
|
}
|
|
393
356
|
const parsed = parseSnapshot(raw);
|
|
394
357
|
if (!parsed.ok) {
|
|
395
|
-
showAlert(
|
|
396
|
-
'Invalid snapshot',
|
|
397
|
-
`${parsed.error.path}: ${parsed.error.message}`,
|
|
398
|
-
);
|
|
358
|
+
showAlert('Invalid snapshot', `${parsed.error.path}: ${parsed.error.message}`);
|
|
399
359
|
return;
|
|
400
360
|
}
|
|
401
361
|
importPreviewAbortControllerRef.current?.abort();
|
|
@@ -410,10 +370,7 @@ function StoragePanelContent() {
|
|
|
410
370
|
payload: { type: 'preview-import', target, snapshot: parsed.snapshot },
|
|
411
371
|
signal: controller.signal,
|
|
412
372
|
});
|
|
413
|
-
if (
|
|
414
|
-
controller.signal.aborted ||
|
|
415
|
-
!sameTarget(target, selectedTargetRef.current ?? target)
|
|
416
|
-
)
|
|
373
|
+
if (controller.signal.aborted || !sameTarget(target, selectedTargetRef.current ?? target))
|
|
417
374
|
return;
|
|
418
375
|
setImportFlight({
|
|
419
376
|
phase: 'preview',
|
|
@@ -428,10 +385,7 @@ function StoragePanelContent() {
|
|
|
428
385
|
),
|
|
429
386
|
});
|
|
430
387
|
} catch {
|
|
431
|
-
if (
|
|
432
|
-
!controller.signal.aborted &&
|
|
433
|
-
sameTarget(target, selectedTargetRef.current ?? target)
|
|
434
|
-
) {
|
|
388
|
+
if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
|
|
435
389
|
showAlert(
|
|
436
390
|
'Could not preview import',
|
|
437
391
|
'Could not inspect the selected storage. Please try again.',
|
|
@@ -476,26 +430,19 @@ function StoragePanelContent() {
|
|
|
476
430
|
payload: { type: 'export-snapshot', target },
|
|
477
431
|
signal: controller.signal,
|
|
478
432
|
});
|
|
479
|
-
if (
|
|
480
|
-
!controller.signal.aborted &&
|
|
481
|
-
sameTarget(target, selectedTargetRef.current ?? target)
|
|
482
|
-
) {
|
|
433
|
+
if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
|
|
483
434
|
downloadJson(response.snapshot, buildExportFilename(target));
|
|
484
435
|
setExportState({ status: 'idle' });
|
|
485
436
|
}
|
|
486
437
|
} catch {
|
|
487
|
-
if (
|
|
488
|
-
!controller.signal.aborted &&
|
|
489
|
-
sameTarget(target, selectedTargetRef.current ?? target)
|
|
490
|
-
) {
|
|
438
|
+
if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
|
|
491
439
|
setExportState({
|
|
492
440
|
status: 'error',
|
|
493
441
|
message: 'Could not export the selected storage. Please try again.',
|
|
494
442
|
});
|
|
495
443
|
}
|
|
496
444
|
} finally {
|
|
497
|
-
if (exportAbortControllerRef.current === controller)
|
|
498
|
-
exportAbortControllerRef.current = null;
|
|
445
|
+
if (exportAbortControllerRef.current === controller) exportAbortControllerRef.current = null;
|
|
499
446
|
}
|
|
500
447
|
};
|
|
501
448
|
|
|
@@ -507,9 +454,7 @@ function StoragePanelContent() {
|
|
|
507
454
|
header: 'Key',
|
|
508
455
|
enableSorting: true,
|
|
509
456
|
cell: ({ row }) => (
|
|
510
|
-
<span className="font-mono text-sm text-foreground">
|
|
511
|
-
{row.original.key}
|
|
512
|
-
</span>
|
|
457
|
+
<span className="font-mono text-sm text-foreground">{row.original.key}</span>
|
|
513
458
|
),
|
|
514
459
|
},
|
|
515
460
|
{
|
|
@@ -532,9 +477,7 @@ function StoragePanelContent() {
|
|
|
532
477
|
<span
|
|
533
478
|
className="ml-2 text-xs text-muted-foreground"
|
|
534
479
|
title={
|
|
535
|
-
row.original.isTruncated
|
|
536
|
-
? `Full value size: ${row.original.valueSize}`
|
|
537
|
-
: undefined
|
|
480
|
+
row.original.isTruncated ? `Full value size: ${row.original.valueSize}` : undefined
|
|
538
481
|
}
|
|
539
482
|
>
|
|
540
483
|
{row.original.isTruncated
|
|
@@ -549,16 +492,11 @@ function StoragePanelContent() {
|
|
|
549
492
|
header: '',
|
|
550
493
|
enableSorting: false,
|
|
551
494
|
cell: ({ row }) => (
|
|
552
|
-
<div
|
|
553
|
-
className="flex items-center gap-1"
|
|
554
|
-
onClick={(event) => event.stopPropagation()}
|
|
555
|
-
>
|
|
495
|
+
<div className="flex items-center gap-1" onClick={(event) => event.stopPropagation()}>
|
|
556
496
|
<Button
|
|
557
497
|
variant="ghost"
|
|
558
498
|
size="icon"
|
|
559
|
-
onClick={() =>
|
|
560
|
-
setInteraction({ key: row.original.key, mode: 'edit' })
|
|
561
|
-
}
|
|
499
|
+
onClick={() => setInteraction({ key: row.original.key, mode: 'edit' })}
|
|
562
500
|
aria-label={`Edit value for ${row.original.key}`}
|
|
563
501
|
>
|
|
564
502
|
<Edit3 className="h-3.5 w-3.5" />
|
|
@@ -578,9 +516,7 @@ function StoragePanelContent() {
|
|
|
578
516
|
],
|
|
579
517
|
[],
|
|
580
518
|
);
|
|
581
|
-
const sorting: SortingState = [
|
|
582
|
-
{ id: 'key', desc: keySortDirection === 'descending' },
|
|
583
|
-
];
|
|
519
|
+
const sorting: SortingState = [{ id: 'key', desc: keySortDirection === 'descending' }];
|
|
584
520
|
const handleSortingChange: OnChangeFn<SortingState> = (updater) => {
|
|
585
521
|
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
|
586
522
|
const keySort = next.find((item) => item.id === 'key');
|
|
@@ -612,9 +548,7 @@ function StoragePanelContent() {
|
|
|
612
548
|
isFetchingPreviousPageRef.current = false;
|
|
613
549
|
});
|
|
614
550
|
};
|
|
615
|
-
const selectedStorageViewId = selectedTarget
|
|
616
|
-
? getStorageViewId(selectedTarget)
|
|
617
|
-
: '';
|
|
551
|
+
const selectedStorageViewId = selectedTarget ? getStorageViewId(selectedTarget) : '';
|
|
618
552
|
|
|
619
553
|
return (
|
|
620
554
|
<PluginShell>
|
|
@@ -628,22 +562,16 @@ function StoragePanelContent() {
|
|
|
628
562
|
</div>
|
|
629
563
|
) : (
|
|
630
564
|
sidebarGroups.map((group) => (
|
|
631
|
-
<Sidebar.Group
|
|
632
|
-
key={group.adapterId}
|
|
633
|
-
label={group.adapterName}
|
|
634
|
-
>
|
|
565
|
+
<Sidebar.Group key={group.adapterId} label={group.adapterName}>
|
|
635
566
|
{group.items.map((item) => (
|
|
636
567
|
<Sidebar.Item
|
|
637
568
|
key={item.viewId}
|
|
638
569
|
selected={item.viewId === selectedStorageViewId}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
570
|
+
adornment={<Database />}
|
|
571
|
+
trailing={<Badge variant="secondary">{item.entryCount}</Badge>}
|
|
642
572
|
onClick={() => {
|
|
643
573
|
const descriptor = descriptors.find(
|
|
644
|
-
(candidate) =>
|
|
645
|
-
getStorageViewId(candidate.target) ===
|
|
646
|
-
item.viewId,
|
|
574
|
+
(candidate) => getStorageViewId(candidate.target) === item.viewId,
|
|
647
575
|
);
|
|
648
576
|
if (descriptor) setSelectedTarget(descriptor.target);
|
|
649
577
|
}}
|
|
@@ -705,20 +633,12 @@ function StoragePanelContent() {
|
|
|
705
633
|
</Toolbar.Button>
|
|
706
634
|
<Toolbar.Button
|
|
707
635
|
onClick={handleExport}
|
|
708
|
-
disabled={
|
|
709
|
-
!client ||
|
|
710
|
-
!selectedTarget ||
|
|
711
|
-
exportState.status === 'loading'
|
|
712
|
-
}
|
|
636
|
+
disabled={!client || !selectedTarget || exportState.status === 'loading'}
|
|
713
637
|
aria-label={
|
|
714
|
-
exportState.status === 'loading'
|
|
715
|
-
? 'Exporting storage'
|
|
716
|
-
: 'Export storage'
|
|
638
|
+
exportState.status === 'loading' ? 'Exporting storage' : 'Export storage'
|
|
717
639
|
}
|
|
718
640
|
title={
|
|
719
|
-
exportState.status === 'loading'
|
|
720
|
-
? 'Exporting storage'
|
|
721
|
-
: 'Export storage'
|
|
641
|
+
exportState.status === 'loading' ? 'Exporting storage' : 'Export storage'
|
|
722
642
|
}
|
|
723
643
|
className="w-7 px-0"
|
|
724
644
|
>
|
|
@@ -759,15 +679,11 @@ function StoragePanelContent() {
|
|
|
759
679
|
getRowTextValue={(entry) => entry.key}
|
|
760
680
|
loading={previews.isLoading}
|
|
761
681
|
emptyMessage={
|
|
762
|
-
debouncedSearch
|
|
763
|
-
? 'No entries match your search.'
|
|
764
|
-
: 'This storage is empty.'
|
|
682
|
+
debouncedSearch ? 'No entries match your search.' : 'This storage is empty.'
|
|
765
683
|
}
|
|
766
684
|
manualSorting
|
|
767
685
|
onEndReached={fetchNextPage}
|
|
768
|
-
onRowClick={(entry) =>
|
|
769
|
-
setInteraction({ key: entry.key, mode: 'detail' })
|
|
770
|
-
}
|
|
686
|
+
onRowClick={(entry) => setInteraction({ key: entry.key, mode: 'detail' })}
|
|
771
687
|
onSortingChange={handleSortingChange}
|
|
772
688
|
onStartReached={fetchPreviousPage}
|
|
773
689
|
scrollClassName="h-full w-full overflow-auto"
|
|
@@ -799,9 +715,7 @@ function StoragePanelContent() {
|
|
|
799
715
|
if (!open) closeInteraction();
|
|
800
716
|
}}
|
|
801
717
|
onEdit={() =>
|
|
802
|
-
setInteraction((current) =>
|
|
803
|
-
current ? { ...current, mode: 'edit' } : current,
|
|
804
|
-
)
|
|
718
|
+
setInteraction((current) => (current ? { ...current, mode: 'edit' } : current))
|
|
805
719
|
}
|
|
806
720
|
entry={fullEntry.entry ?? null}
|
|
807
721
|
/>
|
|
@@ -827,9 +741,7 @@ function StoragePanelContent() {
|
|
|
827
741
|
destructive
|
|
828
742
|
title="Delete Entry"
|
|
829
743
|
description={
|
|
830
|
-
deleteKey
|
|
831
|
-
? `Are you sure you want to delete the entry "${deleteKey}"?`
|
|
832
|
-
: undefined
|
|
744
|
+
deleteKey ? `Are you sure you want to delete the entry "${deleteKey}"?` : undefined
|
|
833
745
|
}
|
|
834
746
|
confirmLabel="Delete"
|
|
835
747
|
onConfirm={handleDeleteEntry}
|
package/src/ui/query-client.tsx
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
QueryClient,
|
|
3
|
-
QueryClientProvider,
|
|
4
|
-
type QueryClientConfig,
|
|
5
|
-
} from '@tanstack/react-query';
|
|
1
|
+
import { QueryClient, QueryClientProvider, type QueryClientConfig } from '@tanstack/react-query';
|
|
6
2
|
import { type ReactNode, useEffect, useState } from 'react';
|
|
7
3
|
|
|
8
4
|
const storageQueryClientConfig: QueryClientConfig = {
|
|
@@ -16,19 +12,12 @@ const storageQueryClientConfig: QueryClientConfig = {
|
|
|
16
12
|
},
|
|
17
13
|
};
|
|
18
14
|
|
|
19
|
-
export const createStorageQueryClient = () =>
|
|
20
|
-
new QueryClient(storageQueryClientConfig);
|
|
15
|
+
export const createStorageQueryClient = () => new QueryClient(storageQueryClientConfig);
|
|
21
16
|
|
|
22
|
-
export const StorageQueryClientProvider = ({
|
|
23
|
-
children,
|
|
24
|
-
}: {
|
|
25
|
-
children: ReactNode;
|
|
26
|
-
}) => {
|
|
17
|
+
export const StorageQueryClientProvider = ({ children }: { children: ReactNode }) => {
|
|
27
18
|
const [queryClient] = useState(createStorageQueryClient);
|
|
28
19
|
|
|
29
20
|
useEffect(() => () => queryClient.clear(), [queryClient]);
|
|
30
21
|
|
|
31
|
-
return
|
|
32
|
-
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
33
|
-
);
|
|
22
|
+
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
|
34
23
|
};
|
|
@@ -15,8 +15,7 @@ const tryDecode = (bytes: readonly number[]): string => {
|
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const encode = (value: string): number[] =>
|
|
19
|
-
Array.from(textEncoder.encode(value));
|
|
18
|
+
const encode = (value: string): number[] => Array.from(textEncoder.encode(value));
|
|
20
19
|
|
|
21
20
|
// Convert a value between storage primitive types for the editor
|
|
22
21
|
// switcher. The headline transition is `string` ↔ `buffer`, which
|
|
@@ -89,9 +88,7 @@ export const convertValue = (
|
|
|
89
88
|
|
|
90
89
|
// Zero value for a type — used when seeding the add-entry dialog and
|
|
91
90
|
// when conversion has no meaningful starting point.
|
|
92
|
-
export const defaultValueForType = (
|
|
93
|
-
type: StorageEntryType,
|
|
94
|
-
): StorageEntryValue => {
|
|
91
|
+
export const defaultValueForType = (type: StorageEntryType): StorageEntryValue => {
|
|
95
92
|
switch (type) {
|
|
96
93
|
case 'string':
|
|
97
94
|
return '';
|
|
@@ -43,11 +43,7 @@ export const TypedValueEditor = ({
|
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
45
|
<div className="flex flex-col gap-2">
|
|
46
|
-
<EditorSwitcher
|
|
47
|
-
supportedTypes={supportedTypes}
|
|
48
|
-
value={type}
|
|
49
|
-
onChange={handleTypeChange}
|
|
50
|
-
/>
|
|
46
|
+
<EditorSwitcher supportedTypes={supportedTypes} value={type} onChange={handleTypeChange} />
|
|
51
47
|
|
|
52
48
|
{type === 'buffer' ? (
|
|
53
49
|
<BinaryValueEditor
|
|
@@ -8,22 +8,15 @@ import {
|
|
|
8
8
|
} from '@tanstack/react-query';
|
|
9
9
|
import type { RozeniteDevToolsRequestClient } from '@rozenite/plugin-bridge';
|
|
10
10
|
import { useCallback, useEffect, useMemo } from 'react';
|
|
11
|
-
import type {
|
|
12
|
-
StorageEventMap,
|
|
13
|
-
StorageListEntryPreviewsResponseEvent,
|
|
14
|
-
} from '../shared/messaging';
|
|
11
|
+
import type { StorageEventMap, StorageListEntryPreviewsResponseEvent } from '../shared/messaging';
|
|
15
12
|
import type { StorageEntryPreview, StorageTarget } from '../shared/types';
|
|
16
13
|
|
|
17
14
|
export const STORAGE_ENTRY_PREVIEW_PAGE_SIZE = 100;
|
|
18
15
|
export const STORAGE_ENTRY_PREVIEW_MAX_PAGES = 5;
|
|
19
16
|
|
|
20
|
-
type StorageRequestClient = Pick<
|
|
21
|
-
RozeniteDevToolsRequestClient<StorageEventMap>,
|
|
22
|
-
'request'
|
|
23
|
-
>;
|
|
17
|
+
type StorageRequestClient = Pick<RozeniteDevToolsRequestClient<StorageEventMap>, 'request'>;
|
|
24
18
|
|
|
25
|
-
export const normalizeStorageKeySearch = (search: string) =>
|
|
26
|
-
search.trim().toLowerCase();
|
|
19
|
+
export const normalizeStorageKeySearch = (search: string) => search.trim().toLowerCase();
|
|
27
20
|
|
|
28
21
|
export const storageEntryPreviewQueryKeyPrefix = (target: StorageTarget) =>
|
|
29
22
|
['storage-entry-previews', target.adapterId, target.storageId] as const;
|
|
@@ -72,9 +65,7 @@ export const dropStorageFullEntries = async (
|
|
|
72
65
|
key?: string,
|
|
73
66
|
) => {
|
|
74
67
|
const queryKey =
|
|
75
|
-
key == null
|
|
76
|
-
? storageFullEntryQueryKeyPrefix(target)
|
|
77
|
-
: storageFullEntryQueryKey(target, key);
|
|
68
|
+
key == null ? storageFullEntryQueryKeyPrefix(target) : storageFullEntryQueryKey(target, key);
|
|
78
69
|
|
|
79
70
|
await queryClient.cancelQueries({ queryKey, exact: key != null });
|
|
80
71
|
queryClient.removeQueries({ queryKey, exact: key != null });
|
|
@@ -101,11 +92,7 @@ export const useStorageEntryPreviews = ({
|
|
|
101
92
|
const queryKey = useMemo(
|
|
102
93
|
() =>
|
|
103
94
|
target
|
|
104
|
-
? storageEntryPreviewQueryKey(
|
|
105
|
-
target,
|
|
106
|
-
normalizedSearch,
|
|
107
|
-
keySortDirection,
|
|
108
|
-
)
|
|
95
|
+
? storageEntryPreviewQueryKey(target, normalizedSearch, keySortDirection)
|
|
109
96
|
: (['storage-entry-previews', 'no-target'] as const),
|
|
110
97
|
[keySortDirection, normalizedSearch, target?.adapterId, target?.storageId],
|
|
111
98
|
);
|
|
@@ -174,9 +161,7 @@ export const useStorageFullEntry = ({
|
|
|
174
161
|
gcTime: 0,
|
|
175
162
|
queryFn: ({ signal }) => {
|
|
176
163
|
if (!client || !target || key == null) {
|
|
177
|
-
throw new Error(
|
|
178
|
-
'A storage target, key, and request client are required.',
|
|
179
|
-
);
|
|
164
|
+
throw new Error('A storage target, key, and request client are required.');
|
|
180
165
|
}
|
|
181
166
|
|
|
182
167
|
return client.request({
|
package/src/ui/utils.ts
CHANGED
|
@@ -15,10 +15,7 @@ export const downloadJson = (data: unknown, filename: string): void => {
|
|
|
15
15
|
|
|
16
16
|
const sanitize = (value: string) => value.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
17
17
|
|
|
18
|
-
export const buildExportFilename = (
|
|
19
|
-
target: StorageTarget,
|
|
20
|
-
now: Date = new Date(),
|
|
21
|
-
): string => {
|
|
18
|
+
export const buildExportFilename = (target: StorageTarget, now: Date = new Date()): string => {
|
|
22
19
|
const yyyy = now.getFullYear().toString().padStart(4, '0');
|
|
23
20
|
const mm = (now.getMonth() + 1).toString().padStart(2, '0');
|
|
24
21
|
const dd = now.getDate().toString().padStart(2, '0');
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-content:"";--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-medium:500;--font-weight-semibold:600;--leading-snug:1.375;--radius-lg:var(--radius);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){*{outline-color:color-mix(in oklab,var(--ring) 50%,transparent)}}body{background-color:var(--background);color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background-color:var(--border);border-radius:var(--radius-lg);background-clip:padding-box;border:2px solid #0000}::-webkit-scrollbar-thumb:hover{background-color:var(--muted-foreground)}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing) * 4)}.right-1\.5{right:calc(var(--spacing) * 1.5)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0\.5{left:calc(var(--spacing) * .5)}.left-2\.5{left:calc(var(--spacing) * 2.5)}.z-10{z-index:10}.z-50{z-index:50}.row-0{grid-row:0}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-1{margin-inline:calc(var(--spacing) * 1)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3\.5{margin-left:calc(var(--spacing) * 3.5)}.ml-auto{margin-left:auto}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-16{height:calc(var(--spacing) * 16)}.h-full{height:100%}.h-screen{height:100vh}.max-h-20{max-height:calc(var(--spacing) * 20)}.max-h-24{max-height:calc(var(--spacing) * 24)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[300px\]{max-height:300px}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-28{min-height:calc(var(--spacing) * 28)}.min-h-56{min-height:calc(var(--spacing) * 56)}.min-h-\[120px\]{min-height:120px}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-56{width:calc(var(--spacing) * 56)}.w-80{width:calc(var(--spacing) * 80)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-2xl{max-width:var(--container-2xl)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-40{min-width:calc(var(--spacing) * 40)}.min-w-\[--anchor-width\]{min-width:--anchor-width}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-90{rotate:90deg}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.resize-y{resize:vertical}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) * .8)}.rounded-sm{border-radius:calc(var(--radius) * .6)}.border{border-style:var(--tw-border-style);border-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-border{border-color:var(--border)}.border-destructive\/40{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/40{border-color:color-mix(in oklab,var(--destructive) 40%,transparent)}}.border-destructive\/50{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/50{border-color:color-mix(in oklab,var(--destructive) 50%,transparent)}}.border-input{border-color:var(--input)}.border-ring{border-color:var(--ring)}.border-sidebar-border{border-color:var(--sidebar-border)}.bg-background,.bg-background\/70{background-color:var(--background)}@supports (color:color-mix(in lab,red,red)){.bg-background\/70{background-color:color-mix(in oklab,var(--background) 70%,transparent)}}.bg-border{background-color:var(--border)}.bg-card{background-color:var(--card)}.bg-destructive,.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/10{background-color:color-mix(in oklab,var(--destructive) 10%,transparent)}}.bg-foreground{background-color:var(--foreground)}.bg-muted{background-color:var(--muted)}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-sidebar{background-color:var(--sidebar)}.bg-transparent{background-color:#0000}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-0{padding-inline:calc(var(--spacing) * 0)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-14{padding-right:calc(var(--spacing) * 14)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.wrap-anywhere{overflow-wrap:anywhere}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--accent)}.text-accent-foreground{color:var(--accent-foreground)}.text-background{color:var(--background)}.text-card-foreground{color:var(--card-foreground)}.text-destructive{color:var(--destructive)}.text-destructive-foreground{color:var(--destructive-foreground)}.text-foreground{color:var(--foreground)}.text-muted{color:var(--muted)}.text-muted-foreground,.text-muted-foreground\/60{color:var(--muted-foreground)}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/60{color:color-mix(in oklab,var(--muted-foreground) 60%,transparent)}}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-sidebar-foreground,.text-sidebar-foreground\/60{color:var(--sidebar-foreground)}@supports (color:color-mix(in lab,red,red)){.text-sidebar-foreground\/60{color:color-mix(in oklab,var(--sidebar-foreground) 60%,transparent)}}.lowercase{text-transform:lowercase}.italic{font-style:italic}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[transform\,opacity\]{transition-property:transform,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[transform\,scale\,opacity\]{transition-property:transform,scale,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}.group-aria-\[orientation\=horizontal\]\/split-handle\:h-3:is(:where(.group\/split-handle)[aria-orientation=horizontal] *){height:calc(var(--spacing) * 3)}.group-aria-\[orientation\=horizontal\]\/split-handle\:w-4:is(:where(.group\/split-handle)[aria-orientation=horizontal] *){width:calc(var(--spacing) * 4)}.group-aria-\[orientation\=horizontal\]\/split-handle\:rotate-90:is(:where(.group\/split-handle)[aria-orientation=horizontal] *){rotate:90deg}.group-aria-\[orientation\=vertical\]\/split-handle\:h-4:is(:where(.group\/split-handle)[aria-orientation=vertical] *){height:calc(var(--spacing) * 4)}.group-aria-\[orientation\=vertical\]\/split-handle\:w-3:is(:where(.group\/split-handle)[aria-orientation=vertical] *){width:calc(var(--spacing) * 3)}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-y-0:after{content:var(--tw-content);inset-block:calc(var(--spacing) * 0)}.after\:left-1\/2:after{content:var(--tw-content);left:50%}.after\:w-1:after{content:var(--tw-content);width:calc(var(--spacing) * 1)}.after\:-translate-x-1\/2:after{content:var(--tw-content);--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media(hover:hover){.hover\:bg-accent:hover,.hover\:bg-accent\/50:hover{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-accent\/50:hover{background-color:color-mix(in oklab,var(--accent) 50%,transparent)}}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive) 90%,transparent)}}.hover\:bg-muted-foreground:hover{background-color:var(--muted-foreground)}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary) 90%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,var(--secondary) 80%,transparent)}}.hover\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-destructive:hover{color:var(--destructive)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-primary:hover{color:var(--primary)}.hover\:text-sidebar-accent-foreground:hover{color:var(--sidebar-accent-foreground)}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-ring:focus-visible,.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.focus-visible\:ring-sidebar-ring:focus-visible{--tw-ring-color:var(--sidebar-ring)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color:var(--background)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.aria-\[orientation\=horizontal\]\:h-px[aria-orientation=horizontal]{height:1px}.aria-\[orientation\=horizontal\]\:cursor-row-resize[aria-orientation=horizontal]{cursor:row-resize}.aria-\[orientation\=vertical\]\:w-px[aria-orientation=vertical]{width:1px}.aria-\[orientation\=vertical\]\:cursor-col-resize[aria-orientation=vertical]{cursor:col-resize}.data-\[checked\]\:translate-x-4[data-checked]{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[checked\]\:border-primary[data-checked]{border-color:var(--primary)}.data-\[checked\]\:bg-primary[data-checked]{background-color:var(--primary)}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[ending-style\]\:scale-90[data-ending-style]{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[ending-style\]\:scale-95[data-ending-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[ending-style\]\:opacity-0[data-ending-style]{opacity:0}.data-\[highlighted\]\:bg-accent[data-highlighted]{background-color:var(--accent)}.data-\[highlighted\]\:text-accent-foreground[data-highlighted]{color:var(--accent-foreground)}.data-\[selected\]\:bg-background[data-selected]{background-color:var(--background)}.data-\[selected\]\:bg-sidebar-accent[data-selected]{background-color:var(--sidebar-accent)}.data-\[selected\]\:font-medium[data-selected]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.data-\[selected\]\:text-foreground[data-selected]{color:var(--foreground)}.data-\[selected\]\:text-sidebar-accent-foreground[data-selected]{color:var(--sidebar-accent-foreground)}.data-\[selected\]\:shadow-xs[data-selected]{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[starting-style\]\:scale-90[data-starting-style]{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[starting-style\]\:scale-95[data-starting-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[starting-style\]\:opacity-0[data-starting-style]{opacity:0}@media(min-width:40rem){.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-3\.5 svg{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&\:\:-webkit-search-cancel-button\]\:appearance-none::-webkit-search-cancel-button{appearance:none}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--radius:0;--background:#fff;--foreground:#201f24;--card:#fff;--card-foreground:#201f24;--popover:#fff;--popover-foreground:#201f24;--primary:#8232ff;--primary-foreground:#fff;--secondary:#f9f8fd;--secondary-foreground:#201f24;--muted:#f9f8fd;--muted-foreground:#838289;--accent:#8232ff29;--accent-foreground:#201f24;--destructive:#fa7171;--destructive-foreground:#201f24;--border:#cfced5;--input:#cfced5;--ring:#8232ff;--sidebar:#fff;--sidebar-foreground:#201f24;--sidebar-primary:#8232ff;--sidebar-primary-foreground:#fff;--sidebar-accent:#8232ff29;--sidebar-accent-foreground:#201f24;--sidebar-border:#cfced5;--sidebar-ring:#8232ff}.dark{--background:#201f24;--foreground:#fff;--card:#201f24;--card-foreground:#fff;--popover:#2b2a2f;--popover-foreground:#fff;--primary:#8232ff;--primary-foreground:#fff;--secondary:#2b2a2f;--secondary-foreground:#fff;--muted:#2b2a2f;--muted-foreground:#cfced5;--accent:#8232ff33;--accent-foreground:#fff;--destructive:#fa7171;--destructive-foreground:#201f24;--border:#424145;--input:#424145;--ring:#8232ff;--sidebar:#201f24;--sidebar-foreground:#fff;--sidebar-primary:#8232ff;--sidebar-primary-foreground:#fff;--sidebar-accent:#8232ff33;--sidebar-accent-foreground:#fff;--sidebar-border:#424145;--sidebar-ring:#8232ff}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}
|