@buoy-gg/storage 7.0.4 → 7.0.6
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/lib/commonjs/index.js +77 -1
- package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +263 -115
- package/lib/commonjs/storage/components/GameUIStorageStats.js +7 -7
- package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/commonjs/storage/components/SelectionActionBar.js +2 -36
- package/lib/commonjs/storage/components/StorageActions.js +6 -6
- package/lib/commonjs/storage/components/StorageBrowserMode.js +16 -2
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +106 -149
- package/lib/commonjs/storage/components/StorageEventListener.js +13 -13
- package/lib/commonjs/storage/components/StorageFilterCards.js +13 -135
- package/lib/commonjs/storage/components/StorageKeyCard.js +1 -1
- package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +392 -0
- package/lib/commonjs/storage/components/StorageKeyRow.js +53 -15
- package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
- package/lib/commonjs/storage/components/StorageKeyStats.js +10 -10
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +272 -129
- package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +46 -0
- package/lib/commonjs/storage/components/StorageTypeFilterBar.js +141 -0
- package/lib/commonjs/storage/components/StorageValueEditModal.js +361 -0
- package/lib/commonjs/storage/hooks/useKeyEditor.js +104 -0
- package/lib/commonjs/storage/hooks/useMeasuredWidth.js +74 -0
- package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +70 -15
- package/lib/commonjs/storage/hooks/useTreeDraft.js +107 -0
- package/lib/commonjs/storage/index.js +29 -0
- package/lib/commonjs/storage/stores/storageEventStore.js +4 -2
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +10 -9
- package/lib/commonjs/storage/utils/MMKVListener.js +40 -4
- package/lib/commonjs/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/commonjs/storage/utils/deleteStorageKeys.js +53 -0
- package/lib/commonjs/storage/utils/index.js +13 -0
- package/lib/commonjs/storage/utils/lineDiff.js +21 -354
- package/lib/commonjs/storage/utils/setStorageValue.js +218 -0
- package/lib/commonjs/storage/utils/storageBrowserRefresh.js +49 -0
- package/lib/commonjs/storage/utils/storageOps.js +53 -0
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +60 -38
- package/lib/commonjs/storage/utils/valueType.js +24 -0
- package/lib/module/index.js +18 -1
- package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/module/storage/components/GameUIStorageBrowser.js +266 -118
- package/lib/module/storage/components/GameUIStorageStats.js +7 -7
- package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/module/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/module/storage/components/SelectionActionBar.js +2 -36
- package/lib/module/storage/components/StorageActions.js +7 -7
- package/lib/module/storage/components/StorageBrowserMode.js +16 -2
- package/lib/module/storage/components/StorageEventDetailContent.js +110 -151
- package/lib/module/storage/components/StorageEventListener.js +14 -14
- package/lib/module/storage/components/StorageFilterCards.js +13 -135
- package/lib/module/storage/components/StorageKeyCard.js +1 -1
- package/lib/module/storage/components/StorageKeyEditorScreen.js +388 -0
- package/lib/module/storage/components/StorageKeyRow.js +55 -17
- package/lib/module/storage/components/StorageKeySection.js +11 -5
- package/lib/module/storage/components/StorageKeyStats.js +11 -10
- package/lib/module/storage/components/StorageModalWithTabs.js +272 -130
- package/lib/module/storage/components/StorageScalarKeyEditor.js +42 -0
- package/lib/module/storage/components/StorageTypeFilterBar.js +136 -0
- package/lib/module/storage/components/StorageValueEditModal.js +357 -0
- package/lib/module/storage/hooks/useKeyEditor.js +100 -0
- package/lib/module/storage/hooks/useMeasuredWidth.js +71 -0
- package/lib/module/storage/hooks/useSecureStoreKeys.js +71 -16
- package/lib/module/storage/hooks/useTreeDraft.js +103 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/stores/storageEventStore.js +4 -2
- package/lib/module/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/module/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/module/storage/utils/MMKVInstanceRegistry.js +8 -7
- package/lib/module/storage/utils/MMKVListener.js +40 -4
- package/lib/module/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/module/storage/utils/deleteStorageKeys.js +49 -0
- package/lib/module/storage/utils/index.js +3 -0
- package/lib/module/storage/utils/lineDiff.js +7 -354
- package/lib/module/storage/utils/setStorageValue.js +209 -0
- package/lib/module/storage/utils/storageBrowserRefresh.js +44 -0
- package/lib/module/storage/utils/storageOps.js +46 -0
- package/lib/module/storage/utils/storageTimeTravelUtils.js +61 -40
- package/lib/module/storage/utils/valueType.js +23 -0
- package/lib/typescript/index.d.ts +12 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts +4 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +7 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +27 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts +17 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +4 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageFilterCards.d.ts +9 -3
- package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +42 -0
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +17 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +28 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +24 -0
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +29 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts +36 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts +51 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts +37 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +8 -1
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts +67 -0
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -0
- package/lib/typescript/storage/index.d.ts +2 -0
- package/lib/typescript/storage/index.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +41 -0
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +15 -0
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVListener.d.ts +7 -0
- package/lib/typescript/storage/utils/MMKVListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts +20 -0
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts +27 -0
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +1 -0
- package/lib/typescript/storage/utils/index.d.ts +1 -0
- package/lib/typescript/storage/utils/index.d.ts.map +1 -1
- package/lib/typescript/storage/utils/lineDiff.d.ts +7 -31
- package/lib/typescript/storage/utils/lineDiff.d.ts.map +1 -1
- package/lib/typescript/storage/utils/setStorageValue.d.ts +64 -0
- package/lib/typescript/storage/utils/setStorageValue.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts +28 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageOps.d.ts +25 -0
- package/lib/typescript/storage/utils/storageOps.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +15 -5
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
- package/lib/typescript/storage/utils/valueType.d.ts +13 -0
- package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
4
|
import { View, Text, StyleSheet } from "react-native";
|
|
5
5
|
import { StorageKeyRow } from "./StorageKeyRow";
|
|
6
|
-
import { SectionHeader } from "@buoy-gg/shared-ui";
|
|
6
|
+
import { SectionHeader, macOSColors } from "@buoy-gg/shared-ui";
|
|
7
7
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
8
|
/**
|
|
9
9
|
* Storage key section component following composition principles [[rule3]]
|
|
@@ -26,7 +26,10 @@ export function StorageKeySection({
|
|
|
26
26
|
onViewHistory,
|
|
27
27
|
onHideKey,
|
|
28
28
|
pinnedKeys,
|
|
29
|
-
onTogglePin
|
|
29
|
+
onTogglePin,
|
|
30
|
+
onEditKey,
|
|
31
|
+
onToggleKey,
|
|
32
|
+
editBlockedFor
|
|
30
33
|
}) {
|
|
31
34
|
const [expandedKey, setExpandedKey] = useState(null);
|
|
32
35
|
const handleKeyPress = useCallback(storageKey => {
|
|
@@ -81,6 +84,9 @@ export function StorageKeySection({
|
|
|
81
84
|
eventCount: eventCountByKey?.[storageKey.key],
|
|
82
85
|
onViewHistory: onViewHistory ? () => onViewHistory(storageKey.key) : undefined,
|
|
83
86
|
onHideKey: onHideKey,
|
|
87
|
+
onEditKey: onEditKey,
|
|
88
|
+
onToggleKey: onToggleKey,
|
|
89
|
+
editBlockedReason: editBlockedFor?.(storageKey),
|
|
84
90
|
isPinned: pinnedKeys?.has(storageKey.key),
|
|
85
91
|
onTogglePin: onTogglePin
|
|
86
92
|
}, uniqueKey);
|
|
@@ -96,15 +102,15 @@ const styles = StyleSheet.create({
|
|
|
96
102
|
// No gap needed, StorageKeyRow has its own margins
|
|
97
103
|
},
|
|
98
104
|
emptySection: {
|
|
99
|
-
backgroundColor: "rgba(
|
|
105
|
+
backgroundColor: "rgba(128, 128, 128, 0.04)",
|
|
100
106
|
borderRadius: 6,
|
|
101
107
|
padding: 16,
|
|
102
108
|
borderWidth: 1,
|
|
103
|
-
borderColor: "rgba(
|
|
109
|
+
borderColor: "rgba(128, 128, 128, 0.1)",
|
|
104
110
|
alignItems: "center"
|
|
105
111
|
},
|
|
106
112
|
emptySectionText: {
|
|
107
|
-
color:
|
|
113
|
+
color: macOSColors.text.icon,
|
|
108
114
|
fontSize: 11,
|
|
109
115
|
textAlign: "center"
|
|
110
116
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { View, Text, StyleSheet } from "react-native";
|
|
4
4
|
import { AlertCircle, CheckCircle2, XCircle, Eye, Database, Shield } from "@buoy-gg/shared-ui";
|
|
5
|
+
import { macOSColors, buoyColors } from "@buoy-gg/shared-ui";
|
|
5
6
|
import { getStorageTypeHexColor } from "../utils/storageQueryUtils";
|
|
6
7
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
8
|
// Variable type configurations matching env vars design
|
|
@@ -255,15 +256,15 @@ export function StorageKeyStatsSection({
|
|
|
255
256
|
const styles = StyleSheet.create({
|
|
256
257
|
statsContainer: {
|
|
257
258
|
marginBottom: 24,
|
|
258
|
-
backgroundColor: "rgba(
|
|
259
|
+
backgroundColor: "rgba(128, 128, 128, 0.06)",
|
|
259
260
|
borderRadius: 8,
|
|
260
261
|
padding: 16,
|
|
261
262
|
borderWidth: 1,
|
|
262
|
-
borderColor: "rgba(
|
|
263
|
+
borderColor: "rgba(128, 128, 128, 0.16)"
|
|
263
264
|
},
|
|
264
265
|
// Section titles
|
|
265
266
|
sectionTitle: {
|
|
266
|
-
color:
|
|
267
|
+
color: buoyColors.textSecondary,
|
|
267
268
|
fontSize: 12,
|
|
268
269
|
fontWeight: "500",
|
|
269
270
|
marginBottom: 16,
|
|
@@ -279,11 +280,11 @@ const styles = StyleSheet.create({
|
|
|
279
280
|
gap: 12
|
|
280
281
|
},
|
|
281
282
|
breakdownItem: {
|
|
282
|
-
backgroundColor: "rgba(
|
|
283
|
+
backgroundColor: "rgba(128, 128, 128, 0.04)",
|
|
283
284
|
padding: 12,
|
|
284
285
|
borderRadius: 8,
|
|
285
286
|
borderWidth: 1,
|
|
286
|
-
borderColor: "rgba(
|
|
287
|
+
borderColor: "rgba(128, 128, 128, 0.1)"
|
|
287
288
|
},
|
|
288
289
|
breakdownItemRow: {
|
|
289
290
|
flexDirection: "row",
|
|
@@ -306,12 +307,12 @@ const styles = StyleSheet.create({
|
|
|
306
307
|
minWidth: 0
|
|
307
308
|
},
|
|
308
309
|
breakdownItemLabel: {
|
|
309
|
-
color:
|
|
310
|
+
color: macOSColors.text.primary,
|
|
310
311
|
fontSize: 14,
|
|
311
312
|
fontWeight: "500"
|
|
312
313
|
},
|
|
313
314
|
breakdownItemDesc: {
|
|
314
|
-
color:
|
|
315
|
+
color: buoyColors.textSecondary,
|
|
315
316
|
fontSize: 12
|
|
316
317
|
},
|
|
317
318
|
breakdownItemRight: {
|
|
@@ -322,18 +323,18 @@ const styles = StyleSheet.create({
|
|
|
322
323
|
fontWeight: "600"
|
|
323
324
|
},
|
|
324
325
|
breakdownPercentage: {
|
|
325
|
-
color:
|
|
326
|
+
color: macOSColors.text.icon,
|
|
326
327
|
fontSize: 10
|
|
327
328
|
},
|
|
328
329
|
// Empty state
|
|
329
330
|
emptyState: {
|
|
330
331
|
padding: 16,
|
|
331
|
-
backgroundColor: "rgba(
|
|
332
|
+
backgroundColor: "rgba(128, 128, 128, 0.04)",
|
|
332
333
|
borderRadius: 6,
|
|
333
334
|
alignItems: "center"
|
|
334
335
|
},
|
|
335
336
|
emptyStateText: {
|
|
336
|
-
color:
|
|
337
|
+
color: macOSColors.text.icon,
|
|
337
338
|
fontSize: 11,
|
|
338
339
|
textAlign: "center"
|
|
339
340
|
}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
4
4
|
import { Text, View, TouchableOpacity, StyleSheet, FlatList, Alert } from "react-native";
|
|
5
5
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
6
|
-
import { JsModal, ModalHeader, TabSelector, parseValue, devToolsStorageKeys, macOSColors, Database, Filter, Search, SearchBar, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider } from "@buoy-gg/shared-ui";
|
|
6
|
+
import { JsModal, ModalHeader, TabSelector, parseValue, devToolsStorageKeys, macOSColors, Database, Filter, RefreshCw, Search, SearchBar, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider } from "@buoy-gg/shared-ui";
|
|
7
7
|
import { StorageBrowserMode } from "./StorageBrowserMode";
|
|
8
|
+
import { StorageTypeFilterBar, countByStorageType } from "./StorageTypeFilterBar";
|
|
8
9
|
import { clearAllAppStorage, clearAllStorageIncludingDevTools } from "../utils/clearAllStorage";
|
|
9
10
|
import { ProFeatureBanner } from "@buoy-gg/shared-ui";
|
|
10
11
|
import { useStorageEvents } from "../hooks/useStorageEvents";
|
|
@@ -25,10 +26,16 @@ export function StorageModalWithTabs({
|
|
|
25
26
|
onBack,
|
|
26
27
|
onMinimize,
|
|
27
28
|
enableSharedModalDimensions = false,
|
|
28
|
-
requiredStorageKeys = []
|
|
29
|
+
requiredStorageKeys = [],
|
|
30
|
+
inline = false,
|
|
31
|
+
onTabChange,
|
|
32
|
+
onRefresh
|
|
29
33
|
}) {
|
|
30
34
|
const isPro = useIsPro();
|
|
31
35
|
const [activeTab, setActiveTab] = useState("browser");
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
onTabChange?.(activeTab);
|
|
38
|
+
}, [activeTab, onTabChange]);
|
|
32
39
|
|
|
33
40
|
// Storage Browser state
|
|
34
41
|
const [searchQuery, setSearchQuery] = useState("");
|
|
@@ -54,6 +61,19 @@ export function StorageModalWithTabs({
|
|
|
54
61
|
const [showFilters, setShowFilters] = useState(false);
|
|
55
62
|
const [ignoredPatterns, setIgnoredPatterns] = useState(new Set(["@react_buoy", "@RNAsyncStorage", "redux-persist", "persist:"]) // Auto-hide dev tool keys by default
|
|
56
63
|
);
|
|
64
|
+
/**
|
|
65
|
+
* Which backend both tabs are looking at.
|
|
66
|
+
*
|
|
67
|
+
* Owned here rather than inside the browser because the Keys and Events tabs
|
|
68
|
+
* answer questions about the same three backends — filter the key list to
|
|
69
|
+
* Keychain, switch to Events to see what wrote to it, and the filter should
|
|
70
|
+
* still be Keychain.
|
|
71
|
+
*
|
|
72
|
+
* Distinct from `enabledStorageTypes` below: that's the persisted "capture
|
|
73
|
+
* this backend at all" setting from the Filters view. This is a view filter,
|
|
74
|
+
* and it never hides data the other one already dropped.
|
|
75
|
+
*/
|
|
76
|
+
const [storageTypeFilter, setStorageTypeFilter] = useState("all");
|
|
57
77
|
const [enabledStorageTypes, setEnabledStorageTypes] = useState(new Set(['async', 'mmkv', 'secure']) // All enabled by default
|
|
58
78
|
);
|
|
59
79
|
const [pinnedKeys, setPinnedKeys] = useState(new Set());
|
|
@@ -65,8 +85,13 @@ export function StorageModalWithTabs({
|
|
|
65
85
|
|
|
66
86
|
// isListening = enabled and capturing (has subscribers)
|
|
67
87
|
const isListening = isListeningEnabled && isCapturing;
|
|
68
|
-
|
|
69
|
-
|
|
88
|
+
|
|
89
|
+
// Track the modal's presentation mode so the stepper footer only reserves
|
|
90
|
+
// safe-area bottom padding while docked as a bottom sheet. In floating mode
|
|
91
|
+
// the footer isn't pinned to the screen edge, so that inset is dead space.
|
|
92
|
+
const [modalMode, setModalMode] = useState("bottomSheet");
|
|
93
|
+
const handleModeChange = useCallback(mode => {
|
|
94
|
+
setModalMode(mode);
|
|
70
95
|
}, []);
|
|
71
96
|
|
|
72
97
|
// Timer removed - using useTickEveryMinute hook instead
|
|
@@ -412,14 +437,34 @@ export function StorageModalWithTabs({
|
|
|
412
437
|
return counts;
|
|
413
438
|
}, [conversations]);
|
|
414
439
|
|
|
440
|
+
/**
|
|
441
|
+
* When each key was last written, for the wide table's "Last write" column.
|
|
442
|
+
* Derived from the recorded event stream, NOT from the key read: a storage
|
|
443
|
+
* read has no timestamp of its own, so a key we never witnessed being written
|
|
444
|
+
* has no honest answer here and is simply absent.
|
|
445
|
+
*/
|
|
446
|
+
const lastWriteByKey = useMemo(() => {
|
|
447
|
+
const at = {};
|
|
448
|
+
conversations.forEach(conv => {
|
|
449
|
+
at[conv.key] = conv.lastEvent.timestamp.getTime();
|
|
450
|
+
});
|
|
451
|
+
return at;
|
|
452
|
+
}, [conversations]);
|
|
453
|
+
|
|
415
454
|
// Selected history conversation (from browser tab "view history")
|
|
416
455
|
const selectedHistoryConversation = useMemo(() => {
|
|
417
456
|
if (!selectedHistoryKey) return null;
|
|
418
457
|
return conversations.find(c => c.key === selectedHistoryKey) || null;
|
|
419
458
|
}, [selectedHistoryKey, conversations]);
|
|
420
459
|
|
|
421
|
-
|
|
422
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Events passing everything EXCEPT the backend pills.
|
|
462
|
+
*
|
|
463
|
+
* Split out so the pills can show what each one would reveal. Counting the
|
|
464
|
+
* already-filtered list would zero out the three types you didn't pick, which
|
|
465
|
+
* tells you nothing about where to look next.
|
|
466
|
+
*/
|
|
467
|
+
const eventsBeforeTypeFilter = useMemo(() => {
|
|
423
468
|
return events.filter(event => {
|
|
424
469
|
if (!event.data?.key) return false;
|
|
425
470
|
if (!enabledStorageTypes.has(event.storageType)) return false;
|
|
@@ -427,6 +472,13 @@ export function StorageModalWithTabs({
|
|
|
427
472
|
return !Array.from(ignoredPatterns).some(p => key.includes(p));
|
|
428
473
|
});
|
|
429
474
|
}, [events, ignoredPatterns, enabledStorageTypes]);
|
|
475
|
+
const eventTypeCounts = useMemo(() => countByStorageType(eventsBeforeTypeFilter), [eventsBeforeTypeFilter]);
|
|
476
|
+
|
|
477
|
+
// Filtered raw events for the events tab (chronological stream, newest-first)
|
|
478
|
+
const filteredEvents = useMemo(() => {
|
|
479
|
+
if (storageTypeFilter === "all") return eventsBeforeTypeFilter;
|
|
480
|
+
return eventsBeforeTypeFilter.filter(event => event.storageType === storageTypeFilter);
|
|
481
|
+
}, [eventsBeforeTypeFilter, storageTypeFilter]);
|
|
430
482
|
const getEventsSnapshot = useCallback(() => {
|
|
431
483
|
return filteredEvents.map(event => ({
|
|
432
484
|
key: event.data?.key,
|
|
@@ -547,10 +599,17 @@ export function StorageModalWithTabs({
|
|
|
547
599
|
searchQuery: searchQuery,
|
|
548
600
|
storageDataRef: storageDataRef,
|
|
549
601
|
eventCountByKey: eventCountByKey,
|
|
602
|
+
lastWriteByKey: lastWriteByKey,
|
|
550
603
|
onViewHistory: handleViewHistoryFromBrowser,
|
|
551
604
|
enabledStorageTypes: enabledStorageTypes,
|
|
552
605
|
pinnedKeys: pinnedKeys,
|
|
553
606
|
onTogglePin: handleTogglePin
|
|
607
|
+
// Only a docked sheet sits on the screen's bottom edge; a floating
|
|
608
|
+
// window's own bottom is well above the home indicator.
|
|
609
|
+
,
|
|
610
|
+
applySafeAreaInset: modalMode === "bottomSheet",
|
|
611
|
+
storageTypeFilter: storageTypeFilter,
|
|
612
|
+
onStorageTypeFilterChange: setStorageTypeFilter
|
|
554
613
|
});
|
|
555
614
|
}
|
|
556
615
|
|
|
@@ -566,48 +625,75 @@ export function StorageModalWithTabs({
|
|
|
566
625
|
})
|
|
567
626
|
});
|
|
568
627
|
}
|
|
628
|
+
|
|
629
|
+
// The pills render ABOVE the empty state as well as the list. Filtering to
|
|
630
|
+
// a backend that happens to have no events would otherwise hide the only
|
|
631
|
+
// control that undoes it, leaving the tab looking permanently broken.
|
|
632
|
+
const eventTypeFilterBar = /*#__PURE__*/_jsx(View, {
|
|
633
|
+
style: styles.eventFilterBar,
|
|
634
|
+
children: /*#__PURE__*/_jsx(StorageTypeFilterBar, {
|
|
635
|
+
active: storageTypeFilter,
|
|
636
|
+
onChange: setStorageTypeFilter,
|
|
637
|
+
counts: eventTypeCounts
|
|
638
|
+
})
|
|
639
|
+
});
|
|
569
640
|
if (filteredEvents.length === 0) {
|
|
641
|
+
const filtered = storageTypeFilter !== "all";
|
|
570
642
|
return /*#__PURE__*/_jsxs(View, {
|
|
571
|
-
style: styles.
|
|
572
|
-
children: [/*#__PURE__*/
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
643
|
+
style: styles.flexFill,
|
|
644
|
+
children: [eventTypeFilterBar, /*#__PURE__*/_jsxs(View, {
|
|
645
|
+
style: styles.emptyState,
|
|
646
|
+
children: [/*#__PURE__*/_jsx(Database, {
|
|
647
|
+
size: 48,
|
|
648
|
+
color: macOSColors.text.muted
|
|
649
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
650
|
+
style: styles.emptyTitle,
|
|
651
|
+
children: !isListening ? "Event listener is paused" : filtered ? `No ${storageTypeFilter} events` : "No storage events yet"
|
|
652
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
653
|
+
style: styles.emptySubtitle,
|
|
654
|
+
children: !isListening ? "Press play to start monitoring" : filtered ? "Other backends have events — pick All Types to see them" : "Storage operations will appear here"
|
|
655
|
+
})]
|
|
581
656
|
})]
|
|
582
657
|
});
|
|
583
658
|
}
|
|
584
|
-
return /*#__PURE__*/
|
|
659
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
585
660
|
style: styles.flexFill,
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
661
|
+
children: [eventTypeFilterBar, /*#__PURE__*/_jsx(FlatList, {
|
|
662
|
+
style: styles.flexFill,
|
|
663
|
+
data: visibleEvents,
|
|
664
|
+
renderItem: renderEventItem,
|
|
665
|
+
keyExtractor: eventKeyExtractor,
|
|
666
|
+
onEndReachedThreshold: END_REACHED_THRESHOLD,
|
|
667
|
+
contentContainerStyle: styles.listContent,
|
|
668
|
+
ListFooterComponent: lockedEventCount > 0 ? /*#__PURE__*/_jsx(View, {
|
|
669
|
+
style: styles.lockedBannerContainer,
|
|
670
|
+
children: /*#__PURE__*/_jsx(ProFeatureBanner, {
|
|
671
|
+
featureName: `${lockedEventCount} event${lockedEventCount > 1 ? 's' : ''} locked`,
|
|
672
|
+
description: "Upgrade to Pro to unlock full event history"
|
|
673
|
+
})
|
|
674
|
+
}) : null,
|
|
675
|
+
initialNumToRender: 10,
|
|
676
|
+
maxToRenderPerBatch: 10,
|
|
677
|
+
windowSize: 10
|
|
678
|
+
})]
|
|
601
679
|
});
|
|
602
680
|
};
|
|
603
|
-
|
|
681
|
+
|
|
682
|
+
// The stepper renders nothing for a single-event conversation
|
|
683
|
+
// (EventStepperFooter bails at totalItems <= 1), but an element is truthy
|
|
684
|
+
// either way — so `footerHeight={footerNode ? 68 : 0}` below used to reserve
|
|
685
|
+
// 68px for a footer that drew nothing, leaving a dead band under the content.
|
|
686
|
+
// Gate the node on the event count so the reserve matches what's painted.
|
|
687
|
+
const footerNode = selectedConversation && selectedConversation.events.length > 1 ? /*#__PURE__*/_jsx(StorageEventDetailFooter, {
|
|
604
688
|
conversation: selectedConversation,
|
|
605
689
|
selectedEventIndex: selectedEventIndex,
|
|
606
|
-
onEventIndexChange: setSelectedEventIndex
|
|
607
|
-
|
|
690
|
+
onEventIndexChange: setSelectedEventIndex,
|
|
691
|
+
applySafeAreaInset: modalMode === "bottomSheet"
|
|
692
|
+
}) : selectedHistoryConversation && selectedHistoryEventIndex !== null && selectedHistoryConversation.events.length > 1 ? /*#__PURE__*/_jsx(StorageEventDetailFooter, {
|
|
608
693
|
conversation: selectedHistoryConversation,
|
|
609
694
|
selectedEventIndex: selectedHistoryEventIndex,
|
|
610
|
-
onEventIndexChange: setSelectedHistoryEventIndex
|
|
695
|
+
onEventIndexChange: setSelectedHistoryEventIndex,
|
|
696
|
+
applySafeAreaInset: modalMode === "bottomSheet"
|
|
611
697
|
}) : null;
|
|
612
698
|
|
|
613
699
|
// Determine the appropriate back handler based on current view state
|
|
@@ -615,6 +701,122 @@ export function StorageModalWithTabs({
|
|
|
615
701
|
setSelectedConversationKey(null);
|
|
616
702
|
setSelectedEventIndex(0);
|
|
617
703
|
} : onBack;
|
|
704
|
+
const headerContent = showFilters ? /*#__PURE__*/_jsxs(ModalHeader, {
|
|
705
|
+
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
706
|
+
onBack: () => setShowFilters(false)
|
|
707
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
708
|
+
title: "Filters"
|
|
709
|
+
})]
|
|
710
|
+
}) : selectedHistoryKey ? /*#__PURE__*/_jsxs(ModalHeader, {
|
|
711
|
+
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
712
|
+
onBack: selectedHistoryEventIndex !== null ? () => setSelectedHistoryEventIndex(null) : handleBackFromHistory
|
|
713
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
714
|
+
title: `${selectedHistoryKey} History`,
|
|
715
|
+
centered: true
|
|
716
|
+
})]
|
|
717
|
+
}) : selectedConversation ? /*#__PURE__*/_jsxs(ModalHeader, {
|
|
718
|
+
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
719
|
+
onBack: () => {
|
|
720
|
+
setSelectedConversationKey(null);
|
|
721
|
+
setSelectedEventIndex(0);
|
|
722
|
+
}
|
|
723
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
724
|
+
title: selectedConversation.key
|
|
725
|
+
})]
|
|
726
|
+
}) : /*#__PURE__*/_jsxs(ModalHeader, {
|
|
727
|
+
children: [onBack && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
728
|
+
onBack: onBack
|
|
729
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
730
|
+
title: "",
|
|
731
|
+
children: isSearchActive ? /*#__PURE__*/_jsx(SearchBar, {
|
|
732
|
+
value: searchQuery,
|
|
733
|
+
onChange: setSearchQuery,
|
|
734
|
+
onClear: () => {
|
|
735
|
+
setSearchQuery("");
|
|
736
|
+
setIsSearchActive(false);
|
|
737
|
+
},
|
|
738
|
+
placeholder: "Search storage keys...",
|
|
739
|
+
autoFocus: true,
|
|
740
|
+
onSubmitEditing: () => setIsSearchActive(false),
|
|
741
|
+
returnKeyType: "search",
|
|
742
|
+
suggestions: allStorageKeys,
|
|
743
|
+
containerStyle: styles.headerSearchContainer
|
|
744
|
+
}) : /*#__PURE__*/_jsx(TabSelector, {
|
|
745
|
+
tabs: [{
|
|
746
|
+
key: "browser",
|
|
747
|
+
label: "Storage"
|
|
748
|
+
}, {
|
|
749
|
+
key: "events",
|
|
750
|
+
label: `Events${filteredEvents.length > 0 ? ` (${filteredEvents.length})` : ""}`
|
|
751
|
+
}],
|
|
752
|
+
activeTab: activeTab,
|
|
753
|
+
onTabChange: tab => {
|
|
754
|
+
setActiveTab(tab);
|
|
755
|
+
setSelectedHistoryKey(null);
|
|
756
|
+
setSelectedHistoryEventIndex(null);
|
|
757
|
+
setSelectedConversationKey(null);
|
|
758
|
+
setSelectedEventIndex(0);
|
|
759
|
+
}
|
|
760
|
+
})
|
|
761
|
+
}), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
|
|
762
|
+
children: [onRefresh && !isSearchActive ? /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
763
|
+
onPress: onRefresh,
|
|
764
|
+
style: styles.iconButton,
|
|
765
|
+
accessibilityLabel: "Refresh storage",
|
|
766
|
+
children: /*#__PURE__*/_jsx(RefreshCw, {
|
|
767
|
+
size: 14,
|
|
768
|
+
color: macOSColors.text.secondary
|
|
769
|
+
})
|
|
770
|
+
}) : null, /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
771
|
+
onPress: handleToggleFilters,
|
|
772
|
+
style: [styles.iconButton, (ignoredPatterns.size > 0 || enabledStorageTypes.size < 3) && styles.activeFilterButton],
|
|
773
|
+
children: /*#__PURE__*/_jsx(Filter, {
|
|
774
|
+
size: 14,
|
|
775
|
+
color: ignoredPatterns.size > 0 || enabledStorageTypes.size < 3 ? macOSColors.semantic.debug : macOSColors.text.secondary
|
|
776
|
+
})
|
|
777
|
+
}), activeTab === "browser" && !isSearchActive && /*#__PURE__*/_jsxs(_Fragment, {
|
|
778
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
779
|
+
onPress: () => setIsSearchActive(true),
|
|
780
|
+
style: styles.iconButton,
|
|
781
|
+
children: /*#__PURE__*/_jsx(Search, {
|
|
782
|
+
size: 14,
|
|
783
|
+
color: macOSColors.text.secondary
|
|
784
|
+
})
|
|
785
|
+
}), /*#__PURE__*/_jsx(ToolbarCopyButton, {
|
|
786
|
+
value: getStorageSnapshot,
|
|
787
|
+
disabled: storageDataRef.current.length === 0,
|
|
788
|
+
buttonStyle: styles.iconButton
|
|
789
|
+
})]
|
|
790
|
+
}), activeTab === "events" && /*#__PURE__*/_jsxs(_Fragment, {
|
|
791
|
+
children: [/*#__PURE__*/_jsx(ToolbarCopyButton, {
|
|
792
|
+
value: getEventsSnapshot,
|
|
793
|
+
disabled: filteredEvents.length === 0,
|
|
794
|
+
buttonStyle: styles.iconButton
|
|
795
|
+
}), /*#__PURE__*/_jsx(PowerToggleButton, {
|
|
796
|
+
isEnabled: isListening,
|
|
797
|
+
onToggle: handleToggleListening,
|
|
798
|
+
accessibilityLabel: "Toggle storage event monitoring"
|
|
799
|
+
}), /*#__PURE__*/_jsx(ToolbarClearButton, {
|
|
800
|
+
onPress: handleClearEvents,
|
|
801
|
+
buttonStyle: styles.iconButton
|
|
802
|
+
})]
|
|
803
|
+
})]
|
|
804
|
+
})]
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// The ONLY difference between the phone and the desktop dashboard is which
|
|
808
|
+
// shell the header, body and footer go in. Everything above this is shared.
|
|
809
|
+
if (inline) {
|
|
810
|
+
return /*#__PURE__*/_jsx(TickProvider, {
|
|
811
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
812
|
+
style: styles.inlineRoot,
|
|
813
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
814
|
+
style: styles.inlineHeader,
|
|
815
|
+
children: headerContent
|
|
816
|
+
}), renderContent(), footerNode]
|
|
817
|
+
})
|
|
818
|
+
});
|
|
819
|
+
}
|
|
618
820
|
return /*#__PURE__*/_jsx(TickProvider, {
|
|
619
821
|
children: /*#__PURE__*/_jsx(JsModal, {
|
|
620
822
|
visible: visible,
|
|
@@ -624,100 +826,7 @@ export function StorageModalWithTabs({
|
|
|
624
826
|
persistenceKey: persistenceKey,
|
|
625
827
|
header: {
|
|
626
828
|
showToggleButton: true,
|
|
627
|
-
customContent:
|
|
628
|
-
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
629
|
-
onBack: () => setShowFilters(false)
|
|
630
|
-
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
631
|
-
title: "Filters"
|
|
632
|
-
})]
|
|
633
|
-
}) : selectedHistoryKey ? /*#__PURE__*/_jsxs(ModalHeader, {
|
|
634
|
-
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
635
|
-
onBack: selectedHistoryEventIndex !== null ? () => setSelectedHistoryEventIndex(null) : handleBackFromHistory
|
|
636
|
-
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
637
|
-
title: `${selectedHistoryKey} History`,
|
|
638
|
-
centered: true
|
|
639
|
-
})]
|
|
640
|
-
}) : selectedConversation ? /*#__PURE__*/_jsxs(ModalHeader, {
|
|
641
|
-
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
642
|
-
onBack: () => {
|
|
643
|
-
setSelectedConversationKey(null);
|
|
644
|
-
setSelectedEventIndex(0);
|
|
645
|
-
}
|
|
646
|
-
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
647
|
-
title: selectedConversation.key
|
|
648
|
-
})]
|
|
649
|
-
}) : /*#__PURE__*/_jsxs(ModalHeader, {
|
|
650
|
-
children: [onBack && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
651
|
-
onBack: onBack
|
|
652
|
-
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
653
|
-
title: "",
|
|
654
|
-
children: isSearchActive ? /*#__PURE__*/_jsx(SearchBar, {
|
|
655
|
-
value: searchQuery,
|
|
656
|
-
onChange: setSearchQuery,
|
|
657
|
-
onClear: () => {
|
|
658
|
-
setSearchQuery("");
|
|
659
|
-
setIsSearchActive(false);
|
|
660
|
-
},
|
|
661
|
-
placeholder: "Search storage keys...",
|
|
662
|
-
autoFocus: true,
|
|
663
|
-
onSubmitEditing: () => setIsSearchActive(false),
|
|
664
|
-
returnKeyType: "search",
|
|
665
|
-
suggestions: allStorageKeys,
|
|
666
|
-
containerStyle: styles.headerSearchContainer
|
|
667
|
-
}) : /*#__PURE__*/_jsx(TabSelector, {
|
|
668
|
-
tabs: [{
|
|
669
|
-
key: "browser",
|
|
670
|
-
label: "Storage"
|
|
671
|
-
}, {
|
|
672
|
-
key: "events",
|
|
673
|
-
label: `Events${filteredEvents.length > 0 ? ` (${filteredEvents.length})` : ""}`
|
|
674
|
-
}],
|
|
675
|
-
activeTab: activeTab,
|
|
676
|
-
onTabChange: tab => {
|
|
677
|
-
setActiveTab(tab);
|
|
678
|
-
setSelectedHistoryKey(null);
|
|
679
|
-
setSelectedHistoryEventIndex(null);
|
|
680
|
-
setSelectedConversationKey(null);
|
|
681
|
-
setSelectedEventIndex(0);
|
|
682
|
-
}
|
|
683
|
-
})
|
|
684
|
-
}), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
|
|
685
|
-
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
686
|
-
onPress: handleToggleFilters,
|
|
687
|
-
style: [styles.iconButton, (ignoredPatterns.size > 0 || enabledStorageTypes.size < 3) && styles.activeFilterButton],
|
|
688
|
-
children: /*#__PURE__*/_jsx(Filter, {
|
|
689
|
-
size: 14,
|
|
690
|
-
color: ignoredPatterns.size > 0 || enabledStorageTypes.size < 3 ? macOSColors.semantic.debug : macOSColors.text.secondary
|
|
691
|
-
})
|
|
692
|
-
}), activeTab === "browser" && !isSearchActive && /*#__PURE__*/_jsxs(_Fragment, {
|
|
693
|
-
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
694
|
-
onPress: () => setIsSearchActive(true),
|
|
695
|
-
style: styles.iconButton,
|
|
696
|
-
children: /*#__PURE__*/_jsx(Search, {
|
|
697
|
-
size: 14,
|
|
698
|
-
color: macOSColors.text.secondary
|
|
699
|
-
})
|
|
700
|
-
}), /*#__PURE__*/_jsx(ToolbarCopyButton, {
|
|
701
|
-
value: getStorageSnapshot,
|
|
702
|
-
disabled: storageDataRef.current.length === 0,
|
|
703
|
-
buttonStyle: styles.iconButton
|
|
704
|
-
})]
|
|
705
|
-
}), activeTab === "events" && /*#__PURE__*/_jsxs(_Fragment, {
|
|
706
|
-
children: [/*#__PURE__*/_jsx(ToolbarCopyButton, {
|
|
707
|
-
value: getEventsSnapshot,
|
|
708
|
-
disabled: filteredEvents.length === 0,
|
|
709
|
-
buttonStyle: styles.iconButton
|
|
710
|
-
}), /*#__PURE__*/_jsx(PowerToggleButton, {
|
|
711
|
-
isEnabled: isListening,
|
|
712
|
-
onToggle: handleToggleListening,
|
|
713
|
-
accessibilityLabel: "Toggle storage event monitoring"
|
|
714
|
-
}), /*#__PURE__*/_jsx(ToolbarClearButton, {
|
|
715
|
-
onPress: handleClearEvents,
|
|
716
|
-
buttonStyle: styles.iconButton
|
|
717
|
-
})]
|
|
718
|
-
})]
|
|
719
|
-
})]
|
|
720
|
-
})
|
|
829
|
+
customContent: headerContent
|
|
721
830
|
},
|
|
722
831
|
onModeChange: handleModeChange,
|
|
723
832
|
enablePersistence: true,
|
|
@@ -731,7 +840,34 @@ export function StorageModalWithTabs({
|
|
|
731
840
|
})
|
|
732
841
|
});
|
|
733
842
|
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* The same storage tool as `StorageModalWithTabs`, rendered INLINE for hosts
|
|
846
|
+
* that place tools in a panel rather than a modal. Exists so there is exactly
|
|
847
|
+
* one implementation — the desktop dashboard used to ship its own copy of this
|
|
848
|
+
* component's header, tabs and screen router.
|
|
849
|
+
*/
|
|
850
|
+
export function StorageToolView(props = {}) {
|
|
851
|
+
return /*#__PURE__*/_jsx(StorageModalWithTabs, {
|
|
852
|
+
...props,
|
|
853
|
+
inline: true,
|
|
854
|
+
visible: true,
|
|
855
|
+
onClose: () => {}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
734
858
|
const styles = StyleSheet.create({
|
|
859
|
+
inlineRoot: {
|
|
860
|
+
flex: 1,
|
|
861
|
+
minHeight: 0,
|
|
862
|
+
backgroundColor: macOSColors.background.base
|
|
863
|
+
},
|
|
864
|
+
inlineHeader: {
|
|
865
|
+
paddingHorizontal: 12,
|
|
866
|
+
paddingVertical: 8,
|
|
867
|
+
borderBottomWidth: 1,
|
|
868
|
+
borderBottomColor: macOSColors.border.default,
|
|
869
|
+
backgroundColor: macOSColors.background.card
|
|
870
|
+
},
|
|
735
871
|
headerSearchContainer: {
|
|
736
872
|
flex: 1
|
|
737
873
|
},
|
|
@@ -761,6 +897,12 @@ const styles = StyleSheet.create({
|
|
|
761
897
|
marginTop: 8,
|
|
762
898
|
marginHorizontal: 16
|
|
763
899
|
},
|
|
900
|
+
/** Gutter matching the event cards below it, so the pills line up with them. */
|
|
901
|
+
eventFilterBar: {
|
|
902
|
+
paddingHorizontal: 12,
|
|
903
|
+
paddingTop: 8,
|
|
904
|
+
paddingBottom: 4
|
|
905
|
+
},
|
|
764
906
|
emptyState: {
|
|
765
907
|
flex: 1,
|
|
766
908
|
justifyContent: "center",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Editing a key whose value is a single string, number or boolean.
|
|
5
|
+
*
|
|
6
|
+
* A tree of one row is not a tree, so there's nothing here to select, reorder
|
|
7
|
+
* or append to — which means none of the structural editor applies. And with
|
|
8
|
+
* one edit producing one write, there's nothing to buffer either: a draft would
|
|
9
|
+
* only add a step between typing the value and storing it.
|
|
10
|
+
*
|
|
11
|
+
* So this is just the value modal over whatever the user was looking at, on
|
|
12
|
+
* both the phone and the dashboard. Changing one string shouldn't black out the
|
|
13
|
+
* screen it came from.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useCallback, useState } from "react";
|
|
17
|
+
import { Alert } from "react-native";
|
|
18
|
+
import { StorageValueEditModal } from "./StorageValueEditModal";
|
|
19
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
20
|
+
export function StorageScalarKeyEditor({
|
|
21
|
+
storageKey,
|
|
22
|
+
value,
|
|
23
|
+
onSave,
|
|
24
|
+
onClose
|
|
25
|
+
}) {
|
|
26
|
+
const [saving, setSaving] = useState(false);
|
|
27
|
+
const save = useCallback(next => {
|
|
28
|
+
if (saving) return;
|
|
29
|
+
setSaving(true);
|
|
30
|
+
onSave(JSON.stringify(next)).then(onClose).catch(e => {
|
|
31
|
+
setSaving(false);
|
|
32
|
+
Alert.alert("Couldn't save", e instanceof Error ? e.message : String(e));
|
|
33
|
+
});
|
|
34
|
+
}, [onClose, onSave, saving]);
|
|
35
|
+
return /*#__PURE__*/_jsx(StorageValueEditModal, {
|
|
36
|
+
path: [storageKey.key],
|
|
37
|
+
value: value,
|
|
38
|
+
onSave: save,
|
|
39
|
+
onCancel: onClose,
|
|
40
|
+
busy: saving
|
|
41
|
+
});
|
|
42
|
+
}
|