@rozenite/storage-plugin 2.0.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 +36 -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/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 +50 -63
- 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 +66 -87
- package/src/ui/binary-value-editor-state.ts +6 -24
- package/src/ui/binary-value-editor.tsx +6 -18
- package/src/ui/binary.ts +6 -19
- package/src/ui/edit-entry-dialog.tsx +59 -78
- package/src/ui/editor-switcher.tsx +2 -11
- package/src/ui/entry-detail-dialog.tsx +9 -16
- package/src/ui/format-value.tsx +2 -9
- package/src/ui/import-dialog.tsx +12 -24
- package/src/ui/large-value-viewer-state.ts +2 -9
- package/src/ui/large-value-viewer.tsx +3 -9
- package/src/ui/panel.tsx +129 -235
- 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/tsconfig.json +4 -7
- 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,32 +1,20 @@
|
|
|
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,
|
|
10
|
-
Button,
|
|
11
|
-
ConfirmDialog,
|
|
12
6
|
EmptyState,
|
|
7
|
+
IconButton,
|
|
13
8
|
PluginShell,
|
|
14
9
|
SearchField,
|
|
15
10
|
Sidebar,
|
|
16
11
|
Split,
|
|
17
12
|
Toolbar,
|
|
13
|
+
useConfirmDialog,
|
|
18
14
|
VirtualizedDataTable,
|
|
19
15
|
} from '@rozenite/ui';
|
|
20
|
-
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';
|
|
16
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent } from 'react';
|
|
17
|
+
import { Database, Download, Edit3, Plus, RefreshCw, Trash2, Upload } from 'lucide-react';
|
|
30
18
|
import type {
|
|
31
19
|
StorageDiscoverStoragesResponseEvent,
|
|
32
20
|
StorageEventMap,
|
|
@@ -60,15 +48,12 @@ import {
|
|
|
60
48
|
import { buildExportFilename, downloadJson } from './utils';
|
|
61
49
|
import './globals.css';
|
|
62
50
|
|
|
63
|
-
type AlertState = { title: string; message: string };
|
|
64
51
|
type FullEntryInteraction = { key: string; mode: 'detail' | 'edit' };
|
|
65
52
|
|
|
66
53
|
const sameTarget = (a: StorageTarget, b: StorageTarget) =>
|
|
67
54
|
a.adapterId === b.adapterId && a.storageId === b.storageId;
|
|
68
55
|
|
|
69
|
-
const getEntryTypeFromValue = (
|
|
70
|
-
value: StorageEntryValue,
|
|
71
|
-
): StorageEntry['type'] => {
|
|
56
|
+
const getEntryTypeFromValue = (value: StorageEntryValue): StorageEntry['type'] => {
|
|
72
57
|
if (typeof value === 'string') return 'string';
|
|
73
58
|
if (typeof value === 'number') return 'number';
|
|
74
59
|
if (typeof value === 'boolean') return 'boolean';
|
|
@@ -88,29 +73,17 @@ const useDebouncedValue = (value: string, delay = 250) => {
|
|
|
88
73
|
|
|
89
74
|
function StoragePanelContent() {
|
|
90
75
|
const [descriptors, setDescriptors] = useState<StorageDescriptor[]>([]);
|
|
91
|
-
const [selectedTarget, setSelectedTarget] = useState<StorageTarget | null>(
|
|
92
|
-
null,
|
|
93
|
-
);
|
|
76
|
+
const [selectedTarget, setSelectedTarget] = useState<StorageTarget | null>(null);
|
|
94
77
|
const [searchTerm, setSearchTerm] = useState('');
|
|
95
|
-
const [keySortDirection, setKeySortDirection] = useState<
|
|
96
|
-
|
|
97
|
-
>('ascending');
|
|
98
|
-
const [interaction, setInteraction] = useState<FullEntryInteraction | null>(
|
|
99
|
-
null,
|
|
100
|
-
);
|
|
78
|
+
const [keySortDirection, setKeySortDirection] = useState<'ascending' | 'descending'>('ascending');
|
|
79
|
+
const [interaction, setInteraction] = useState<FullEntryInteraction | null>(null);
|
|
101
80
|
const [showAddDialog, setShowAddDialog] = useState(false);
|
|
102
|
-
const [deleteKey, setDeleteKey] = useState<string | null>(null);
|
|
103
|
-
const [showPurgeDialog, setShowPurgeDialog] = useState(false);
|
|
104
81
|
const [virtualListVersion, setVirtualListVersion] = useState(0);
|
|
105
82
|
const [exportState, setExportState] = useState<
|
|
106
|
-
| { status: '
|
|
107
|
-
| { status: 'loading' }
|
|
108
|
-
| { status: 'error'; message: string }
|
|
83
|
+
{ status: 'idle' } | { status: 'loading' } | { status: 'error'; message: string }
|
|
109
84
|
>({ status: 'idle' });
|
|
110
|
-
const [importFlight, setImportFlight] = useState<ImportFlightState | null>(
|
|
111
|
-
|
|
112
|
-
);
|
|
113
|
-
const [alertState, setAlertState] = useState<AlertState | null>(null);
|
|
85
|
+
const [importFlight, setImportFlight] = useState<ImportFlightState | null>(null);
|
|
86
|
+
const confirm = useConfirmDialog();
|
|
114
87
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
115
88
|
const selectedTargetRef = useRef<StorageTarget | null>(null);
|
|
116
89
|
const discoveryRequestIdRef = useRef(0);
|
|
@@ -127,8 +100,7 @@ function StoragePanelContent() {
|
|
|
127
100
|
const queryClient = useQueryClient();
|
|
128
101
|
const debouncedSearch = useDebouncedValue(searchTerm);
|
|
129
102
|
const selectedDescriptor = descriptors.find(
|
|
130
|
-
(descriptor) =>
|
|
131
|
-
selectedTarget && sameTarget(descriptor.target, selectedTarget),
|
|
103
|
+
(descriptor) => selectedTarget && sameTarget(descriptor.target, selectedTarget),
|
|
132
104
|
);
|
|
133
105
|
const previews = useStorageEntryPreviews({
|
|
134
106
|
client,
|
|
@@ -146,10 +118,7 @@ function StoragePanelContent() {
|
|
|
146
118
|
const supportedTypes = selectedDescriptor?.capabilities.supportedTypes ?? [];
|
|
147
119
|
const sidebarGroups = useMemo(() => {
|
|
148
120
|
const storages = new Map(
|
|
149
|
-
descriptors.map((descriptor) => [
|
|
150
|
-
getStorageViewId(descriptor.target),
|
|
151
|
-
descriptor,
|
|
152
|
-
]),
|
|
121
|
+
descriptors.map((descriptor) => [getStorageViewId(descriptor.target), descriptor]),
|
|
153
122
|
);
|
|
154
123
|
return buildStorageSidebarGroups(storages);
|
|
155
124
|
}, [descriptors]);
|
|
@@ -169,17 +138,21 @@ function StoragePanelContent() {
|
|
|
169
138
|
useEffect(() => {
|
|
170
139
|
if (!client) return;
|
|
171
140
|
|
|
141
|
+
const requestDiscovery = () => {
|
|
142
|
+
discoveryRequestIdRef.current += 1;
|
|
143
|
+
client.send('discover-storages', {
|
|
144
|
+
type: 'discover-storages',
|
|
145
|
+
requestId: `discovery-${discoveryRequestIdRef.current}`,
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
172
149
|
const descriptorsSubscription = client.onMessage(
|
|
173
150
|
'storage-descriptors',
|
|
174
151
|
(event: StorageDiscoverStoragesResponseEvent) => {
|
|
175
|
-
if (event.requestId !== `discovery-${discoveryRequestIdRef.current}`)
|
|
176
|
-
return;
|
|
152
|
+
if (event.requestId !== `discovery-${discoveryRequestIdRef.current}`) return;
|
|
177
153
|
setDescriptors(event.storages);
|
|
178
154
|
setSelectedTarget((previous) =>
|
|
179
|
-
previous &&
|
|
180
|
-
event.storages.some((descriptor) =>
|
|
181
|
-
sameTarget(descriptor.target, previous),
|
|
182
|
-
)
|
|
155
|
+
previous && event.storages.some((descriptor) => sameTarget(descriptor.target, previous))
|
|
183
156
|
? previous
|
|
184
157
|
: (event.storages[0]?.target ?? null),
|
|
185
158
|
);
|
|
@@ -234,23 +207,16 @@ function StoragePanelContent() {
|
|
|
234
207
|
'storage-invalidated',
|
|
235
208
|
(event: StorageInvalidatedEvent) => {
|
|
236
209
|
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
|
-
) {
|
|
210
|
+
setDescriptors((current) =>
|
|
211
|
+
current.map((descriptor) =>
|
|
212
|
+
sameTarget(descriptor.target, event.target)
|
|
213
|
+
? { ...descriptor, entryCount: event.entryCount }
|
|
214
|
+
: descriptor,
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
if (selectedTargetRef.current && sameTarget(event.target, selectedTargetRef.current)) {
|
|
250
218
|
setInteraction((current) =>
|
|
251
|
-
current && (event.key == null || current.key === event.key)
|
|
252
|
-
? null
|
|
253
|
-
: current,
|
|
219
|
+
current && (event.key == null || current.key === event.key) ? null : current,
|
|
254
220
|
);
|
|
255
221
|
}
|
|
256
222
|
|
|
@@ -259,10 +225,7 @@ function StoragePanelContent() {
|
|
|
259
225
|
pendingInvalidationsRef.current.add(targetId);
|
|
260
226
|
queueMicrotask(() => {
|
|
261
227
|
pendingInvalidationsRef.current.delete(targetId);
|
|
262
|
-
if (
|
|
263
|
-
selectedTargetRef.current &&
|
|
264
|
-
sameTarget(event.target, selectedTargetRef.current)
|
|
265
|
-
) {
|
|
228
|
+
if (selectedTargetRef.current && sameTarget(event.target, selectedTargetRef.current)) {
|
|
266
229
|
setVirtualListVersion((version) => version + 1);
|
|
267
230
|
}
|
|
268
231
|
void invalidateStorageEntryPreviews(queryClient, event.target);
|
|
@@ -270,16 +233,21 @@ function StoragePanelContent() {
|
|
|
270
233
|
},
|
|
271
234
|
);
|
|
272
235
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
236
|
+
// The device announces itself once it is listening. It reconnects on every
|
|
237
|
+
// app reload, so discovery has to run again — the descriptors and cached
|
|
238
|
+
// entries the panel holds belong to the previous JS context.
|
|
239
|
+
const deviceReadySubscription = client.onMessage('device-ready', () => {
|
|
240
|
+
void queryClient.resetQueries();
|
|
241
|
+
requestDiscovery();
|
|
277
242
|
});
|
|
243
|
+
|
|
244
|
+
requestDiscovery();
|
|
278
245
|
return () => {
|
|
279
246
|
descriptorsSubscription.remove();
|
|
280
247
|
importProgressSubscription.remove();
|
|
281
248
|
importResultSubscription.remove();
|
|
282
249
|
invalidationSubscription.remove();
|
|
250
|
+
deviceReadySubscription.remove();
|
|
283
251
|
};
|
|
284
252
|
}, [client, queryClient]);
|
|
285
253
|
|
|
@@ -291,8 +259,6 @@ function StoragePanelContent() {
|
|
|
291
259
|
importPreviewAbortControllerRef.current = null;
|
|
292
260
|
activeImportRequestIdRef.current = null;
|
|
293
261
|
setExportState({ status: 'idle' });
|
|
294
|
-
setDeleteKey(null);
|
|
295
|
-
setShowPurgeDialog(false);
|
|
296
262
|
setInteraction(null);
|
|
297
263
|
setImportFlight(null);
|
|
298
264
|
}, [selectedTarget]);
|
|
@@ -310,11 +276,7 @@ function StoragePanelContent() {
|
|
|
310
276
|
};
|
|
311
277
|
|
|
312
278
|
const handleValueChange = (key: string, newValue: StorageEntryValue) => {
|
|
313
|
-
if (
|
|
314
|
-
!client ||
|
|
315
|
-
!selectedTarget ||
|
|
316
|
-
!supportedTypes.includes(getEntryTypeFromValue(newValue))
|
|
317
|
-
)
|
|
279
|
+
if (!client || !selectedTarget || !supportedTypes.includes(getEntryTypeFromValue(newValue)))
|
|
318
280
|
return;
|
|
319
281
|
const type = getEntryTypeFromValue(newValue);
|
|
320
282
|
const entry =
|
|
@@ -345,30 +307,47 @@ function StoragePanelContent() {
|
|
|
345
307
|
);
|
|
346
308
|
};
|
|
347
309
|
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
+
);
|
|
360
330
|
|
|
361
|
-
const
|
|
331
|
+
const handlePurgeClick = async () => {
|
|
362
332
|
if (!client || !selectedTarget) return;
|
|
363
|
-
|
|
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;
|
|
364
342
|
client.send('purge-storage', {
|
|
365
343
|
type: 'purge-storage',
|
|
366
344
|
target: selectedTarget,
|
|
367
345
|
});
|
|
368
346
|
};
|
|
369
347
|
|
|
370
|
-
const showAlert = (title: string, message: string) =>
|
|
371
|
-
|
|
348
|
+
const showAlert = async (title: string, message: string) => {
|
|
349
|
+
await confirm({ variant: 'alert', title, description: message });
|
|
350
|
+
};
|
|
372
351
|
|
|
373
352
|
const handleImportClick = () => {
|
|
374
353
|
if (fileInputRef.current) {
|
|
@@ -384,18 +363,12 @@ function StoragePanelContent() {
|
|
|
384
363
|
try {
|
|
385
364
|
raw = JSON.parse(await file.text());
|
|
386
365
|
} catch (error) {
|
|
387
|
-
showAlert(
|
|
388
|
-
'Could not read file',
|
|
389
|
-
error instanceof Error ? error.message : String(error),
|
|
390
|
-
);
|
|
366
|
+
void showAlert('Could not read file', error instanceof Error ? error.message : String(error));
|
|
391
367
|
return;
|
|
392
368
|
}
|
|
393
369
|
const parsed = parseSnapshot(raw);
|
|
394
370
|
if (!parsed.ok) {
|
|
395
|
-
showAlert(
|
|
396
|
-
'Invalid snapshot',
|
|
397
|
-
`${parsed.error.path}: ${parsed.error.message}`,
|
|
398
|
-
);
|
|
371
|
+
void showAlert('Invalid snapshot', `${parsed.error.path}: ${parsed.error.message}`);
|
|
399
372
|
return;
|
|
400
373
|
}
|
|
401
374
|
importPreviewAbortControllerRef.current?.abort();
|
|
@@ -410,10 +383,7 @@ function StoragePanelContent() {
|
|
|
410
383
|
payload: { type: 'preview-import', target, snapshot: parsed.snapshot },
|
|
411
384
|
signal: controller.signal,
|
|
412
385
|
});
|
|
413
|
-
if (
|
|
414
|
-
controller.signal.aborted ||
|
|
415
|
-
!sameTarget(target, selectedTargetRef.current ?? target)
|
|
416
|
-
)
|
|
386
|
+
if (controller.signal.aborted || !sameTarget(target, selectedTargetRef.current ?? target))
|
|
417
387
|
return;
|
|
418
388
|
setImportFlight({
|
|
419
389
|
phase: 'preview',
|
|
@@ -428,11 +398,8 @@ function StoragePanelContent() {
|
|
|
428
398
|
),
|
|
429
399
|
});
|
|
430
400
|
} catch {
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
sameTarget(target, selectedTargetRef.current ?? target)
|
|
434
|
-
) {
|
|
435
|
-
showAlert(
|
|
401
|
+
if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
|
|
402
|
+
void showAlert(
|
|
436
403
|
'Could not preview import',
|
|
437
404
|
'Could not inspect the selected storage. Please try again.',
|
|
438
405
|
);
|
|
@@ -476,26 +443,19 @@ function StoragePanelContent() {
|
|
|
476
443
|
payload: { type: 'export-snapshot', target },
|
|
477
444
|
signal: controller.signal,
|
|
478
445
|
});
|
|
479
|
-
if (
|
|
480
|
-
!controller.signal.aborted &&
|
|
481
|
-
sameTarget(target, selectedTargetRef.current ?? target)
|
|
482
|
-
) {
|
|
446
|
+
if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
|
|
483
447
|
downloadJson(response.snapshot, buildExportFilename(target));
|
|
484
448
|
setExportState({ status: 'idle' });
|
|
485
449
|
}
|
|
486
450
|
} catch {
|
|
487
|
-
if (
|
|
488
|
-
!controller.signal.aborted &&
|
|
489
|
-
sameTarget(target, selectedTargetRef.current ?? target)
|
|
490
|
-
) {
|
|
451
|
+
if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
|
|
491
452
|
setExportState({
|
|
492
453
|
status: 'error',
|
|
493
454
|
message: 'Could not export the selected storage. Please try again.',
|
|
494
455
|
});
|
|
495
456
|
}
|
|
496
457
|
} finally {
|
|
497
|
-
if (exportAbortControllerRef.current === controller)
|
|
498
|
-
exportAbortControllerRef.current = null;
|
|
458
|
+
if (exportAbortControllerRef.current === controller) exportAbortControllerRef.current = null;
|
|
499
459
|
}
|
|
500
460
|
};
|
|
501
461
|
|
|
@@ -507,9 +467,7 @@ function StoragePanelContent() {
|
|
|
507
467
|
header: 'Key',
|
|
508
468
|
enableSorting: true,
|
|
509
469
|
cell: ({ row }) => (
|
|
510
|
-
<span className="font-mono text-sm text-foreground">
|
|
511
|
-
{row.original.key}
|
|
512
|
-
</span>
|
|
470
|
+
<span className="font-mono text-sm text-foreground">{row.original.key}</span>
|
|
513
471
|
),
|
|
514
472
|
},
|
|
515
473
|
{
|
|
@@ -517,7 +475,11 @@ function StoragePanelContent() {
|
|
|
517
475
|
accessorKey: 'type',
|
|
518
476
|
header: 'Type',
|
|
519
477
|
enableSorting: false,
|
|
520
|
-
cell: ({ row }) =>
|
|
478
|
+
cell: ({ row }) => (
|
|
479
|
+
<Badge tone="neutral" variant="outline">
|
|
480
|
+
{row.original.type}
|
|
481
|
+
</Badge>
|
|
482
|
+
),
|
|
521
483
|
},
|
|
522
484
|
{
|
|
523
485
|
id: 'preview',
|
|
@@ -532,9 +494,7 @@ function StoragePanelContent() {
|
|
|
532
494
|
<span
|
|
533
495
|
className="ml-2 text-xs text-muted-foreground"
|
|
534
496
|
title={
|
|
535
|
-
row.original.isTruncated
|
|
536
|
-
? `Full value size: ${row.original.valueSize}`
|
|
537
|
-
: undefined
|
|
497
|
+
row.original.isTruncated ? `Full value size: ${row.original.valueSize}` : undefined
|
|
538
498
|
}
|
|
539
499
|
>
|
|
540
500
|
{row.original.isTruncated
|
|
@@ -549,38 +509,31 @@ function StoragePanelContent() {
|
|
|
549
509
|
header: '',
|
|
550
510
|
enableSorting: false,
|
|
551
511
|
cell: ({ row }) => (
|
|
552
|
-
<div
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
>
|
|
556
|
-
<Button
|
|
512
|
+
<div className="flex items-center gap-1" onClick={(event) => event.stopPropagation()}>
|
|
513
|
+
<IconButton
|
|
514
|
+
tone="neutral"
|
|
557
515
|
variant="ghost"
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
setInteraction({ key: row.original.key, mode: 'edit' })
|
|
561
|
-
}
|
|
562
|
-
aria-label={`Edit value for ${row.original.key}`}
|
|
516
|
+
onClick={() => setInteraction({ key: row.original.key, mode: 'edit' })}
|
|
517
|
+
label={`Edit value for ${row.original.key}`}
|
|
563
518
|
>
|
|
564
519
|
<Edit3 className="h-3.5 w-3.5" />
|
|
565
|
-
</
|
|
566
|
-
<
|
|
520
|
+
</IconButton>
|
|
521
|
+
<IconButton
|
|
522
|
+
tone="neutral"
|
|
567
523
|
variant="ghost"
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
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}`}
|
|
572
527
|
>
|
|
573
528
|
<Trash2 className="h-3.5 w-3.5" />
|
|
574
|
-
</
|
|
529
|
+
</IconButton>
|
|
575
530
|
</div>
|
|
576
531
|
),
|
|
577
532
|
},
|
|
578
533
|
],
|
|
579
|
-
[],
|
|
534
|
+
[handleDeleteClick],
|
|
580
535
|
);
|
|
581
|
-
const sorting: SortingState = [
|
|
582
|
-
{ id: 'key', desc: keySortDirection === 'descending' },
|
|
583
|
-
];
|
|
536
|
+
const sorting: SortingState = [{ id: 'key', desc: keySortDirection === 'descending' }];
|
|
584
537
|
const handleSortingChange: OnChangeFn<SortingState> = (updater) => {
|
|
585
538
|
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
|
586
539
|
const keySort = next.find((item) => item.id === 'key');
|
|
@@ -612,12 +565,10 @@ function StoragePanelContent() {
|
|
|
612
565
|
isFetchingPreviousPageRef.current = false;
|
|
613
566
|
});
|
|
614
567
|
};
|
|
615
|
-
const selectedStorageViewId = selectedTarget
|
|
616
|
-
? getStorageViewId(selectedTarget)
|
|
617
|
-
: '';
|
|
568
|
+
const selectedStorageViewId = selectedTarget ? getStorageViewId(selectedTarget) : '';
|
|
618
569
|
|
|
619
570
|
return (
|
|
620
|
-
|
|
571
|
+
<>
|
|
621
572
|
<PluginShell.Body>
|
|
622
573
|
<Split direction="horizontal" autoSaveId="storage">
|
|
623
574
|
<Split.Pane defaultSize={22} minSize={15} maxSize={40}>
|
|
@@ -628,22 +579,16 @@ function StoragePanelContent() {
|
|
|
628
579
|
</div>
|
|
629
580
|
) : (
|
|
630
581
|
sidebarGroups.map((group) => (
|
|
631
|
-
<Sidebar.Group
|
|
632
|
-
key={group.adapterId}
|
|
633
|
-
label={group.adapterName}
|
|
634
|
-
>
|
|
582
|
+
<Sidebar.Group key={group.adapterId} label={group.adapterName}>
|
|
635
583
|
{group.items.map((item) => (
|
|
636
584
|
<Sidebar.Item
|
|
637
585
|
key={item.viewId}
|
|
638
586
|
selected={item.viewId === selectedStorageViewId}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
587
|
+
leading={<Database />}
|
|
588
|
+
trailing={<Badge tone="neutral">{item.entryCount}</Badge>}
|
|
642
589
|
onClick={() => {
|
|
643
590
|
const descriptor = descriptors.find(
|
|
644
|
-
(candidate) =>
|
|
645
|
-
getStorageViewId(candidate.target) ===
|
|
646
|
-
item.viewId,
|
|
591
|
+
(candidate) => getStorageViewId(candidate.target) === item.viewId,
|
|
647
592
|
);
|
|
648
593
|
if (descriptor) setSelectedTarget(descriptor.target);
|
|
649
594
|
}}
|
|
@@ -666,16 +611,16 @@ function StoragePanelContent() {
|
|
|
666
611
|
disabled={!selectedDescriptor}
|
|
667
612
|
aria-label="Add entry"
|
|
668
613
|
title="Add entry"
|
|
669
|
-
className="w-
|
|
614
|
+
className="w-6 px-0"
|
|
670
615
|
>
|
|
671
616
|
<Plus className="h-3.5 w-3.5" />
|
|
672
617
|
</Toolbar.Button>
|
|
673
618
|
<Toolbar.Button
|
|
674
|
-
onClick={() =>
|
|
619
|
+
onClick={() => void handlePurgeClick()}
|
|
675
620
|
disabled={!client || !selectedTarget || previews.isFetching}
|
|
676
621
|
aria-label="Purge storage"
|
|
677
622
|
title="Purge storage"
|
|
678
|
-
className="w-
|
|
623
|
+
className="w-6 px-0 text-muted-foreground hover:text-danger"
|
|
679
624
|
>
|
|
680
625
|
<Trash2 className="h-3.5 w-3.5" />
|
|
681
626
|
</Toolbar.Button>
|
|
@@ -687,7 +632,7 @@ function StoragePanelContent() {
|
|
|
687
632
|
disabled={!selectedTarget || previews.isFetching}
|
|
688
633
|
aria-label="Refresh storage"
|
|
689
634
|
title="Refresh storage"
|
|
690
|
-
className="w-
|
|
635
|
+
className="w-6 px-0"
|
|
691
636
|
>
|
|
692
637
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
693
638
|
</Toolbar.Button>
|
|
@@ -699,28 +644,20 @@ function StoragePanelContent() {
|
|
|
699
644
|
disabled={!selectedDescriptor}
|
|
700
645
|
aria-label="Import storage"
|
|
701
646
|
title="Import storage"
|
|
702
|
-
className="w-
|
|
647
|
+
className="w-6 px-0"
|
|
703
648
|
>
|
|
704
649
|
<Upload className="h-3.5 w-3.5" />
|
|
705
650
|
</Toolbar.Button>
|
|
706
651
|
<Toolbar.Button
|
|
707
652
|
onClick={handleExport}
|
|
708
|
-
disabled={
|
|
709
|
-
!client ||
|
|
710
|
-
!selectedTarget ||
|
|
711
|
-
exportState.status === 'loading'
|
|
712
|
-
}
|
|
653
|
+
disabled={!client || !selectedTarget || exportState.status === 'loading'}
|
|
713
654
|
aria-label={
|
|
714
|
-
exportState.status === 'loading'
|
|
715
|
-
? 'Exporting storage'
|
|
716
|
-
: 'Export storage'
|
|
655
|
+
exportState.status === 'loading' ? 'Exporting storage' : 'Export storage'
|
|
717
656
|
}
|
|
718
657
|
title={
|
|
719
|
-
exportState.status === 'loading'
|
|
720
|
-
? 'Exporting storage'
|
|
721
|
-
: 'Export storage'
|
|
658
|
+
exportState.status === 'loading' ? 'Exporting storage' : 'Export storage'
|
|
722
659
|
}
|
|
723
|
-
className="w-
|
|
660
|
+
className="w-6 px-0"
|
|
724
661
|
>
|
|
725
662
|
<Download className="h-3.5 w-3.5" />
|
|
726
663
|
</Toolbar.Button>
|
|
@@ -736,7 +673,7 @@ function StoragePanelContent() {
|
|
|
736
673
|
/>
|
|
737
674
|
</div>
|
|
738
675
|
{exportState.status === 'error' ? (
|
|
739
|
-
<span role="alert" className="text-xs text-
|
|
676
|
+
<span role="alert" className="text-xs text-danger">
|
|
740
677
|
{exportState.message}
|
|
741
678
|
</span>
|
|
742
679
|
) : null}
|
|
@@ -759,15 +696,11 @@ function StoragePanelContent() {
|
|
|
759
696
|
getRowTextValue={(entry) => entry.key}
|
|
760
697
|
loading={previews.isLoading}
|
|
761
698
|
emptyMessage={
|
|
762
|
-
debouncedSearch
|
|
763
|
-
? 'No entries match your search.'
|
|
764
|
-
: 'This storage is empty.'
|
|
699
|
+
debouncedSearch ? 'No entries match your search.' : 'This storage is empty.'
|
|
765
700
|
}
|
|
766
701
|
manualSorting
|
|
767
702
|
onEndReached={fetchNextPage}
|
|
768
|
-
onRowClick={(entry) =>
|
|
769
|
-
setInteraction({ key: entry.key, mode: 'detail' })
|
|
770
|
-
}
|
|
703
|
+
onRowClick={(entry) => setInteraction({ key: entry.key, mode: 'detail' })}
|
|
771
704
|
onSortingChange={handleSortingChange}
|
|
772
705
|
onStartReached={fetchPreviousPage}
|
|
773
706
|
scrollClassName="h-full w-full overflow-auto"
|
|
@@ -799,9 +732,7 @@ function StoragePanelContent() {
|
|
|
799
732
|
if (!open) closeInteraction();
|
|
800
733
|
}}
|
|
801
734
|
onEdit={() =>
|
|
802
|
-
setInteraction((current) =>
|
|
803
|
-
current ? { ...current, mode: 'edit' } : current,
|
|
804
|
-
)
|
|
735
|
+
setInteraction((current) => (current ? { ...current, mode: 'edit' } : current))
|
|
805
736
|
}
|
|
806
737
|
entry={fullEntry.entry ?? null}
|
|
807
738
|
/>
|
|
@@ -818,53 +749,16 @@ function StoragePanelContent() {
|
|
|
818
749
|
onCancel={() => setImportFlight(null)}
|
|
819
750
|
onClose={() => setImportFlight(null)}
|
|
820
751
|
/>
|
|
821
|
-
|
|
822
|
-
open={deleteKey !== null}
|
|
823
|
-
onOpenChange={(open) => {
|
|
824
|
-
if (!open) setDeleteKey(null);
|
|
825
|
-
}}
|
|
826
|
-
variant="confirm"
|
|
827
|
-
destructive
|
|
828
|
-
title="Delete Entry"
|
|
829
|
-
description={
|
|
830
|
-
deleteKey
|
|
831
|
-
? `Are you sure you want to delete the entry "${deleteKey}"?`
|
|
832
|
-
: undefined
|
|
833
|
-
}
|
|
834
|
-
confirmLabel="Delete"
|
|
835
|
-
onConfirm={handleDeleteEntry}
|
|
836
|
-
/>
|
|
837
|
-
<ConfirmDialog
|
|
838
|
-
open={showPurgeDialog}
|
|
839
|
-
onOpenChange={setShowPurgeDialog}
|
|
840
|
-
variant="confirm"
|
|
841
|
-
destructive
|
|
842
|
-
title="Purge Storage"
|
|
843
|
-
description={
|
|
844
|
-
selectedDescriptor
|
|
845
|
-
? `Are you sure you want to remove all entries from "${selectedDescriptor.storageName}"? This action cannot be undone.`
|
|
846
|
-
: 'Are you sure you want to remove all entries from this storage? This action cannot be undone.'
|
|
847
|
-
}
|
|
848
|
-
confirmLabel="Purge"
|
|
849
|
-
onConfirm={handlePurgeStorage}
|
|
850
|
-
/>
|
|
851
|
-
<ConfirmDialog
|
|
852
|
-
open={alertState !== null}
|
|
853
|
-
onOpenChange={(open) => {
|
|
854
|
-
if (!open) setAlertState(null);
|
|
855
|
-
}}
|
|
856
|
-
variant="alert"
|
|
857
|
-
title={alertState?.title ?? ''}
|
|
858
|
-
description={alertState?.message}
|
|
859
|
-
/>
|
|
860
|
-
</PluginShell>
|
|
752
|
+
</>
|
|
861
753
|
);
|
|
862
754
|
}
|
|
863
755
|
|
|
864
756
|
export default function StoragePanel() {
|
|
865
757
|
return (
|
|
866
758
|
<StorageQueryClientProvider>
|
|
867
|
-
<
|
|
759
|
+
<PluginShell>
|
|
760
|
+
<StoragePanelContent />
|
|
761
|
+
</PluginShell>
|
|
868
762
|
</StorageQueryClientProvider>
|
|
869
763
|
);
|
|
870
764
|
}
|