@buoy-gg/storage 3.0.1 → 4.0.1
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/README.md +1 -1
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +25 -4
- package/lib/commonjs/storage/components/GameUIStorageStats.js +2 -2
- package/lib/commonjs/storage/components/SelectionActionBar.js +16 -3
- package/lib/commonjs/storage/components/StorageBrowserMode.js +6 -2
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +2 -2
- package/lib/commonjs/storage/components/StorageKeyCard.js +5 -5
- package/lib/commonjs/storage/components/StorageKeyRow.js +97 -8
- package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +47 -1
- package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +15 -3
- package/lib/commonjs/storage/hooks/useMMKVKeys.js +20 -2
- package/lib/commonjs/storage/stores/storageEventStore.js +84 -0
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +53 -0
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +148 -160
- package/lib/commonjs/storage/utils/asyncStorageCompat.js +89 -0
- package/lib/commonjs/storage/utils/clearAllStorage.js +2 -1
- package/lib/commonjs/storage/utils/mmkvTypeDetection.js +20 -5
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +3 -2
- package/lib/commonjs/storage/utils/valueType.js +41 -0
- package/lib/module/index.js +5 -0
- package/lib/module/storage/components/GameUIStorageBrowser.js +26 -4
- package/lib/module/storage/components/GameUIStorageStats.js +3 -2
- package/lib/module/storage/components/SelectionActionBar.js +17 -3
- package/lib/module/storage/components/StorageBrowserMode.js +6 -2
- package/lib/module/storage/components/StorageEventDetailContent.js +2 -2
- package/lib/module/storage/components/StorageKeyCard.js +5 -5
- package/lib/module/storage/components/StorageKeyRow.js +99 -10
- package/lib/module/storage/components/StorageKeySection.js +10 -4
- package/lib/module/storage/components/StorageModalWithTabs.js +47 -1
- package/lib/module/storage/hooks/useAsyncStorageKeys.js +16 -4
- package/lib/module/storage/hooks/useMMKVKeys.js +21 -3
- package/lib/module/storage/stores/storageEventStore.js +84 -0
- package/lib/module/storage/sync/storageSyncAdapter.js +48 -0
- package/lib/module/storage/utils/AsyncStorageListener.js +124 -135
- package/lib/module/storage/utils/asyncStorageCompat.js +81 -0
- package/lib/module/storage/utils/clearAllStorage.js +2 -1
- package/lib/module/storage/utils/mmkvTypeDetection.js +20 -5
- package/lib/module/storage/utils/storageTimeTravelUtils.js +3 -2
- package/lib/module/storage/utils/valueType.js +39 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +5 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageStats.d.ts.map +1 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts +3 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts +3 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +7 -1
- 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/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -1
- package/lib/typescript/storage/stores/storageEventStore.d.ts +8 -0
- package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +31 -0
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -0
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +20 -0
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/asyncStorageCompat.d.ts +30 -0
- package/lib/typescript/storage/utils/asyncStorageCompat.d.ts.map +1 -0
- package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +1 -1
- package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +1 -1
- 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 +6 -6
package/README.md
CHANGED
|
@@ -570,7 +570,7 @@ Required storage keys can be validated for:
|
|
|
570
570
|
## Dependencies
|
|
571
571
|
|
|
572
572
|
- `@buoy/shared-ui` - Common UI components and utilities
|
|
573
|
-
- `@react-native-async-storage/async-storage` - AsyncStorage implementation (peer dependency)
|
|
573
|
+
- `@react-native-async-storage/async-storage` - AsyncStorage implementation (peer dependency; supports v2 and v3 — `^2.0.0 || ^3.1.0`. Avoid 3.0.x, which has an Android build issue fixed upstream in 3.1.0.)
|
|
574
574
|
- `react-native-mmkv` - MMKV storage implementation (optional peer dependency)
|
|
575
575
|
- React and React Native (peer dependencies)
|
|
576
576
|
|
package/lib/commonjs/index.js
CHANGED
|
@@ -153,6 +153,12 @@ Object.defineProperty(exports, "storageEventStore", {
|
|
|
153
153
|
return _storageEventStore.storageEventStore;
|
|
154
154
|
}
|
|
155
155
|
});
|
|
156
|
+
Object.defineProperty(exports, "storageSyncAdapter", {
|
|
157
|
+
enumerable: true,
|
|
158
|
+
get: function () {
|
|
159
|
+
return _storageSyncAdapter.storageSyncAdapter;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
156
162
|
Object.defineProperty(exports, "storageToolPreset", {
|
|
157
163
|
enumerable: true,
|
|
158
164
|
get: function () {
|
|
@@ -234,4 +240,5 @@ var _mmkvAvailability = require("./storage/utils/mmkvAvailability");
|
|
|
234
240
|
var _mmkvTypeDetection = require("./storage/utils/mmkvTypeDetection");
|
|
235
241
|
var _storageTimeTravelUtils = require("./storage/utils/storageTimeTravelUtils");
|
|
236
242
|
var _MMKVInstanceRegistry = require("./storage/utils/MMKVInstanceRegistry");
|
|
243
|
+
var _storageSyncAdapter = require("./storage/sync/storageSyncAdapter");
|
|
237
244
|
var _storageEventStore = require("./storage/stores/storageEventStore");
|
|
@@ -63,7 +63,9 @@ function GameUIStorageBrowser({
|
|
|
63
63
|
storageDataRef,
|
|
64
64
|
eventCountByKey,
|
|
65
65
|
onViewHistory,
|
|
66
|
-
enabledStorageTypes
|
|
66
|
+
enabledStorageTypes,
|
|
67
|
+
pinnedKeys,
|
|
68
|
+
onTogglePin
|
|
67
69
|
}) {
|
|
68
70
|
const isPro = (0, _license.useIsPro)();
|
|
69
71
|
const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
|
|
@@ -173,6 +175,15 @@ function GameUIStorageBrowser({
|
|
|
173
175
|
refresh();
|
|
174
176
|
}, [refresh]);
|
|
175
177
|
|
|
178
|
+
// Hide the selected keys by adding each to the ignored-pattern filter store,
|
|
179
|
+
// then exit selection mode. Non-destructive — keys can be unhidden from the
|
|
180
|
+
// filters UI.
|
|
181
|
+
const handleHideKeys = (0, _react.useCallback)(keys => {
|
|
182
|
+
keys.forEach(k => onAddPattern?.(k.key));
|
|
183
|
+
setSelectedKeyIds(new Set());
|
|
184
|
+
setIsSelectMode(false);
|
|
185
|
+
}, [onAddPattern]);
|
|
186
|
+
|
|
176
187
|
// Memoized export data for copy functionality
|
|
177
188
|
const copyExportData = (0, _react.useMemo)(() => {
|
|
178
189
|
const allKeys = allStorageKeys;
|
|
@@ -395,8 +406,14 @@ function GameUIStorageBrowser({
|
|
|
395
406
|
// NOTE: MMKV instance filtering is now handled in mmkvStorageKeys memo (line 128)
|
|
396
407
|
// No need to filter again here
|
|
397
408
|
|
|
409
|
+
// Float pinned keys to the top (stable — preserves existing order otherwise)
|
|
410
|
+
if (pinnedKeys && pinnedKeys.size > 0) {
|
|
411
|
+
const pinned = keys.filter(k => pinnedKeys.has(k.key));
|
|
412
|
+
const rest = keys.filter(k => !pinnedKeys.has(k.key));
|
|
413
|
+
keys = [...pinned, ...rest];
|
|
414
|
+
}
|
|
398
415
|
return keys;
|
|
399
|
-
}, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes]);
|
|
416
|
+
}, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes, pinnedKeys]);
|
|
400
417
|
|
|
401
418
|
// For free users, limit visible keys to FREE_TIER_KEY_LIMIT
|
|
402
419
|
const visibleKeys = (0, _react.useMemo)(() => {
|
|
@@ -618,6 +635,7 @@ function GameUIStorageBrowser({
|
|
|
618
635
|
instance: inst.instance
|
|
619
636
|
})),
|
|
620
637
|
onDeleteComplete: handleDeleteComplete,
|
|
638
|
+
onHideKeys: handleHideKeys,
|
|
621
639
|
onSelectAll: handleSelectAll,
|
|
622
640
|
onClearSelection: handleClearSelection,
|
|
623
641
|
totalVisibleKeys: visibleKeys.length
|
|
@@ -630,7 +648,10 @@ function GameUIStorageBrowser({
|
|
|
630
648
|
selectedKeys: selectedKeyIds,
|
|
631
649
|
onSelectionChange: handleSelectionChange,
|
|
632
650
|
eventCountByKey: eventCountByKey,
|
|
633
|
-
onViewHistory: onViewHistory
|
|
651
|
+
onViewHistory: onViewHistory,
|
|
652
|
+
onHideKey: key => handleHideKeys([key]),
|
|
653
|
+
pinnedKeys: pinnedKeys,
|
|
654
|
+
onTogglePin: onTogglePin
|
|
634
655
|
}), hasLockedKeys && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
635
656
|
style: styles.limitBanner,
|
|
636
657
|
onPress: () => setShowUpgradeModal(true),
|
|
@@ -684,7 +705,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
684
705
|
paddingBottom: 32
|
|
685
706
|
},
|
|
686
707
|
backgroundGrid: {
|
|
687
|
-
...
|
|
708
|
+
..._sharedUi.absoluteFill,
|
|
688
709
|
opacity: 0.006,
|
|
689
710
|
backgroundColor: _sharedUi.gameUIColors.info
|
|
690
711
|
},
|
|
@@ -517,7 +517,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
517
517
|
shadowRadius: 8
|
|
518
518
|
},
|
|
519
519
|
healthGridOverlay: {
|
|
520
|
-
...
|
|
520
|
+
..._sharedUi.absoluteFill,
|
|
521
521
|
opacity: 0.1,
|
|
522
522
|
borderWidth: 1,
|
|
523
523
|
borderColor: "rgba(255, 255, 255, 0.1)",
|
|
@@ -540,7 +540,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
540
540
|
backgroundColor: "rgba(255, 0, 0, 0.02)"
|
|
541
541
|
},
|
|
542
542
|
cardGlow: {
|
|
543
|
-
...
|
|
543
|
+
..._sharedUi.absoluteFill,
|
|
544
544
|
opacity: 0.3
|
|
545
545
|
},
|
|
546
546
|
cardHeader: {
|
|
@@ -8,13 +8,13 @@ var _react = require("react");
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
10
|
var _license = require("@buoy-gg/license");
|
|
11
|
-
var
|
|
11
|
+
var _asyncStorageCompat = require("../utils/asyncStorageCompat");
|
|
12
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
13
|
function SelectionActionBar({
|
|
15
14
|
selectedKeys,
|
|
16
15
|
mmkvInstances,
|
|
17
16
|
onDeleteComplete,
|
|
17
|
+
onHideKeys,
|
|
18
18
|
onSelectAll,
|
|
19
19
|
onClearSelection,
|
|
20
20
|
totalVisibleKeys
|
|
@@ -45,6 +45,12 @@ function SelectionActionBar({
|
|
|
45
45
|
};
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
// Hide selected keys (adds them to the filter store; non-destructive)
|
|
49
|
+
const handleHideSelected = () => {
|
|
50
|
+
if (selectedCount === 0) return;
|
|
51
|
+
onHideKeys?.(selectedKeys);
|
|
52
|
+
};
|
|
53
|
+
|
|
48
54
|
// Handle delete selected keys
|
|
49
55
|
const handleDeleteSelected = () => {
|
|
50
56
|
if (selectedCount === 0) return;
|
|
@@ -68,7 +74,7 @@ function SelectionActionBar({
|
|
|
68
74
|
|
|
69
75
|
// Delete AsyncStorage keys
|
|
70
76
|
if (asyncKeys.length > 0) {
|
|
71
|
-
await
|
|
77
|
+
await (0, _asyncStorageCompat.removeMany)(asyncKeys.map(k => k.key));
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
// Delete MMKV keys
|
|
@@ -131,6 +137,13 @@ function SelectionActionBar({
|
|
|
131
137
|
success: _sharedUi.macOSColors.semantic.success,
|
|
132
138
|
error: _sharedUi.macOSColors.semantic.error
|
|
133
139
|
}
|
|
140
|
+
}), onHideKeys && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
141
|
+
style: styles.actionButton,
|
|
142
|
+
onPress: handleHideSelected,
|
|
143
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EyeOff, {
|
|
144
|
+
size: 14,
|
|
145
|
+
color: _sharedUi.macOSColors.semantic.warning
|
|
146
|
+
})
|
|
134
147
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
135
148
|
style: styles.actionButton,
|
|
136
149
|
onPress: handleDeleteSelected,
|
|
@@ -20,7 +20,9 @@ function StorageBrowserMode({
|
|
|
20
20
|
storageDataRef,
|
|
21
21
|
eventCountByKey,
|
|
22
22
|
onViewHistory,
|
|
23
|
-
enabledStorageTypes
|
|
23
|
+
enabledStorageTypes,
|
|
24
|
+
pinnedKeys,
|
|
25
|
+
onTogglePin
|
|
24
26
|
}) {
|
|
25
27
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_GameUIStorageBrowser.GameUIStorageBrowser, {
|
|
26
28
|
requiredStorageKeys: requiredStorageKeys,
|
|
@@ -32,6 +34,8 @@ function StorageBrowserMode({
|
|
|
32
34
|
storageDataRef: storageDataRef,
|
|
33
35
|
eventCountByKey: eventCountByKey,
|
|
34
36
|
onViewHistory: onViewHistory,
|
|
35
|
-
enabledStorageTypes: enabledStorageTypes
|
|
37
|
+
enabledStorageTypes: enabledStorageTypes,
|
|
38
|
+
pinnedKeys: pinnedKeys,
|
|
39
|
+
onTogglePin: onTogglePin
|
|
36
40
|
});
|
|
37
41
|
}
|
|
@@ -241,7 +241,7 @@ function StorageEventDetailContent({
|
|
|
241
241
|
children: "CURRENT VALUE"
|
|
242
242
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
243
243
|
style: styles.valueHeaderBadges,
|
|
244
|
-
children: [action
|
|
244
|
+
children: [action ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
245
245
|
style: [styles.actionBadge, {
|
|
246
246
|
backgroundColor: `${actionColor}20`
|
|
247
247
|
}],
|
|
@@ -251,7 +251,7 @@ function StorageEventDetailContent({
|
|
|
251
251
|
}],
|
|
252
252
|
children: (0, _storageActionHelpers.translateStorageAction)(action)
|
|
253
253
|
})
|
|
254
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
254
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
255
255
|
style: styles.typeBadge,
|
|
256
256
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
257
257
|
style: styles.typeText,
|
|
@@ -150,10 +150,10 @@ function StorageKeyCard({
|
|
|
150
150
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
151
151
|
style: styles.storageKeyText,
|
|
152
152
|
children: storageKey.key
|
|
153
|
-
}), storageKey.description
|
|
153
|
+
}), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
154
154
|
style: styles.descriptionText,
|
|
155
155
|
children: storageKey.description
|
|
156
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
156
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
157
157
|
style: styles.cardHeaderMeta,
|
|
158
158
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
159
159
|
style: [styles.statusBadge, {
|
|
@@ -282,13 +282,13 @@ function StorageKeyCard({
|
|
|
282
282
|
style: styles.typeHelperText,
|
|
283
283
|
children: ["Current type: ", (0, _valueType.getValueTypeLabel)(storageKey.value)]
|
|
284
284
|
})]
|
|
285
|
-
}), storageKey.lastUpdated
|
|
285
|
+
}), storageKey.lastUpdated ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
286
286
|
style: styles.metaInfo,
|
|
287
287
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
288
288
|
style: styles.metaLabel,
|
|
289
|
-
children: ["Last
|
|
289
|
+
children: ["Last updated: ", storageKey.lastUpdated.toLocaleString()]
|
|
290
290
|
})
|
|
291
|
-
})]
|
|
291
|
+
}) : null]
|
|
292
292
|
}), isExpanded && !hasValue && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
293
293
|
style: styles.cardBody,
|
|
294
294
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
@@ -81,11 +81,18 @@ function StorageKeyRow({
|
|
|
81
81
|
isSelected = false,
|
|
82
82
|
onSelectionChange,
|
|
83
83
|
eventCount,
|
|
84
|
-
onViewHistory
|
|
84
|
+
onViewHistory,
|
|
85
|
+
onHideKey,
|
|
86
|
+
isPinned = false,
|
|
87
|
+
onTogglePin
|
|
85
88
|
}) {
|
|
86
89
|
const config = getStatusConfig(storageKey.status);
|
|
87
90
|
const hasValue = storageKey.value !== undefined && storageKey.value !== null;
|
|
88
91
|
|
|
92
|
+
// Booleans get a dedicated colored badge instead of an inline text preview so
|
|
93
|
+
// their value is scannable at a glance.
|
|
94
|
+
const isBoolean = hasValue && typeof storageKey.value === "boolean";
|
|
95
|
+
|
|
89
96
|
// Format primary text - show the key
|
|
90
97
|
const primaryText = storageKey.key;
|
|
91
98
|
|
|
@@ -189,7 +196,7 @@ function StorageKeyRow({
|
|
|
189
196
|
style: styles.expandedExpected,
|
|
190
197
|
children: String(storageKey.expectedValue)
|
|
191
198
|
})]
|
|
192
|
-
}), storageKey.description
|
|
199
|
+
}), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
193
200
|
style: styles.expandedRow,
|
|
194
201
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
195
202
|
style: styles.expandedLabel,
|
|
@@ -198,7 +205,46 @@ function StorageKeyRow({
|
|
|
198
205
|
style: styles.expandedDescription,
|
|
199
206
|
children: storageKey.description
|
|
200
207
|
})]
|
|
201
|
-
})
|
|
208
|
+
}) : null, onTogglePin || onHideKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
209
|
+
style: styles.actionRow,
|
|
210
|
+
children: [onTogglePin ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
211
|
+
style: [styles.actionChip, isPinned && styles.actionChipActive],
|
|
212
|
+
onPress: () => onTogglePin(storageKey.key),
|
|
213
|
+
hitSlop: {
|
|
214
|
+
top: 6,
|
|
215
|
+
bottom: 6,
|
|
216
|
+
left: 6,
|
|
217
|
+
right: 6
|
|
218
|
+
},
|
|
219
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pin, {
|
|
220
|
+
size: 12,
|
|
221
|
+
color: _sharedUi.macOSColors.semantic.info
|
|
222
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
223
|
+
style: [styles.actionChipText, {
|
|
224
|
+
color: _sharedUi.macOSColors.semantic.info
|
|
225
|
+
}],
|
|
226
|
+
children: isPinned ? "Unpin" : "Pin to top"
|
|
227
|
+
})]
|
|
228
|
+
}) : null, onHideKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
229
|
+
style: styles.actionChip,
|
|
230
|
+
onPress: () => onHideKey(storageKey),
|
|
231
|
+
hitSlop: {
|
|
232
|
+
top: 6,
|
|
233
|
+
bottom: 6,
|
|
234
|
+
left: 6,
|
|
235
|
+
right: 6
|
|
236
|
+
},
|
|
237
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EyeOff, {
|
|
238
|
+
size: 12,
|
|
239
|
+
color: _sharedUi.macOSColors.semantic.warning
|
|
240
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
241
|
+
style: [styles.actionChipText, {
|
|
242
|
+
color: _sharedUi.macOSColors.semantic.warning
|
|
243
|
+
}],
|
|
244
|
+
children: "Hide from list"
|
|
245
|
+
})]
|
|
246
|
+
}) : null]
|
|
247
|
+
}) : null]
|
|
202
248
|
});
|
|
203
249
|
|
|
204
250
|
// Handle checkbox press in select mode
|
|
@@ -233,17 +279,28 @@ function StorageKeyRow({
|
|
|
233
279
|
statusLabel: config.label,
|
|
234
280
|
statusSublabel: config.sublabel,
|
|
235
281
|
primaryText: primaryText,
|
|
236
|
-
secondaryText: hasValue ? (0, _valueType.getValueTypeLabel)(storageKey.value) : undefined,
|
|
282
|
+
secondaryText: hasValue ? isBoolean ? (0, _valueType.getValueTypeLabel)(storageKey.value) : (0, _valueType.getValueTypeWithPreview)(storageKey.value) : undefined,
|
|
283
|
+
secondaryAccessory: isBoolean ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
|
|
284
|
+
size: "sm",
|
|
285
|
+
color: storageKey.value ? _sharedUi.macOSColors.semantic.success : _sharedUi.macOSColors.semantic.error,
|
|
286
|
+
children: storageKey.value ? "true" : "false"
|
|
287
|
+
}) : undefined,
|
|
237
288
|
expandedContent: expandedContent,
|
|
238
289
|
isExpanded: isExpanded,
|
|
239
290
|
expandedGlowColor: config.color,
|
|
240
|
-
customBadge: /*#__PURE__*/(0, _jsxRuntime.
|
|
241
|
-
|
|
242
|
-
children:
|
|
291
|
+
customBadge: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
292
|
+
style: styles.badgeRow,
|
|
293
|
+
children: [isPinned && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pin, {
|
|
294
|
+
size: 12,
|
|
295
|
+
color: _sharedUi.macOSColors.semantic.info
|
|
296
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
|
|
297
|
+
color: getStorageTypeColor(storageKey.storageType),
|
|
298
|
+
children: storageTypeLabel
|
|
299
|
+
})]
|
|
243
300
|
}),
|
|
244
301
|
showChevron: !isSelectMode,
|
|
245
302
|
onPress: isSelectMode ? handleCheckboxPress : onPress ? () => onPress(storageKey) : undefined
|
|
246
|
-
}), eventCount != null && eventCount >
|
|
303
|
+
}), eventCount != null && eventCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
247
304
|
style: [styles.absCountBadge, {
|
|
248
305
|
backgroundColor: _sharedUi.macOSColors.semantic.warning + "22",
|
|
249
306
|
borderColor: _sharedUi.macOSColors.semantic.warning + "55"
|
|
@@ -272,6 +329,11 @@ const getStorageTypeColor = storageType => {
|
|
|
272
329
|
}
|
|
273
330
|
};
|
|
274
331
|
const styles = _reactNative.StyleSheet.create({
|
|
332
|
+
badgeRow: {
|
|
333
|
+
flexDirection: "row",
|
|
334
|
+
alignItems: "center",
|
|
335
|
+
gap: 6
|
|
336
|
+
},
|
|
275
337
|
expandedContainer: {
|
|
276
338
|
gap: 8
|
|
277
339
|
},
|
|
@@ -345,6 +407,33 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
345
407
|
fontWeight: "700",
|
|
346
408
|
fontFamily: "monospace"
|
|
347
409
|
},
|
|
410
|
+
actionRow: {
|
|
411
|
+
flexDirection: "row",
|
|
412
|
+
alignItems: "center",
|
|
413
|
+
flexWrap: "wrap",
|
|
414
|
+
gap: 8,
|
|
415
|
+
marginTop: 4
|
|
416
|
+
},
|
|
417
|
+
actionChip: {
|
|
418
|
+
flexDirection: "row",
|
|
419
|
+
alignItems: "center",
|
|
420
|
+
gap: 6,
|
|
421
|
+
paddingVertical: 6,
|
|
422
|
+
paddingHorizontal: 10,
|
|
423
|
+
borderRadius: 6,
|
|
424
|
+
backgroundColor: _sharedUi.macOSColors.background.card,
|
|
425
|
+
borderWidth: 1,
|
|
426
|
+
borderColor: _sharedUi.macOSColors.border.default
|
|
427
|
+
},
|
|
428
|
+
actionChipActive: {
|
|
429
|
+
backgroundColor: _sharedUi.macOSColors.semantic.info + "15",
|
|
430
|
+
borderColor: _sharedUi.macOSColors.semantic.info + "44"
|
|
431
|
+
},
|
|
432
|
+
actionChipText: {
|
|
433
|
+
fontSize: 11,
|
|
434
|
+
fontWeight: "600",
|
|
435
|
+
fontFamily: "monospace"
|
|
436
|
+
},
|
|
348
437
|
viewHistoryButton: {
|
|
349
438
|
marginLeft: 4
|
|
350
439
|
},
|
|
@@ -27,7 +27,10 @@ function StorageKeySection({
|
|
|
27
27
|
selectedKeys = new Set(),
|
|
28
28
|
onSelectionChange,
|
|
29
29
|
eventCountByKey,
|
|
30
|
-
onViewHistory
|
|
30
|
+
onViewHistory,
|
|
31
|
+
onHideKey,
|
|
32
|
+
pinnedKeys,
|
|
33
|
+
onTogglePin
|
|
31
34
|
}) {
|
|
32
35
|
const [expandedKey, setExpandedKey] = (0, _react.useState)(null);
|
|
33
36
|
const handleKeyPress = (0, _react.useCallback)(storageKey => {
|
|
@@ -60,14 +63,14 @@ function StorageKeySection({
|
|
|
60
63
|
if (keys.length === 0) return null;
|
|
61
64
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
62
65
|
style: styles.sectionContainer,
|
|
63
|
-
children: [title
|
|
66
|
+
children: [title ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
|
|
64
67
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
|
|
65
68
|
children: title
|
|
66
69
|
}), count >= 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Badge, {
|
|
67
70
|
count: count,
|
|
68
71
|
color: headerColor
|
|
69
72
|
})]
|
|
70
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
73
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
71
74
|
style: styles.sectionContent,
|
|
72
75
|
children: keys.map(storageKey => {
|
|
73
76
|
// Create unique key by combining storage type, instance ID (if present), and key name
|
|
@@ -80,7 +83,10 @@ function StorageKeySection({
|
|
|
80
83
|
isSelected: selectedKeys.has(uniqueKey),
|
|
81
84
|
onSelectionChange: onSelectionChange,
|
|
82
85
|
eventCount: eventCountByKey?.[storageKey.key],
|
|
83
|
-
onViewHistory: onViewHistory ? () => onViewHistory(storageKey.key) : undefined
|
|
86
|
+
onViewHistory: onViewHistory ? () => onViewHistory(storageKey.key) : undefined,
|
|
87
|
+
onHideKey: onHideKey,
|
|
88
|
+
isPinned: pinnedKeys?.has(storageKey.key),
|
|
89
|
+
onTogglePin: onTogglePin
|
|
84
90
|
}, uniqueKey);
|
|
85
91
|
})
|
|
86
92
|
})]
|
|
@@ -60,8 +60,10 @@ function StorageModalWithTabs({
|
|
|
60
60
|
);
|
|
61
61
|
const [enabledStorageTypes, setEnabledStorageTypes] = (0, _react.useState)(new Set(['async', 'mmkv', 'secure']) // All enabled by default
|
|
62
62
|
);
|
|
63
|
+
const [pinnedKeys, setPinnedKeys] = (0, _react.useState)(new Set());
|
|
63
64
|
const lastEventRef = (0, _react.useRef)(null);
|
|
64
65
|
const hasLoadedFilters = (0, _react.useRef)(false);
|
|
66
|
+
const hasLoadedPins = (0, _react.useRef)(false);
|
|
65
67
|
const hasLoadedTabState = (0, _react.useRef)(false);
|
|
66
68
|
const hasLoadedMonitoringState = (0, _react.useRef)(false);
|
|
67
69
|
|
|
@@ -176,6 +178,37 @@ function StorageModalWithTabs({
|
|
|
176
178
|
saveFilters();
|
|
177
179
|
}, [ignoredPatterns]);
|
|
178
180
|
|
|
181
|
+
// Load persisted pinned keys on mount
|
|
182
|
+
(0, _react.useEffect)(() => {
|
|
183
|
+
if (!visible || hasLoadedPins.current) return;
|
|
184
|
+
const loadPins = async () => {
|
|
185
|
+
try {
|
|
186
|
+
const stored = await _asyncStorage.default.getItem(_sharedUi.devToolsStorageKeys.storage.pinnedKeys());
|
|
187
|
+
if (stored) {
|
|
188
|
+
setPinnedKeys(new Set(JSON.parse(stored)));
|
|
189
|
+
}
|
|
190
|
+
hasLoadedPins.current = true;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// Failed to load pinned keys
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
loadPins();
|
|
196
|
+
}, [visible]);
|
|
197
|
+
|
|
198
|
+
// Save pinned keys when they change
|
|
199
|
+
(0, _react.useEffect)(() => {
|
|
200
|
+
if (!hasLoadedPins.current) return; // Don't save on initial load
|
|
201
|
+
|
|
202
|
+
const savePins = async () => {
|
|
203
|
+
try {
|
|
204
|
+
await _asyncStorage.default.setItem(_sharedUi.devToolsStorageKeys.storage.pinnedKeys(), JSON.stringify(Array.from(pinnedKeys)));
|
|
205
|
+
} catch (error) {
|
|
206
|
+
// Failed to save pinned keys
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
savePins();
|
|
210
|
+
}, [pinnedKeys]);
|
|
211
|
+
|
|
179
212
|
// Event listener setup - now handled by useStorageEvents hook
|
|
180
213
|
// The hook subscribes to the centralized storageEventStore
|
|
181
214
|
|
|
@@ -210,6 +243,17 @@ function StorageModalWithTabs({
|
|
|
210
243
|
const handleAddPattern = (0, _react.useCallback)(pattern => {
|
|
211
244
|
setIgnoredPatterns(prev => new Set([...prev, pattern]));
|
|
212
245
|
}, []);
|
|
246
|
+
const handleTogglePin = (0, _react.useCallback)(key => {
|
|
247
|
+
setPinnedKeys(prev => {
|
|
248
|
+
const next = new Set(prev);
|
|
249
|
+
if (next.has(key)) {
|
|
250
|
+
next.delete(key);
|
|
251
|
+
} else {
|
|
252
|
+
next.add(key);
|
|
253
|
+
}
|
|
254
|
+
return next;
|
|
255
|
+
});
|
|
256
|
+
}, []);
|
|
213
257
|
const handleToggleFilters = (0, _react.useCallback)(() => {
|
|
214
258
|
setShowFilters(!showFilters);
|
|
215
259
|
}, [showFilters]);
|
|
@@ -508,7 +552,9 @@ function StorageModalWithTabs({
|
|
|
508
552
|
storageDataRef: storageDataRef,
|
|
509
553
|
eventCountByKey: eventCountByKey,
|
|
510
554
|
onViewHistory: handleViewHistoryFromBrowser,
|
|
511
|
-
enabledStorageTypes: enabledStorageTypes
|
|
555
|
+
enabledStorageTypes: enabledStorageTypes,
|
|
556
|
+
pinnedKeys: pinnedKeys,
|
|
557
|
+
onTogglePin: handleTogglePin
|
|
512
558
|
});
|
|
513
559
|
}
|
|
514
560
|
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.useAsyncStorageKeys = useAsyncStorageKeys;
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
9
|
+
var _asyncStorageCompat = require("../utils/asyncStorageCompat");
|
|
9
10
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
11
|
function useAsyncStorageKeys(requiredStorageKeys = []) {
|
|
11
12
|
// State management
|
|
@@ -13,8 +14,18 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
|
|
|
13
14
|
const [isLoading, setIsLoading] = (0, _react.useState)(true);
|
|
14
15
|
const [error, setError] = (0, _react.useState)(null);
|
|
15
16
|
|
|
17
|
+
// Callers routinely pass a fresh array literal (or rely on the `= []`
|
|
18
|
+
// default, which is a new array every render). Depending on its identity
|
|
19
|
+
// would recreate `fetchStorageData` each render, re-fire the effect, and
|
|
20
|
+
// loop AsyncStorage.getAllKeys/multiGet forever. Depend on a stable content
|
|
21
|
+
// signature instead, and read the latest array via a ref inside the fetch.
|
|
22
|
+
const requiredSignature = JSON.stringify(requiredStorageKeys);
|
|
23
|
+
const requiredRef = (0, _react.useRef)(requiredStorageKeys);
|
|
24
|
+
requiredRef.current = requiredStorageKeys;
|
|
25
|
+
|
|
16
26
|
// Fetch all keys and values from AsyncStorage
|
|
17
27
|
const fetchStorageData = (0, _react.useCallback)(async () => {
|
|
28
|
+
const requiredStorageKeys = requiredRef.current;
|
|
18
29
|
setIsLoading(true);
|
|
19
30
|
setError(null);
|
|
20
31
|
try {
|
|
@@ -26,8 +37,8 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
|
|
|
26
37
|
return;
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
// 2. Get all values
|
|
30
|
-
const allKeyValuePairs = await
|
|
40
|
+
// 2. Get all values (compat helper normalizes v2 multiGet / v3 getMany)
|
|
41
|
+
const allKeyValuePairs = await (0, _asyncStorageCompat.readMany)(allKeys);
|
|
31
42
|
|
|
32
43
|
// 3. Process keys into StorageKeyInfo format
|
|
33
44
|
const allStorageKeys = [];
|
|
@@ -111,7 +122,8 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
|
|
|
111
122
|
} finally {
|
|
112
123
|
setIsLoading(false);
|
|
113
124
|
}
|
|
114
|
-
|
|
125
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
126
|
+
}, [requiredSignature]);
|
|
115
127
|
|
|
116
128
|
// Initial fetch
|
|
117
129
|
(0, _react.useEffect)(() => {
|
|
@@ -76,8 +76,16 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
|
|
|
76
76
|
const [isLoading, setIsLoading] = (0, _react.useState)(true);
|
|
77
77
|
const [error, setError] = (0, _react.useState)(null);
|
|
78
78
|
|
|
79
|
+
// Depend on a stable content signature rather than the array identity — a
|
|
80
|
+
// fresh `requiredStorageKeys` literal each render would otherwise loop the
|
|
81
|
+
// fetch effect. Read the latest array via ref inside the fetch.
|
|
82
|
+
const requiredSignature = JSON.stringify(requiredStorageKeys);
|
|
83
|
+
const requiredRef = (0, _react.useRef)(requiredStorageKeys);
|
|
84
|
+
requiredRef.current = requiredStorageKeys;
|
|
85
|
+
|
|
79
86
|
// Fetch all keys and values from MMKV instance
|
|
80
87
|
const fetchStorageData = (0, _react.useCallback)(() => {
|
|
88
|
+
const requiredStorageKeys = requiredRef.current;
|
|
81
89
|
setIsLoading(true);
|
|
82
90
|
setError(null);
|
|
83
91
|
try {
|
|
@@ -209,7 +217,8 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
|
|
|
209
217
|
} finally {
|
|
210
218
|
setIsLoading(false);
|
|
211
219
|
}
|
|
212
|
-
|
|
220
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
221
|
+
}, [instance, instanceId, requiredSignature]);
|
|
213
222
|
|
|
214
223
|
// Initial fetch
|
|
215
224
|
(0, _react.useEffect)(() => {
|
|
@@ -255,7 +264,15 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
|
|
|
255
264
|
const [storageKeys, setStorageKeys] = (0, _react.useState)([]);
|
|
256
265
|
const [isLoading, setIsLoading] = (0, _react.useState)(true);
|
|
257
266
|
const [error, setError] = (0, _react.useState)(null);
|
|
267
|
+
|
|
268
|
+
// Depend on a stable content signature rather than the array identity — a
|
|
269
|
+
// fresh `requiredStorageKeys` literal each render would otherwise loop the
|
|
270
|
+
// fetch effect. Read the latest array via ref inside the fetch.
|
|
271
|
+
const requiredSignature = JSON.stringify(requiredStorageKeys);
|
|
272
|
+
const requiredRef = (0, _react.useRef)(requiredStorageKeys);
|
|
273
|
+
requiredRef.current = requiredStorageKeys;
|
|
258
274
|
const fetchStorageData = (0, _react.useCallback)(() => {
|
|
275
|
+
const requiredStorageKeys = requiredRef.current;
|
|
259
276
|
setIsLoading(true);
|
|
260
277
|
setError(null);
|
|
261
278
|
try {
|
|
@@ -349,7 +366,8 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
|
|
|
349
366
|
} finally {
|
|
350
367
|
setIsLoading(false);
|
|
351
368
|
}
|
|
352
|
-
|
|
369
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
370
|
+
}, [instances, requiredSignature]);
|
|
353
371
|
(0, _react.useEffect)(() => {
|
|
354
372
|
fetchStorageData();
|
|
355
373
|
}, [fetchStorageData]);
|