@rozenite/storage-plugin 2.1.0 → 2.2.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-D5z1BY8U.js +32 -0
- package/dist/devtools/assets/panel-DVlWRg6d.css +1 -0
- package/dist/devtools/panel.html +2 -2
- package/dist/rozenite.json +1 -1
- package/package.json +7 -7
- package/src/ui/__tests__/panel.test.tsx +14 -0
- package/src/ui/add-entry-dialog.tsx +63 -73
- package/src/ui/binary-value-editor.tsx +1 -1
- package/src/ui/edit-entry-dialog.tsx +59 -70
- package/src/ui/entry-detail-dialog.tsx +4 -2
- package/src/ui/format-value.tsx +1 -1
- package/src/ui/import-dialog.tsx +5 -5
- package/src/ui/panel.tsx +70 -88
- package/tsconfig.json +4 -7
- package/dist/devtools/assets/panel-BlDRXgVW.js +0 -32
- package/dist/devtools/assets/panel-DBweZnW5.css +0 -1
package/src/ui/panel.tsx
CHANGED
|
@@ -3,17 +3,17 @@ import type { ColumnDef, OnChangeFn, SortingState } from '@tanstack/react-table'
|
|
|
3
3
|
import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
4
4
|
import {
|
|
5
5
|
Badge,
|
|
6
|
-
Button,
|
|
7
|
-
ConfirmDialog,
|
|
8
6
|
EmptyState,
|
|
7
|
+
IconButton,
|
|
9
8
|
PluginShell,
|
|
10
9
|
SearchField,
|
|
11
10
|
Sidebar,
|
|
12
11
|
Split,
|
|
13
12
|
Toolbar,
|
|
13
|
+
useConfirmDialog,
|
|
14
14
|
VirtualizedDataTable,
|
|
15
15
|
} from '@rozenite/ui';
|
|
16
|
-
import { useEffect, useMemo, useRef, useState, type ChangeEvent } from 'react';
|
|
16
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent } from 'react';
|
|
17
17
|
import { Database, Download, Edit3, Plus, RefreshCw, Trash2, Upload } from 'lucide-react';
|
|
18
18
|
import type {
|
|
19
19
|
StorageDiscoverStoragesResponseEvent,
|
|
@@ -48,7 +48,6 @@ import {
|
|
|
48
48
|
import { buildExportFilename, downloadJson } from './utils';
|
|
49
49
|
import './globals.css';
|
|
50
50
|
|
|
51
|
-
type AlertState = { title: string; message: string };
|
|
52
51
|
type FullEntryInteraction = { key: string; mode: 'detail' | 'edit' };
|
|
53
52
|
|
|
54
53
|
const sameTarget = (a: StorageTarget, b: StorageTarget) =>
|
|
@@ -79,14 +78,12 @@ function StoragePanelContent() {
|
|
|
79
78
|
const [keySortDirection, setKeySortDirection] = useState<'ascending' | 'descending'>('ascending');
|
|
80
79
|
const [interaction, setInteraction] = useState<FullEntryInteraction | null>(null);
|
|
81
80
|
const [showAddDialog, setShowAddDialog] = useState(false);
|
|
82
|
-
const [deleteKey, setDeleteKey] = useState<string | null>(null);
|
|
83
|
-
const [showPurgeDialog, setShowPurgeDialog] = useState(false);
|
|
84
81
|
const [virtualListVersion, setVirtualListVersion] = useState(0);
|
|
85
82
|
const [exportState, setExportState] = useState<
|
|
86
83
|
{ status: 'idle' } | { status: 'loading' } | { status: 'error'; message: string }
|
|
87
84
|
>({ status: 'idle' });
|
|
88
85
|
const [importFlight, setImportFlight] = useState<ImportFlightState | null>(null);
|
|
89
|
-
const
|
|
86
|
+
const confirm = useConfirmDialog();
|
|
90
87
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
91
88
|
const selectedTargetRef = useRef<StorageTarget | null>(null);
|
|
92
89
|
const discoveryRequestIdRef = useRef(0);
|
|
@@ -262,8 +259,6 @@ function StoragePanelContent() {
|
|
|
262
259
|
importPreviewAbortControllerRef.current = null;
|
|
263
260
|
activeImportRequestIdRef.current = null;
|
|
264
261
|
setExportState({ status: 'idle' });
|
|
265
|
-
setDeleteKey(null);
|
|
266
|
-
setShowPurgeDialog(false);
|
|
267
262
|
setInteraction(null);
|
|
268
263
|
setImportFlight(null);
|
|
269
264
|
}, [selectedTarget]);
|
|
@@ -312,29 +307,47 @@ function StoragePanelContent() {
|
|
|
312
307
|
);
|
|
313
308
|
};
|
|
314
309
|
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
310
|
+
const handleDeleteClick = useCallback(
|
|
311
|
+
async (key: string) => {
|
|
312
|
+
if (!client || !selectedTarget) return;
|
|
313
|
+
const confirmed = await confirm({
|
|
314
|
+
title: 'Delete Entry',
|
|
315
|
+
description: `Are you sure you want to delete the entry "${key}"?`,
|
|
316
|
+
tone: 'danger',
|
|
317
|
+
confirmLabel: 'Delete',
|
|
318
|
+
});
|
|
319
|
+
if (!confirmed) return;
|
|
320
|
+
mutateSelectedStorage(() =>
|
|
321
|
+
client.send('delete-entry', {
|
|
322
|
+
type: 'delete-entry',
|
|
323
|
+
target: selectedTarget,
|
|
324
|
+
key,
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
},
|
|
328
|
+
[client, selectedTarget, confirm],
|
|
329
|
+
);
|
|
327
330
|
|
|
328
|
-
const
|
|
331
|
+
const handlePurgeClick = async () => {
|
|
329
332
|
if (!client || !selectedTarget) return;
|
|
330
|
-
|
|
333
|
+
const confirmed = await confirm({
|
|
334
|
+
title: 'Purge Storage',
|
|
335
|
+
description: selectedDescriptor
|
|
336
|
+
? `Are you sure you want to remove all entries from "${selectedDescriptor.storageName}"? This action cannot be undone.`
|
|
337
|
+
: 'Are you sure you want to remove all entries from this storage? This action cannot be undone.',
|
|
338
|
+
tone: 'danger',
|
|
339
|
+
confirmLabel: 'Purge',
|
|
340
|
+
});
|
|
341
|
+
if (!confirmed) return;
|
|
331
342
|
client.send('purge-storage', {
|
|
332
343
|
type: 'purge-storage',
|
|
333
344
|
target: selectedTarget,
|
|
334
345
|
});
|
|
335
346
|
};
|
|
336
347
|
|
|
337
|
-
const showAlert = (title: string, message: string) =>
|
|
348
|
+
const showAlert = async (title: string, message: string) => {
|
|
349
|
+
await confirm({ variant: 'alert', title, description: message });
|
|
350
|
+
};
|
|
338
351
|
|
|
339
352
|
const handleImportClick = () => {
|
|
340
353
|
if (fileInputRef.current) {
|
|
@@ -350,12 +363,12 @@ function StoragePanelContent() {
|
|
|
350
363
|
try {
|
|
351
364
|
raw = JSON.parse(await file.text());
|
|
352
365
|
} catch (error) {
|
|
353
|
-
showAlert('Could not read file', error instanceof Error ? error.message : String(error));
|
|
366
|
+
void showAlert('Could not read file', error instanceof Error ? error.message : String(error));
|
|
354
367
|
return;
|
|
355
368
|
}
|
|
356
369
|
const parsed = parseSnapshot(raw);
|
|
357
370
|
if (!parsed.ok) {
|
|
358
|
-
showAlert('Invalid snapshot', `${parsed.error.path}: ${parsed.error.message}`);
|
|
371
|
+
void showAlert('Invalid snapshot', `${parsed.error.path}: ${parsed.error.message}`);
|
|
359
372
|
return;
|
|
360
373
|
}
|
|
361
374
|
importPreviewAbortControllerRef.current?.abort();
|
|
@@ -386,7 +399,7 @@ function StoragePanelContent() {
|
|
|
386
399
|
});
|
|
387
400
|
} catch {
|
|
388
401
|
if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
|
|
389
|
-
showAlert(
|
|
402
|
+
void showAlert(
|
|
390
403
|
'Could not preview import',
|
|
391
404
|
'Could not inspect the selected storage. Please try again.',
|
|
392
405
|
);
|
|
@@ -462,7 +475,11 @@ function StoragePanelContent() {
|
|
|
462
475
|
accessorKey: 'type',
|
|
463
476
|
header: 'Type',
|
|
464
477
|
enableSorting: false,
|
|
465
|
-
cell: ({ row }) =>
|
|
478
|
+
cell: ({ row }) => (
|
|
479
|
+
<Badge tone="neutral" variant="outline">
|
|
480
|
+
{row.original.type}
|
|
481
|
+
</Badge>
|
|
482
|
+
),
|
|
466
483
|
},
|
|
467
484
|
{
|
|
468
485
|
id: 'preview',
|
|
@@ -493,28 +510,28 @@ function StoragePanelContent() {
|
|
|
493
510
|
enableSorting: false,
|
|
494
511
|
cell: ({ row }) => (
|
|
495
512
|
<div className="flex items-center gap-1" onClick={(event) => event.stopPropagation()}>
|
|
496
|
-
<
|
|
513
|
+
<IconButton
|
|
514
|
+
tone="neutral"
|
|
497
515
|
variant="ghost"
|
|
498
|
-
size="icon"
|
|
499
516
|
onClick={() => setInteraction({ key: row.original.key, mode: 'edit' })}
|
|
500
|
-
|
|
517
|
+
label={`Edit value for ${row.original.key}`}
|
|
501
518
|
>
|
|
502
519
|
<Edit3 className="h-3.5 w-3.5" />
|
|
503
|
-
</
|
|
504
|
-
<
|
|
520
|
+
</IconButton>
|
|
521
|
+
<IconButton
|
|
522
|
+
tone="neutral"
|
|
505
523
|
variant="ghost"
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
aria-label={`Delete entry ${row.original.key}`}
|
|
524
|
+
className="text-muted-foreground hover:text-danger"
|
|
525
|
+
onClick={() => void handleDeleteClick(row.original.key)}
|
|
526
|
+
label={`Delete entry ${row.original.key}`}
|
|
510
527
|
>
|
|
511
528
|
<Trash2 className="h-3.5 w-3.5" />
|
|
512
|
-
</
|
|
529
|
+
</IconButton>
|
|
513
530
|
</div>
|
|
514
531
|
),
|
|
515
532
|
},
|
|
516
533
|
],
|
|
517
|
-
[],
|
|
534
|
+
[handleDeleteClick],
|
|
518
535
|
);
|
|
519
536
|
const sorting: SortingState = [{ id: 'key', desc: keySortDirection === 'descending' }];
|
|
520
537
|
const handleSortingChange: OnChangeFn<SortingState> = (updater) => {
|
|
@@ -551,7 +568,7 @@ function StoragePanelContent() {
|
|
|
551
568
|
const selectedStorageViewId = selectedTarget ? getStorageViewId(selectedTarget) : '';
|
|
552
569
|
|
|
553
570
|
return (
|
|
554
|
-
|
|
571
|
+
<>
|
|
555
572
|
<PluginShell.Body>
|
|
556
573
|
<Split direction="horizontal" autoSaveId="storage">
|
|
557
574
|
<Split.Pane defaultSize={22} minSize={15} maxSize={40}>
|
|
@@ -567,8 +584,8 @@ function StoragePanelContent() {
|
|
|
567
584
|
<Sidebar.Item
|
|
568
585
|
key={item.viewId}
|
|
569
586
|
selected={item.viewId === selectedStorageViewId}
|
|
570
|
-
|
|
571
|
-
trailing={<Badge
|
|
587
|
+
leading={<Database />}
|
|
588
|
+
trailing={<Badge tone="neutral">{item.entryCount}</Badge>}
|
|
572
589
|
onClick={() => {
|
|
573
590
|
const descriptor = descriptors.find(
|
|
574
591
|
(candidate) => getStorageViewId(candidate.target) === item.viewId,
|
|
@@ -594,16 +611,16 @@ function StoragePanelContent() {
|
|
|
594
611
|
disabled={!selectedDescriptor}
|
|
595
612
|
aria-label="Add entry"
|
|
596
613
|
title="Add entry"
|
|
597
|
-
className="w-
|
|
614
|
+
className="w-6 px-0"
|
|
598
615
|
>
|
|
599
616
|
<Plus className="h-3.5 w-3.5" />
|
|
600
617
|
</Toolbar.Button>
|
|
601
618
|
<Toolbar.Button
|
|
602
|
-
onClick={() =>
|
|
619
|
+
onClick={() => void handlePurgeClick()}
|
|
603
620
|
disabled={!client || !selectedTarget || previews.isFetching}
|
|
604
621
|
aria-label="Purge storage"
|
|
605
622
|
title="Purge storage"
|
|
606
|
-
className="w-
|
|
623
|
+
className="w-6 px-0 text-muted-foreground hover:text-danger"
|
|
607
624
|
>
|
|
608
625
|
<Trash2 className="h-3.5 w-3.5" />
|
|
609
626
|
</Toolbar.Button>
|
|
@@ -615,7 +632,7 @@ function StoragePanelContent() {
|
|
|
615
632
|
disabled={!selectedTarget || previews.isFetching}
|
|
616
633
|
aria-label="Refresh storage"
|
|
617
634
|
title="Refresh storage"
|
|
618
|
-
className="w-
|
|
635
|
+
className="w-6 px-0"
|
|
619
636
|
>
|
|
620
637
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
621
638
|
</Toolbar.Button>
|
|
@@ -627,7 +644,7 @@ function StoragePanelContent() {
|
|
|
627
644
|
disabled={!selectedDescriptor}
|
|
628
645
|
aria-label="Import storage"
|
|
629
646
|
title="Import storage"
|
|
630
|
-
className="w-
|
|
647
|
+
className="w-6 px-0"
|
|
631
648
|
>
|
|
632
649
|
<Upload className="h-3.5 w-3.5" />
|
|
633
650
|
</Toolbar.Button>
|
|
@@ -640,7 +657,7 @@ function StoragePanelContent() {
|
|
|
640
657
|
title={
|
|
641
658
|
exportState.status === 'loading' ? 'Exporting storage' : 'Export storage'
|
|
642
659
|
}
|
|
643
|
-
className="w-
|
|
660
|
+
className="w-6 px-0"
|
|
644
661
|
>
|
|
645
662
|
<Download className="h-3.5 w-3.5" />
|
|
646
663
|
</Toolbar.Button>
|
|
@@ -656,7 +673,7 @@ function StoragePanelContent() {
|
|
|
656
673
|
/>
|
|
657
674
|
</div>
|
|
658
675
|
{exportState.status === 'error' ? (
|
|
659
|
-
<span role="alert" className="text-xs text-
|
|
676
|
+
<span role="alert" className="text-xs text-danger">
|
|
660
677
|
{exportState.message}
|
|
661
678
|
</span>
|
|
662
679
|
) : null}
|
|
@@ -732,51 +749,16 @@ function StoragePanelContent() {
|
|
|
732
749
|
onCancel={() => setImportFlight(null)}
|
|
733
750
|
onClose={() => setImportFlight(null)}
|
|
734
751
|
/>
|
|
735
|
-
|
|
736
|
-
open={deleteKey !== null}
|
|
737
|
-
onOpenChange={(open) => {
|
|
738
|
-
if (!open) setDeleteKey(null);
|
|
739
|
-
}}
|
|
740
|
-
variant="confirm"
|
|
741
|
-
destructive
|
|
742
|
-
title="Delete Entry"
|
|
743
|
-
description={
|
|
744
|
-
deleteKey ? `Are you sure you want to delete the entry "${deleteKey}"?` : undefined
|
|
745
|
-
}
|
|
746
|
-
confirmLabel="Delete"
|
|
747
|
-
onConfirm={handleDeleteEntry}
|
|
748
|
-
/>
|
|
749
|
-
<ConfirmDialog
|
|
750
|
-
open={showPurgeDialog}
|
|
751
|
-
onOpenChange={setShowPurgeDialog}
|
|
752
|
-
variant="confirm"
|
|
753
|
-
destructive
|
|
754
|
-
title="Purge Storage"
|
|
755
|
-
description={
|
|
756
|
-
selectedDescriptor
|
|
757
|
-
? `Are you sure you want to remove all entries from "${selectedDescriptor.storageName}"? This action cannot be undone.`
|
|
758
|
-
: 'Are you sure you want to remove all entries from this storage? This action cannot be undone.'
|
|
759
|
-
}
|
|
760
|
-
confirmLabel="Purge"
|
|
761
|
-
onConfirm={handlePurgeStorage}
|
|
762
|
-
/>
|
|
763
|
-
<ConfirmDialog
|
|
764
|
-
open={alertState !== null}
|
|
765
|
-
onOpenChange={(open) => {
|
|
766
|
-
if (!open) setAlertState(null);
|
|
767
|
-
}}
|
|
768
|
-
variant="alert"
|
|
769
|
-
title={alertState?.title ?? ''}
|
|
770
|
-
description={alertState?.message}
|
|
771
|
-
/>
|
|
772
|
-
</PluginShell>
|
|
752
|
+
</>
|
|
773
753
|
);
|
|
774
754
|
}
|
|
775
755
|
|
|
776
756
|
export default function StoragePanel() {
|
|
777
757
|
return (
|
|
778
758
|
<StorageQueryClientProvider>
|
|
779
|
-
<
|
|
759
|
+
<PluginShell>
|
|
760
|
+
<StoragePanelContent />
|
|
761
|
+
</PluginShell>
|
|
780
762
|
</StorageQueryClientProvider>
|
|
781
763
|
);
|
|
782
764
|
}
|
package/tsconfig.json
CHANGED
|
@@ -11,13 +11,7 @@
|
|
|
11
11
|
"noFallthroughCasesInSwitch": true,
|
|
12
12
|
"module": "ESNext",
|
|
13
13
|
"moduleResolution": "bundler",
|
|
14
|
-
"
|
|
15
|
-
"paths": {
|
|
16
|
-
"@rozenite/agent-bridge": ["../agent-bridge/src/index.ts"],
|
|
17
|
-
"@rozenite/agent-shared": ["../agent-shared/src/index.ts"],
|
|
18
|
-
"@rozenite/plugin-bridge": ["../plugin-bridge/src/index.ts"],
|
|
19
|
-
"@rozenite/ui": ["../ui/src/index.ts"]
|
|
20
|
-
},
|
|
14
|
+
"customConditions": ["development"],
|
|
21
15
|
"resolveJsonModule": true,
|
|
22
16
|
"isolatedModules": true,
|
|
23
17
|
"noEmit": true,
|
|
@@ -26,6 +20,9 @@
|
|
|
26
20
|
"include": ["src/**/*", "react-native.ts", "sdk.ts", "rozenite.config.ts"],
|
|
27
21
|
"exclude": ["node_modules", "dist", "build"],
|
|
28
22
|
"references": [
|
|
23
|
+
{
|
|
24
|
+
"path": "../agent-bridge"
|
|
25
|
+
},
|
|
29
26
|
{
|
|
30
27
|
"path": "../plugin-bridge"
|
|
31
28
|
},
|