@buoy-gg/storage 7.0.30 → 7.0.31
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/storage/components/GameUIStorageBrowser.js +92 -21
- package/lib/commonjs/storage/components/StorageBrowserMode.js +8 -2
- package/lib/commonjs/storage/components/StorageKeyDetailScreen.js +572 -0
- package/lib/commonjs/storage/components/StorageKeyRow.js +21 -2
- package/lib/commonjs/storage/components/StorageKeySection.js +44 -21
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +24 -2
- package/lib/commonjs/storage/components/StorageSaveNotice.js +171 -0
- package/lib/commonjs/storage/components/StorageValueEditModal.js +11 -364
- package/lib/commonjs/storage/hooks/useKeyEditor.js +12 -85
- package/lib/commonjs/storage/hooks/useTreeDraft.js +7 -100
- package/lib/module/storage/components/GameUIStorageBrowser.js +94 -23
- package/lib/module/storage/components/StorageBrowserMode.js +8 -2
- package/lib/module/storage/components/StorageKeyDetailScreen.js +570 -0
- package/lib/module/storage/components/StorageKeyRow.js +20 -3
- package/lib/module/storage/components/StorageKeySection.js +43 -21
- package/lib/module/storage/components/StorageModalWithTabs.js +24 -2
- package/lib/module/storage/components/StorageSaveNotice.js +168 -0
- package/lib/module/storage/components/StorageValueEditModal.js +13 -366
- package/lib/module/storage/hooks/useKeyEditor.js +14 -87
- package/lib/module/storage/hooks/useTreeDraft.js +9 -102
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +13 -2
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts +7 -2
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyDetailScreen.d.ts +46 -0
- package/lib/typescript/storage/components/StorageKeyDetailScreen.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +9 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts +19 -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/components/StorageSaveNotice.d.ts +6 -0
- package/lib/typescript/storage/components/StorageSaveNotice.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts +9 -31
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts +7 -31
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts +7 -66
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +1 -1
- package/package.json +5 -5
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { useMemo, useCallback, useState, useEffect } from "react";
|
|
3
|
+
import { useMemo, useCallback, useState, useEffect, useRef } from "react";
|
|
4
4
|
import { StyleSheet, Text, View, TouchableOpacity, ScrollView, Alert } from "react-native";
|
|
5
5
|
import { absoluteFill, useLimit, TIER_LIMITS } from "@buoy-gg/shared-ui";
|
|
6
6
|
import { Database, Zap, ProBadge, ProUpgradeModal } from "@buoy-gg/shared-ui";
|
|
7
7
|
import { isDevToolsStorageKey } from "@buoy-gg/shared-ui";
|
|
8
|
-
import { StorageKeySection } from "./StorageKeySection";
|
|
9
|
-
import {
|
|
8
|
+
import { StorageKeySection, storageRowId } from "./StorageKeySection";
|
|
9
|
+
import { StorageKeyDetailScreen } from "./StorageKeyDetailScreen";
|
|
10
10
|
import { StorageFilterCards } from "./StorageFilterCards";
|
|
11
11
|
import { useAsyncStorageKeys } from "../hooks/useAsyncStorageKeys";
|
|
12
12
|
import { addListener } from "../utils/AsyncStorageListener";
|
|
13
13
|
import { useMultiMMKVKeys } from "../hooks/useMMKVKeys";
|
|
14
14
|
import { useSecureStoreKeys } from "../hooks/useSecureStoreKeys";
|
|
15
15
|
import { useMMKVInstances } from "../hooks/useMMKVInstances";
|
|
16
|
+
import { StorageSaveNotice } from "./StorageSaveNotice";
|
|
16
17
|
import { StorageActionButtons } from "./StorageActionButtons";
|
|
17
18
|
import { SelectionActionBar } from "./SelectionActionBar";
|
|
18
19
|
// Pure JS (no react-native-mmkv dependency) — works with any registered
|
|
@@ -75,7 +76,10 @@ export function GameUIStorageBrowser({
|
|
|
75
76
|
onTogglePin,
|
|
76
77
|
storageTypeFilter,
|
|
77
78
|
onStorageTypeFilterChange,
|
|
78
|
-
applySafeAreaInset
|
|
79
|
+
applySafeAreaInset,
|
|
80
|
+
detailKey = null,
|
|
81
|
+
onOpenKey,
|
|
82
|
+
onCloseDetail
|
|
79
83
|
}) {
|
|
80
84
|
const isPro = useIsPro();
|
|
81
85
|
const keyLimit = useLimit("storage.keys");
|
|
@@ -100,16 +104,38 @@ export function GameUIStorageBrowser({
|
|
|
100
104
|
const activeStorageType = isStorageTypeControlled ? storageTypeFilter : internalStorageType;
|
|
101
105
|
const setActiveStorageType = isStorageTypeControlled ? onStorageTypeFilterChange : setInternalStorageType;
|
|
102
106
|
const [selectedMMKVInstance, setSelectedMMKVInstance] = useState(null);
|
|
107
|
+
const scrollRef = useRef(null);
|
|
108
|
+
const scrollOffsetRef = useRef(0);
|
|
109
|
+
const rowNodesRef = useRef(new Map());
|
|
110
|
+
const handleRowRef = useCallback((id, node) => {
|
|
111
|
+
if (node) rowNodesRef.current.set(id, node);else rowNodesRef.current.delete(id);
|
|
112
|
+
}, []);
|
|
103
113
|
|
|
104
114
|
/**
|
|
105
|
-
*
|
|
115
|
+
* Shown after every successful write, with the same sentence every time.
|
|
116
|
+
*
|
|
117
|
+
* An earlier version looked up which live store owned the key and offered to
|
|
118
|
+
* make that store re-read it. That required knowing what is linked to what,
|
|
119
|
+
* and every route to that knowledge is a guess — key names collide across
|
|
120
|
+
* backends, only zustand stores register at all, and a store can register
|
|
121
|
+
* after the lookup ran. A message that is right most of the time is the
|
|
122
|
+
* problem this tool has, not the fix for it.
|
|
123
|
+
*
|
|
124
|
+
* There is also no general mechanism to find: the app owns its cache, and
|
|
125
|
+
* only the app can invalidate it. So the tool promises the one thing that is
|
|
126
|
+
* true for every key, every backend and every persistence library — this is
|
|
127
|
+
* what loads next launch — and "make it live now" lives in the state tools,
|
|
128
|
+
* where the store you mean is the one you are looking at.
|
|
129
|
+
*/
|
|
130
|
+
/**
|
|
131
|
+
* Bumped on every save rather than flipped to `true`.
|
|
106
132
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
133
|
+
* The toast unmounts itself when it fades, and a boolean already `true` would
|
|
134
|
+
* not change on the next save — so a second edit would show nothing. The
|
|
135
|
+
* counter is the toast's `key`, so each save mounts a fresh one and the
|
|
136
|
+
* animation restarts from the beginning.
|
|
111
137
|
*/
|
|
112
|
-
const [
|
|
138
|
+
const [saveToastId, setSaveToastId] = useState(0);
|
|
113
139
|
|
|
114
140
|
// Selection mode state
|
|
115
141
|
const [isSelectMode, setIsSelectMode] = useState(false);
|
|
@@ -167,6 +193,22 @@ export function GameUIStorageBrowser({
|
|
|
167
193
|
return [...asyncStorageKeys, ...mmkvStorageKeys, ...secureStorageKeys];
|
|
168
194
|
}, [asyncStorageKeys, mmkvStorageKeys, secureStorageKeys]);
|
|
169
195
|
|
|
196
|
+
/**
|
|
197
|
+
* The CURRENT snapshot of the key the detail screen is open on. `detailKey`
|
|
198
|
+
* is only an identity — resolving it fresh each pass is what makes the
|
|
199
|
+
* detail screen live: the app writes, the snapshot refreshes, the screen
|
|
200
|
+
* follows. Falls back to the captured info because the backend hooks
|
|
201
|
+
* transiently EMPTY their lists mid-refresh — treating "missing from this
|
|
202
|
+
* render's list" as "deleted" closed the screen the moment it opened, so
|
|
203
|
+
* absence here is never acted on (deletion closes explicitly, from the
|
|
204
|
+
* delete flow itself).
|
|
205
|
+
*/
|
|
206
|
+
const detailInfo = useMemo(() => {
|
|
207
|
+
if (!detailKey) return null;
|
|
208
|
+
const id = storageRowId(detailKey);
|
|
209
|
+
return allStorageKeys.find(k => storageRowId(k) === id) ?? detailKey;
|
|
210
|
+
}, [detailKey, allStorageKeys]);
|
|
211
|
+
|
|
170
212
|
// Determine loading and error states
|
|
171
213
|
const isLoading = isLoadingAsync || isLoadingMMKV || isLoadingSecure;
|
|
172
214
|
const error = asyncError || mmkvError || secureError;
|
|
@@ -240,6 +282,7 @@ export function GameUIStorageBrowser({
|
|
|
240
282
|
}
|
|
241
283
|
await setStorageValue(storageKey, raw, mmkvInstances);
|
|
242
284
|
refresh();
|
|
285
|
+
setSaveToastId(id => id + 1);
|
|
243
286
|
}, [isPro, mmkvInstances, refresh]);
|
|
244
287
|
|
|
245
288
|
/**
|
|
@@ -266,7 +309,7 @@ export function GameUIStorageBrowser({
|
|
|
266
309
|
* Pro is checked here rather than on the button because it's an upsell, not
|
|
267
310
|
* a capability: the row shows the action and explains the wall on press.
|
|
268
311
|
*/
|
|
269
|
-
const handleDeleteKey = useCallback(storageKey => {
|
|
312
|
+
const handleDeleteKey = useCallback((storageKey, onDeleted) => {
|
|
270
313
|
if (!isPro) {
|
|
271
314
|
setShowUpgradeModal(true);
|
|
272
315
|
return;
|
|
@@ -284,6 +327,9 @@ export function GameUIStorageBrowser({
|
|
|
284
327
|
instance: inst.instance
|
|
285
328
|
})));
|
|
286
329
|
refresh();
|
|
330
|
+
// Only after the delete actually landed — a cancelled alert
|
|
331
|
+
// or a failed delete leaves the caller's screen alone.
|
|
332
|
+
onDeleted?.();
|
|
287
333
|
} catch (error) {
|
|
288
334
|
Alert.alert("Error", `Failed to delete key: ${error}`);
|
|
289
335
|
}
|
|
@@ -607,6 +653,31 @@ export function GameUIStorageBrowser({
|
|
|
607
653
|
const healthStatus = healthPercentage >= 90 ? "OPTIMAL" : healthPercentage >= 70 ? "WARNING" : "CRITICAL";
|
|
608
654
|
const healthColor = healthPercentage >= 90 ? gameUIColors.success : healthPercentage >= 70 ? gameUIColors.warning : gameUIColors.error;
|
|
609
655
|
|
|
656
|
+
// The detail screen REPLACES the list as the modal's whole body — the host
|
|
657
|
+
// modal's header carries back + the key's name, the React Query
|
|
658
|
+
// interaction (and the Zustand tool's). All hooks above have run; this
|
|
659
|
+
// return only swaps what's painted.
|
|
660
|
+
if (detailInfo) {
|
|
661
|
+
return /*#__PURE__*/_jsx(View, {
|
|
662
|
+
style: styles.root,
|
|
663
|
+
children: /*#__PURE__*/_jsx(StorageKeyDetailScreen, {
|
|
664
|
+
storageKey: detailInfo,
|
|
665
|
+
editBlockedReason: editBlockedFor(detailInfo),
|
|
666
|
+
eventCount: eventCountByKey?.[detailInfo.key],
|
|
667
|
+
onSave: raw => handleSaveValue(detailInfo, raw),
|
|
668
|
+
onViewHistory: onViewHistory ? () => onViewHistory(detailInfo.key) : undefined,
|
|
669
|
+
onDeleteKey: k => handleDeleteKey(k, () => onCloseDetail?.()),
|
|
670
|
+
onHideKey: k => {
|
|
671
|
+
handleHideKeys([k]);
|
|
672
|
+
onCloseDetail?.();
|
|
673
|
+
},
|
|
674
|
+
isPinned: pinnedKeys?.has(detailInfo.key) ?? false,
|
|
675
|
+
onTogglePin: onTogglePin,
|
|
676
|
+
applySafeAreaInset: applySafeAreaInset
|
|
677
|
+
})
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
610
681
|
// Loading state
|
|
611
682
|
if (isLoading && allStorageKeys.length === 0) {
|
|
612
683
|
return /*#__PURE__*/_jsxs(View, {
|
|
@@ -808,11 +879,18 @@ export function GameUIStorageBrowser({
|
|
|
808
879
|
});
|
|
809
880
|
return /*#__PURE__*/_jsxs(View, {
|
|
810
881
|
style: styles.root,
|
|
811
|
-
children: [/*#__PURE__*/
|
|
882
|
+
children: [saveToastId > 0 ? /*#__PURE__*/_jsx(StorageSaveNotice, {
|
|
883
|
+
onDone: () => setSaveToastId(0)
|
|
884
|
+
}, saveToastId) : null, /*#__PURE__*/_jsxs(ScrollView
|
|
812
885
|
// Named because `by.type("RCTScrollView")` doesn't match on Fabric
|
|
813
886
|
// (renamed class) — automation scrolls below-the-fold rows by this id.
|
|
814
887
|
, {
|
|
815
888
|
testID: "buoy-storage-scroll",
|
|
889
|
+
ref: scrollRef,
|
|
890
|
+
onScroll: e => {
|
|
891
|
+
scrollOffsetRef.current = e.nativeEvent.contentOffset.y;
|
|
892
|
+
},
|
|
893
|
+
scrollEventThrottle: 32,
|
|
816
894
|
style: styles.scrollContainer,
|
|
817
895
|
contentContainerStyle: styles.container,
|
|
818
896
|
showsVerticalScrollIndicator: false,
|
|
@@ -860,26 +938,19 @@ export function GameUIStorageBrowser({
|
|
|
860
938
|
eventCountByKey: eventCountByKey,
|
|
861
939
|
onViewHistory: onViewHistory,
|
|
862
940
|
onHideKey: key => handleHideKeys([key]),
|
|
863
|
-
onEditKey: setEditingKey,
|
|
864
941
|
onToggleKey: handleToggleKey,
|
|
865
942
|
onDeleteKey: handleDeleteKey,
|
|
866
943
|
editBlockedFor: editBlockedFor,
|
|
867
944
|
pinnedKeys: pinnedKeys,
|
|
868
|
-
onTogglePin: onTogglePin
|
|
945
|
+
onTogglePin: onTogglePin,
|
|
946
|
+
onRowRef: handleRowRef,
|
|
947
|
+
onOpenKey: onOpenKey
|
|
869
948
|
}), lockedBannerNode]
|
|
870
949
|
}) : emptyStateNode, /*#__PURE__*/_jsx(Text, {
|
|
871
950
|
style: styles.techFooter,
|
|
872
951
|
children: "ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"
|
|
873
952
|
}), upgradeModalNode]
|
|
874
|
-
})
|
|
875
|
-
style: styles.editorOverlay,
|
|
876
|
-
children: /*#__PURE__*/_jsx(StorageKeyEditorScreen, {
|
|
877
|
-
storageKey: editingKey,
|
|
878
|
-
onSave: raw => handleSaveValue(editingKey, raw),
|
|
879
|
-
onClose: () => setEditingKey(null),
|
|
880
|
-
applySafeAreaInset: applySafeAreaInset
|
|
881
|
-
})
|
|
882
|
-
}) : null]
|
|
953
|
+
})]
|
|
883
954
|
});
|
|
884
955
|
}
|
|
885
956
|
const styles = StyleSheet.create({
|
|
@@ -25,7 +25,10 @@ export function StorageBrowserMode({
|
|
|
25
25
|
onTogglePin,
|
|
26
26
|
applySafeAreaInset,
|
|
27
27
|
storageTypeFilter,
|
|
28
|
-
onStorageTypeFilterChange
|
|
28
|
+
onStorageTypeFilterChange,
|
|
29
|
+
detailKey,
|
|
30
|
+
onOpenKey,
|
|
31
|
+
onCloseDetail
|
|
29
32
|
}) {
|
|
30
33
|
return /*#__PURE__*/_jsx(GameUIStorageBrowser, {
|
|
31
34
|
requiredStorageKeys: requiredStorageKeys,
|
|
@@ -46,6 +49,9 @@ export function StorageBrowserMode({
|
|
|
46
49
|
onTogglePin: onTogglePin,
|
|
47
50
|
applySafeAreaInset: applySafeAreaInset,
|
|
48
51
|
storageTypeFilter: storageTypeFilter,
|
|
49
|
-
onStorageTypeFilterChange: onStorageTypeFilterChange
|
|
52
|
+
onStorageTypeFilterChange: onStorageTypeFilterChange,
|
|
53
|
+
detailKey: detailKey,
|
|
54
|
+
onOpenKey: onOpenKey,
|
|
55
|
+
onCloseDetail: onCloseDetail
|
|
50
56
|
});
|
|
51
57
|
}
|