@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/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 [alertState, setAlertState] = useState<AlertState | null>(null);
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 handleDeleteEntry = () => {
316
- if (!client || !selectedTarget || !deleteKey) return;
317
- const key = deleteKey;
318
- setDeleteKey(null);
319
- mutateSelectedStorage(() =>
320
- client.send('delete-entry', {
321
- type: 'delete-entry',
322
- target: selectedTarget,
323
- key,
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 handlePurgeStorage = () => {
331
+ const handlePurgeClick = async () => {
329
332
  if (!client || !selectedTarget) return;
330
- setShowPurgeDialog(false);
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) => setAlertState({ title, message });
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 }) => <Badge variant="outline">{row.original.type}</Badge>,
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
- <Button
513
+ <IconButton
514
+ tone="neutral"
497
515
  variant="ghost"
498
- size="icon"
499
516
  onClick={() => setInteraction({ key: row.original.key, mode: 'edit' })}
500
- aria-label={`Edit value for ${row.original.key}`}
517
+ label={`Edit value for ${row.original.key}`}
501
518
  >
502
519
  <Edit3 className="h-3.5 w-3.5" />
503
- </Button>
504
- <Button
520
+ </IconButton>
521
+ <IconButton
522
+ tone="neutral"
505
523
  variant="ghost"
506
- size="icon"
507
- className="text-muted-foreground hover:text-destructive"
508
- onClick={() => setDeleteKey(row.original.key)}
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
- </Button>
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
- <PluginShell>
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
- adornment={<Database />}
571
- trailing={<Badge variant="secondary">{item.entryCount}</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-7 px-0"
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={() => setShowPurgeDialog(true)}
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-7 px-0 text-muted-foreground hover:text-destructive"
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-7 px-0"
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-7 px-0"
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-7 px-0"
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-destructive">
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
- <ConfirmDialog
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
- <StoragePanelContent />
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
- "baseUrl": ".",
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
  },