@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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.asyncStorageCaps = exports.asyncStorage = void 0;
|
|
7
|
+
exports.readMany = readMany;
|
|
8
|
+
exports.removeMany = removeMany;
|
|
9
|
+
exports.writeMany = writeMany;
|
|
10
|
+
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
/**
|
|
13
|
+
* AsyncStorage compatibility adapter (v2 + v3)
|
|
14
|
+
*
|
|
15
|
+
* `@react-native-async-storage/async-storage` 3.x reshaped the default-export
|
|
16
|
+
* API. The single-item methods kept their v2 signatures, but the batch + merge
|
|
17
|
+
* methods changed:
|
|
18
|
+
*
|
|
19
|
+
* v2 (what this package was written against) v3
|
|
20
|
+
* ------------------------------------------ --------------------------------
|
|
21
|
+
* multiGet(keys): [key, value|null][] getMany(keys): Record<key, value|null>
|
|
22
|
+
* multiSet([[k, v]]): void setMany({ k: v }): void
|
|
23
|
+
* multiRemove(keys): void removeMany(keys): void
|
|
24
|
+
* mergeItem / multiMerge removed entirely
|
|
25
|
+
*
|
|
26
|
+
* This module is the single seam between the storage tool and whichever
|
|
27
|
+
* async-storage version the host app installed. The rest of the package always
|
|
28
|
+
* speaks the v2 tuple-shaped API; we translate to v3 here when needed.
|
|
29
|
+
*
|
|
30
|
+
* Reads (`readMany`) are routed through the *unswizzled* native methods, so the
|
|
31
|
+
* event listener can safely call them while capturing previous values without
|
|
32
|
+
* re-triggering itself.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// Loosely typed because the surface differs across major versions; callers go
|
|
36
|
+
// through the helpers below rather than touching this directly.
|
|
37
|
+
|
|
38
|
+
/** The live default-export object (methods may be swizzled by the listener). */
|
|
39
|
+
const asyncStorage = exports.asyncStorage = _asyncStorage.default;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Capability flags describing which API surface the installed version exposes.
|
|
43
|
+
* `multiGet` only exists on v2; `mergeItem` was removed in v3.
|
|
44
|
+
*/
|
|
45
|
+
const asyncStorageCaps = exports.asyncStorageCaps = {
|
|
46
|
+
/** v2 exposes multiGet/multiSet/multiRemove; v3 renamed these to *Many. */
|
|
47
|
+
hasLegacyMultiApi: typeof asyncStorage?.multiGet === "function",
|
|
48
|
+
/** v3 removed mergeItem/multiMerge with no replacement. */
|
|
49
|
+
hasMergeApi: typeof asyncStorage?.mergeItem === "function"
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Batch-read keys, normalized to the v2 `[key, value|null][]` tuple shape and
|
|
54
|
+
* preserving the requested key order regardless of installed version.
|
|
55
|
+
*/
|
|
56
|
+
async function readMany(keys) {
|
|
57
|
+
if (typeof asyncStorage.multiGet === "function") {
|
|
58
|
+
return await asyncStorage.multiGet(keys);
|
|
59
|
+
}
|
|
60
|
+
// v3: getMany returns a Record; rebuild ordered tuples ("what you request is
|
|
61
|
+
// what you get" — missing keys come back as null).
|
|
62
|
+
const record = await asyncStorage.getMany(keys);
|
|
63
|
+
return keys.map(key => [key, record[key] ?? null]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Batch-write key/value pairs. Calls the live method (which may be swizzled by
|
|
68
|
+
* the listener on v2's `multiSet` / v3's `setMany`) so writes still emit events.
|
|
69
|
+
*/
|
|
70
|
+
async function writeMany(pairs) {
|
|
71
|
+
if (typeof asyncStorage.multiSet === "function") {
|
|
72
|
+
return asyncStorage.multiSet(pairs);
|
|
73
|
+
}
|
|
74
|
+
const record = {};
|
|
75
|
+
for (const [key, value] of pairs) record[key] = value;
|
|
76
|
+
return asyncStorage.setMany(record);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Batch-remove keys via the live method (swizzled on v2's `multiRemove` /
|
|
81
|
+
* v3's `removeMany`).
|
|
82
|
+
*/
|
|
83
|
+
async function removeMany(keys) {
|
|
84
|
+
if (typeof asyncStorage.multiRemove === "function") {
|
|
85
|
+
return asyncStorage.multiRemove(keys);
|
|
86
|
+
}
|
|
87
|
+
return asyncStorage.removeMany(keys);
|
|
88
|
+
}
|
|
89
|
+
var _default = exports.default = asyncStorage;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.clearAllAppStorage = clearAllAppStorage;
|
|
7
7
|
exports.clearAllStorageIncludingDevTools = clearAllStorageIncludingDevTools;
|
|
8
8
|
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
9
|
+
var _asyncStorageCompat = require("./asyncStorageCompat");
|
|
9
10
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
/**
|
|
@@ -30,7 +31,7 @@ async function clearAllAppStorage() {
|
|
|
30
31
|
// Clearing ${keysToRemove.length} app storage keys
|
|
31
32
|
|
|
32
33
|
// Remove all non-dev-tool keys
|
|
33
|
-
await
|
|
34
|
+
await (0, _asyncStorageCompat.removeMany)(keysToRemove);
|
|
34
35
|
|
|
35
36
|
// Successfully cleared app storage
|
|
36
37
|
}
|
|
@@ -42,16 +42,25 @@ exports.isTypeMatch = isTypeMatch;
|
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
function detectMMKVType(instance, key) {
|
|
45
|
-
//
|
|
45
|
+
// MMKV stores no type metadata, so the native getters can cross-read: in
|
|
46
|
+
// particular `getString` returns "" (an empty string, NOT undefined) for many
|
|
47
|
+
// non-string values like numbers and `false`. Trying getString first and
|
|
48
|
+
// trusting its result therefore mis-detects those as empty strings — e.g.
|
|
49
|
+
// a key set to 42 or false would render as "". Probe the getters and only let
|
|
50
|
+
// getString win when it returns a NON-empty string; treat an empty result as
|
|
51
|
+
// ambiguous and prefer the number/boolean getters.
|
|
46
52
|
const stringValue = instance.getString(key);
|
|
47
|
-
|
|
53
|
+
|
|
54
|
+
// A non-empty string is unambiguously a string.
|
|
55
|
+
if (stringValue !== undefined && stringValue !== '') {
|
|
48
56
|
return {
|
|
49
57
|
value: stringValue,
|
|
50
58
|
type: 'string'
|
|
51
59
|
};
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
//
|
|
62
|
+
// Empty getString result is ambiguous (genuine "" vs a number/boolean the
|
|
63
|
+
// native layer decoded to ""). Prefer typed getters when they have a value.
|
|
55
64
|
const numberValue = instance.getNumber(key);
|
|
56
65
|
if (numberValue !== undefined) {
|
|
57
66
|
return {
|
|
@@ -59,8 +68,6 @@ function detectMMKVType(instance, key) {
|
|
|
59
68
|
type: 'number'
|
|
60
69
|
};
|
|
61
70
|
}
|
|
62
|
-
|
|
63
|
-
// Try boolean
|
|
64
71
|
const booleanValue = instance.getBoolean(key);
|
|
65
72
|
if (booleanValue !== undefined) {
|
|
66
73
|
return {
|
|
@@ -69,6 +76,14 @@ function detectMMKVType(instance, key) {
|
|
|
69
76
|
};
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
// getString returned "" and nothing else matched -> a genuine empty string.
|
|
80
|
+
if (stringValue === '') {
|
|
81
|
+
return {
|
|
82
|
+
value: '',
|
|
83
|
+
type: 'string'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
// Try buffer (least common)
|
|
73
88
|
const bufferValue = instance.getBuffer(key);
|
|
74
89
|
if (bufferValue !== undefined) {
|
|
@@ -7,6 +7,7 @@ exports.canUndo = canUndo;
|
|
|
7
7
|
exports.jumpToState = jumpToState;
|
|
8
8
|
exports.undoOperation = undoOperation;
|
|
9
9
|
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
10
|
+
var _asyncStorageCompat = require("./asyncStorageCompat");
|
|
10
11
|
var _AsyncStorageListener = require("./AsyncStorageListener");
|
|
11
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
13
|
/**
|
|
@@ -79,7 +80,7 @@ async function undoOperation(event) {
|
|
|
79
80
|
// Restore all cleared key-value pairs
|
|
80
81
|
const pairsToRestore = data.prevPairs.filter(([, value]) => value !== null);
|
|
81
82
|
if (pairsToRestore.length > 0) {
|
|
82
|
-
await
|
|
83
|
+
await (0, _asyncStorageCompat.writeMany)(pairsToRestore);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
break;
|
|
@@ -131,7 +132,7 @@ async function jumpToState(events, targetEventIndex) {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
if (pairsToSet.length > 0) {
|
|
134
|
-
await
|
|
135
|
+
await (0, _asyncStorageCompat.writeMany)(pairsToSet);
|
|
135
136
|
}
|
|
136
137
|
} finally {
|
|
137
138
|
// Always resume capture
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.getValuePreview = getValuePreview;
|
|
6
7
|
exports.getValueTypeLabel = getValueTypeLabel;
|
|
8
|
+
exports.getValueTypeWithPreview = getValueTypeWithPreview;
|
|
7
9
|
/** Return a concise string describing the JavaScript type of the provided value. */
|
|
8
10
|
function getValueTypeLabel(value) {
|
|
9
11
|
if (value === null) return "null";
|
|
@@ -15,4 +17,43 @@ function getValueTypeLabel(value) {
|
|
|
15
17
|
if (type === "number") return "number";
|
|
16
18
|
if (type === "string") return "string";
|
|
17
19
|
return "unknown";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Longest value preview rendered inline next to the type label. */
|
|
23
|
+
const MAX_INLINE_PREVIEW = 40;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Short, human-readable preview of a value for inline display next to the type
|
|
27
|
+
* label, or `null` when the value is too large/complex to preview (long
|
|
28
|
+
* strings, objects, arrays). Lets the user read simple values without opening
|
|
29
|
+
* the card.
|
|
30
|
+
*/
|
|
31
|
+
function getValuePreview(value) {
|
|
32
|
+
switch (typeof value) {
|
|
33
|
+
case "boolean":
|
|
34
|
+
return value ? "true" : "false";
|
|
35
|
+
case "number":
|
|
36
|
+
return Number.isFinite(value) ? String(value) : null;
|
|
37
|
+
case "string":
|
|
38
|
+
{
|
|
39
|
+
if (value.length === 0) return '""';
|
|
40
|
+
if (value.length > MAX_INLINE_PREVIEW) return null;
|
|
41
|
+
// Collapse whitespace so multi-line/tabbed values stay on one line.
|
|
42
|
+
return `"${value.replace(/\s+/g, " ")}"`;
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
return null;
|
|
46
|
+
// objects, arrays, null, undefined
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Type label optionally followed by a short value preview — e.g. `boolean · true`,
|
|
52
|
+
* `number · 42`, `string · "hello"`. Falls back to just the type label when the
|
|
53
|
+
* value is too large to preview.
|
|
54
|
+
*/
|
|
55
|
+
function getValueTypeWithPreview(value) {
|
|
56
|
+
const label = getValueTypeLabel(value);
|
|
57
|
+
const preview = getValuePreview(value);
|
|
58
|
+
return preview ? `${label} · ${preview}` : label;
|
|
18
59
|
}
|
package/lib/module/index.js
CHANGED
|
@@ -55,6 +55,11 @@ export { undoOperation, jumpToState, canUndo } from "./storage/utils/storageTime
|
|
|
55
55
|
// =============================================================================
|
|
56
56
|
export { registerMMKVInstance, unregisterMMKVInstance } from "./storage/utils/MMKVInstanceRegistry";
|
|
57
57
|
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
|
|
60
|
+
// =============================================================================
|
|
61
|
+
export { storageSyncAdapter } from "./storage/sync/storageSyncAdapter";
|
|
62
|
+
|
|
58
63
|
// =============================================================================
|
|
59
64
|
// INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
|
|
60
65
|
// =============================================================================
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useMemo, useCallback, useState, useEffect } from "react";
|
|
4
4
|
import { StyleSheet, Text, View, TouchableOpacity, ScrollView } from "react-native";
|
|
5
|
+
import { absoluteFill } from "@buoy-gg/shared-ui";
|
|
5
6
|
import { Database, Zap, ProBadge, ProUpgradeModal } from "@buoy-gg/shared-ui";
|
|
6
7
|
import { isDevToolsStorageKey } from "@buoy-gg/shared-ui";
|
|
7
8
|
import { StorageKeySection } from "./StorageKeySection";
|
|
@@ -61,7 +62,9 @@ export function GameUIStorageBrowser({
|
|
|
61
62
|
storageDataRef,
|
|
62
63
|
eventCountByKey,
|
|
63
64
|
onViewHistory,
|
|
64
|
-
enabledStorageTypes
|
|
65
|
+
enabledStorageTypes,
|
|
66
|
+
pinnedKeys,
|
|
67
|
+
onTogglePin
|
|
65
68
|
}) {
|
|
66
69
|
const isPro = useIsPro();
|
|
67
70
|
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
|
@@ -171,6 +174,15 @@ export function GameUIStorageBrowser({
|
|
|
171
174
|
refresh();
|
|
172
175
|
}, [refresh]);
|
|
173
176
|
|
|
177
|
+
// Hide the selected keys by adding each to the ignored-pattern filter store,
|
|
178
|
+
// then exit selection mode. Non-destructive — keys can be unhidden from the
|
|
179
|
+
// filters UI.
|
|
180
|
+
const handleHideKeys = useCallback(keys => {
|
|
181
|
+
keys.forEach(k => onAddPattern?.(k.key));
|
|
182
|
+
setSelectedKeyIds(new Set());
|
|
183
|
+
setIsSelectMode(false);
|
|
184
|
+
}, [onAddPattern]);
|
|
185
|
+
|
|
174
186
|
// Memoized export data for copy functionality
|
|
175
187
|
const copyExportData = useMemo(() => {
|
|
176
188
|
const allKeys = allStorageKeys;
|
|
@@ -393,8 +405,14 @@ export function GameUIStorageBrowser({
|
|
|
393
405
|
// NOTE: MMKV instance filtering is now handled in mmkvStorageKeys memo (line 128)
|
|
394
406
|
// No need to filter again here
|
|
395
407
|
|
|
408
|
+
// Float pinned keys to the top (stable — preserves existing order otherwise)
|
|
409
|
+
if (pinnedKeys && pinnedKeys.size > 0) {
|
|
410
|
+
const pinned = keys.filter(k => pinnedKeys.has(k.key));
|
|
411
|
+
const rest = keys.filter(k => !pinnedKeys.has(k.key));
|
|
412
|
+
keys = [...pinned, ...rest];
|
|
413
|
+
}
|
|
396
414
|
return keys;
|
|
397
|
-
}, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes]);
|
|
415
|
+
}, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes, pinnedKeys]);
|
|
398
416
|
|
|
399
417
|
// For free users, limit visible keys to FREE_TIER_KEY_LIMIT
|
|
400
418
|
const visibleKeys = useMemo(() => {
|
|
@@ -616,6 +634,7 @@ export function GameUIStorageBrowser({
|
|
|
616
634
|
instance: inst.instance
|
|
617
635
|
})),
|
|
618
636
|
onDeleteComplete: handleDeleteComplete,
|
|
637
|
+
onHideKeys: handleHideKeys,
|
|
619
638
|
onSelectAll: handleSelectAll,
|
|
620
639
|
onClearSelection: handleClearSelection,
|
|
621
640
|
totalVisibleKeys: visibleKeys.length
|
|
@@ -628,7 +647,10 @@ export function GameUIStorageBrowser({
|
|
|
628
647
|
selectedKeys: selectedKeyIds,
|
|
629
648
|
onSelectionChange: handleSelectionChange,
|
|
630
649
|
eventCountByKey: eventCountByKey,
|
|
631
|
-
onViewHistory: onViewHistory
|
|
650
|
+
onViewHistory: onViewHistory,
|
|
651
|
+
onHideKey: key => handleHideKeys([key]),
|
|
652
|
+
pinnedKeys: pinnedKeys,
|
|
653
|
+
onTogglePin: onTogglePin
|
|
632
654
|
}), hasLockedKeys && /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
633
655
|
style: styles.limitBanner,
|
|
634
656
|
onPress: () => setShowUpgradeModal(true),
|
|
@@ -682,7 +704,7 @@ const styles = StyleSheet.create({
|
|
|
682
704
|
paddingBottom: 32
|
|
683
705
|
},
|
|
684
706
|
backgroundGrid: {
|
|
685
|
-
...
|
|
707
|
+
...absoluteFill,
|
|
686
708
|
opacity: 0.006,
|
|
687
709
|
backgroundColor: gameUIColors.info
|
|
688
710
|
},
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef } from "react";
|
|
4
4
|
import { StyleSheet, View, Text, Animated } from "react-native";
|
|
5
|
+
import { absoluteFill } from "@buoy-gg/shared-ui";
|
|
5
6
|
import { Database, Shield, AlertCircle, CheckCircle2, XCircle, Eye, Zap, gameUIColors, macOSColors } from "@buoy-gg/shared-ui";
|
|
6
7
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
8
|
// Use macOS colors
|
|
@@ -513,7 +514,7 @@ const styles = StyleSheet.create({
|
|
|
513
514
|
shadowRadius: 8
|
|
514
515
|
},
|
|
515
516
|
healthGridOverlay: {
|
|
516
|
-
...
|
|
517
|
+
...absoluteFill,
|
|
517
518
|
opacity: 0.1,
|
|
518
519
|
borderWidth: 1,
|
|
519
520
|
borderColor: "rgba(255, 255, 255, 0.1)",
|
|
@@ -536,7 +537,7 @@ const styles = StyleSheet.create({
|
|
|
536
537
|
backgroundColor: "rgba(255, 0, 0, 0.02)"
|
|
537
538
|
},
|
|
538
539
|
cardGlow: {
|
|
539
|
-
...
|
|
540
|
+
...absoluteFill,
|
|
540
541
|
opacity: 0.3
|
|
541
542
|
},
|
|
542
543
|
cardHeader: {
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native";
|
|
5
|
-
import { Trash2, CopyButton, CheckSquare, ProUpgradeModal } from "@buoy-gg/shared-ui";
|
|
5
|
+
import { Trash2, CopyButton, CheckSquare, EyeOff, ProUpgradeModal } from "@buoy-gg/shared-ui";
|
|
6
6
|
import { macOSColors } from "@buoy-gg/shared-ui";
|
|
7
7
|
import { useIsPro } from "@buoy-gg/license";
|
|
8
|
-
import
|
|
8
|
+
import { removeMany } from "../utils/asyncStorageCompat";
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
10
|
export function SelectionActionBar({
|
|
11
11
|
selectedKeys,
|
|
12
12
|
mmkvInstances,
|
|
13
13
|
onDeleteComplete,
|
|
14
|
+
onHideKeys,
|
|
14
15
|
onSelectAll,
|
|
15
16
|
onClearSelection,
|
|
16
17
|
totalVisibleKeys
|
|
@@ -41,6 +42,12 @@ export function SelectionActionBar({
|
|
|
41
42
|
};
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
// Hide selected keys (adds them to the filter store; non-destructive)
|
|
46
|
+
const handleHideSelected = () => {
|
|
47
|
+
if (selectedCount === 0) return;
|
|
48
|
+
onHideKeys?.(selectedKeys);
|
|
49
|
+
};
|
|
50
|
+
|
|
44
51
|
// Handle delete selected keys
|
|
45
52
|
const handleDeleteSelected = () => {
|
|
46
53
|
if (selectedCount === 0) return;
|
|
@@ -64,7 +71,7 @@ export function SelectionActionBar({
|
|
|
64
71
|
|
|
65
72
|
// Delete AsyncStorage keys
|
|
66
73
|
if (asyncKeys.length > 0) {
|
|
67
|
-
await
|
|
74
|
+
await removeMany(asyncKeys.map(k => k.key));
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
// Delete MMKV keys
|
|
@@ -127,6 +134,13 @@ export function SelectionActionBar({
|
|
|
127
134
|
success: macOSColors.semantic.success,
|
|
128
135
|
error: macOSColors.semantic.error
|
|
129
136
|
}
|
|
137
|
+
}), onHideKeys && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
138
|
+
style: styles.actionButton,
|
|
139
|
+
onPress: handleHideSelected,
|
|
140
|
+
children: /*#__PURE__*/_jsx(EyeOff, {
|
|
141
|
+
size: 14,
|
|
142
|
+
color: macOSColors.semantic.warning
|
|
143
|
+
})
|
|
130
144
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
131
145
|
style: styles.actionButton,
|
|
132
146
|
onPress: handleDeleteSelected,
|
|
@@ -16,7 +16,9 @@ export function StorageBrowserMode({
|
|
|
16
16
|
storageDataRef,
|
|
17
17
|
eventCountByKey,
|
|
18
18
|
onViewHistory,
|
|
19
|
-
enabledStorageTypes
|
|
19
|
+
enabledStorageTypes,
|
|
20
|
+
pinnedKeys,
|
|
21
|
+
onTogglePin
|
|
20
22
|
}) {
|
|
21
23
|
return /*#__PURE__*/_jsx(GameUIStorageBrowser, {
|
|
22
24
|
requiredStorageKeys: requiredStorageKeys,
|
|
@@ -28,6 +30,8 @@ export function StorageBrowserMode({
|
|
|
28
30
|
storageDataRef: storageDataRef,
|
|
29
31
|
eventCountByKey: eventCountByKey,
|
|
30
32
|
onViewHistory: onViewHistory,
|
|
31
|
-
enabledStorageTypes: enabledStorageTypes
|
|
33
|
+
enabledStorageTypes: enabledStorageTypes,
|
|
34
|
+
pinnedKeys: pinnedKeys,
|
|
35
|
+
onTogglePin: onTogglePin
|
|
32
36
|
});
|
|
33
37
|
}
|
|
@@ -235,7 +235,7 @@ export function StorageEventDetailContent({
|
|
|
235
235
|
children: "CURRENT VALUE"
|
|
236
236
|
}), /*#__PURE__*/_jsxs(View, {
|
|
237
237
|
style: styles.valueHeaderBadges,
|
|
238
|
-
children: [action
|
|
238
|
+
children: [action ? /*#__PURE__*/_jsx(View, {
|
|
239
239
|
style: [styles.actionBadge, {
|
|
240
240
|
backgroundColor: `${actionColor}20`
|
|
241
241
|
}],
|
|
@@ -245,7 +245,7 @@ export function StorageEventDetailContent({
|
|
|
245
245
|
}],
|
|
246
246
|
children: translateStorageAction(action)
|
|
247
247
|
})
|
|
248
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
248
|
+
}) : null, /*#__PURE__*/_jsx(View, {
|
|
249
249
|
style: styles.typeBadge,
|
|
250
250
|
children: /*#__PURE__*/_jsx(Text, {
|
|
251
251
|
style: styles.typeText,
|
|
@@ -147,10 +147,10 @@ export function StorageKeyCard({
|
|
|
147
147
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
148
148
|
style: styles.storageKeyText,
|
|
149
149
|
children: storageKey.key
|
|
150
|
-
}), storageKey.description
|
|
150
|
+
}), storageKey.description ? /*#__PURE__*/_jsx(Text, {
|
|
151
151
|
style: styles.descriptionText,
|
|
152
152
|
children: storageKey.description
|
|
153
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
153
|
+
}) : null, /*#__PURE__*/_jsxs(View, {
|
|
154
154
|
style: styles.cardHeaderMeta,
|
|
155
155
|
children: [/*#__PURE__*/_jsx(View, {
|
|
156
156
|
style: [styles.statusBadge, {
|
|
@@ -279,13 +279,13 @@ export function StorageKeyCard({
|
|
|
279
279
|
style: styles.typeHelperText,
|
|
280
280
|
children: ["Current type: ", getValueTypeLabel(storageKey.value)]
|
|
281
281
|
})]
|
|
282
|
-
}), storageKey.lastUpdated
|
|
282
|
+
}), storageKey.lastUpdated ? /*#__PURE__*/_jsx(View, {
|
|
283
283
|
style: styles.metaInfo,
|
|
284
284
|
children: /*#__PURE__*/_jsxs(Text, {
|
|
285
285
|
style: styles.metaLabel,
|
|
286
|
-
children: ["Last
|
|
286
|
+
children: ["Last updated: ", storageKey.lastUpdated.toLocaleString()]
|
|
287
287
|
})
|
|
288
|
-
})]
|
|
288
|
+
}) : null]
|
|
289
289
|
}), isExpanded && !hasValue && /*#__PURE__*/_jsxs(View, {
|
|
290
290
|
style: styles.cardBody,
|
|
291
291
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
|
|
4
|
-
import { macOSColors, CompactRow, TypeBadge, HardDrive, Square, CheckSquare, ExpandedInfoRow, PillBadge } from "@buoy-gg/shared-ui";
|
|
4
|
+
import { macOSColors, CompactRow, TypeBadge, HardDrive, Square, CheckSquare, EyeOff, Pin, ExpandedInfoRow, PillBadge } from "@buoy-gg/shared-ui";
|
|
5
5
|
import { getStorageTypeLabel } from "../utils/storageQueryUtils";
|
|
6
|
-
import { getValueTypeLabel } from "../utils/valueType";
|
|
6
|
+
import { getValueTypeLabel, getValueTypeWithPreview } from "../utils/valueType";
|
|
7
7
|
import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
|
|
8
8
|
|
|
9
9
|
// MMKV Instance color palette - consistent colors per instance
|
|
@@ -78,11 +78,18 @@ export function StorageKeyRow({
|
|
|
78
78
|
isSelected = false,
|
|
79
79
|
onSelectionChange,
|
|
80
80
|
eventCount,
|
|
81
|
-
onViewHistory
|
|
81
|
+
onViewHistory,
|
|
82
|
+
onHideKey,
|
|
83
|
+
isPinned = false,
|
|
84
|
+
onTogglePin
|
|
82
85
|
}) {
|
|
83
86
|
const config = getStatusConfig(storageKey.status);
|
|
84
87
|
const hasValue = storageKey.value !== undefined && storageKey.value !== null;
|
|
85
88
|
|
|
89
|
+
// Booleans get a dedicated colored badge instead of an inline text preview so
|
|
90
|
+
// their value is scannable at a glance.
|
|
91
|
+
const isBoolean = hasValue && typeof storageKey.value === "boolean";
|
|
92
|
+
|
|
86
93
|
// Format primary text - show the key
|
|
87
94
|
const primaryText = storageKey.key;
|
|
88
95
|
|
|
@@ -186,7 +193,7 @@ export function StorageKeyRow({
|
|
|
186
193
|
style: styles.expandedExpected,
|
|
187
194
|
children: String(storageKey.expectedValue)
|
|
188
195
|
})]
|
|
189
|
-
}), storageKey.description
|
|
196
|
+
}), storageKey.description ? /*#__PURE__*/_jsxs(View, {
|
|
190
197
|
style: styles.expandedRow,
|
|
191
198
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
192
199
|
style: styles.expandedLabel,
|
|
@@ -195,7 +202,46 @@ export function StorageKeyRow({
|
|
|
195
202
|
style: styles.expandedDescription,
|
|
196
203
|
children: storageKey.description
|
|
197
204
|
})]
|
|
198
|
-
})
|
|
205
|
+
}) : null, onTogglePin || onHideKey ? /*#__PURE__*/_jsxs(View, {
|
|
206
|
+
style: styles.actionRow,
|
|
207
|
+
children: [onTogglePin ? /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
208
|
+
style: [styles.actionChip, isPinned && styles.actionChipActive],
|
|
209
|
+
onPress: () => onTogglePin(storageKey.key),
|
|
210
|
+
hitSlop: {
|
|
211
|
+
top: 6,
|
|
212
|
+
bottom: 6,
|
|
213
|
+
left: 6,
|
|
214
|
+
right: 6
|
|
215
|
+
},
|
|
216
|
+
children: [/*#__PURE__*/_jsx(Pin, {
|
|
217
|
+
size: 12,
|
|
218
|
+
color: macOSColors.semantic.info
|
|
219
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
220
|
+
style: [styles.actionChipText, {
|
|
221
|
+
color: macOSColors.semantic.info
|
|
222
|
+
}],
|
|
223
|
+
children: isPinned ? "Unpin" : "Pin to top"
|
|
224
|
+
})]
|
|
225
|
+
}) : null, onHideKey ? /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
226
|
+
style: styles.actionChip,
|
|
227
|
+
onPress: () => onHideKey(storageKey),
|
|
228
|
+
hitSlop: {
|
|
229
|
+
top: 6,
|
|
230
|
+
bottom: 6,
|
|
231
|
+
left: 6,
|
|
232
|
+
right: 6
|
|
233
|
+
},
|
|
234
|
+
children: [/*#__PURE__*/_jsx(EyeOff, {
|
|
235
|
+
size: 12,
|
|
236
|
+
color: macOSColors.semantic.warning
|
|
237
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
238
|
+
style: [styles.actionChipText, {
|
|
239
|
+
color: macOSColors.semantic.warning
|
|
240
|
+
}],
|
|
241
|
+
children: "Hide from list"
|
|
242
|
+
})]
|
|
243
|
+
}) : null]
|
|
244
|
+
}) : null]
|
|
199
245
|
});
|
|
200
246
|
|
|
201
247
|
// Handle checkbox press in select mode
|
|
@@ -230,17 +276,28 @@ export function StorageKeyRow({
|
|
|
230
276
|
statusLabel: config.label,
|
|
231
277
|
statusSublabel: config.sublabel,
|
|
232
278
|
primaryText: primaryText,
|
|
233
|
-
secondaryText: hasValue ? getValueTypeLabel(storageKey.value) : undefined,
|
|
279
|
+
secondaryText: hasValue ? isBoolean ? getValueTypeLabel(storageKey.value) : getValueTypeWithPreview(storageKey.value) : undefined,
|
|
280
|
+
secondaryAccessory: isBoolean ? /*#__PURE__*/_jsx(PillBadge, {
|
|
281
|
+
size: "sm",
|
|
282
|
+
color: storageKey.value ? macOSColors.semantic.success : macOSColors.semantic.error,
|
|
283
|
+
children: storageKey.value ? "true" : "false"
|
|
284
|
+
}) : undefined,
|
|
234
285
|
expandedContent: expandedContent,
|
|
235
286
|
isExpanded: isExpanded,
|
|
236
287
|
expandedGlowColor: config.color,
|
|
237
|
-
customBadge: /*#__PURE__*/
|
|
238
|
-
|
|
239
|
-
children:
|
|
288
|
+
customBadge: /*#__PURE__*/_jsxs(View, {
|
|
289
|
+
style: styles.badgeRow,
|
|
290
|
+
children: [isPinned && /*#__PURE__*/_jsx(Pin, {
|
|
291
|
+
size: 12,
|
|
292
|
+
color: macOSColors.semantic.info
|
|
293
|
+
}), /*#__PURE__*/_jsx(PillBadge, {
|
|
294
|
+
color: getStorageTypeColor(storageKey.storageType),
|
|
295
|
+
children: storageTypeLabel
|
|
296
|
+
})]
|
|
240
297
|
}),
|
|
241
298
|
showChevron: !isSelectMode,
|
|
242
299
|
onPress: isSelectMode ? handleCheckboxPress : onPress ? () => onPress(storageKey) : undefined
|
|
243
|
-
}), eventCount != null && eventCount >
|
|
300
|
+
}), eventCount != null && eventCount > 1 && /*#__PURE__*/_jsx(View, {
|
|
244
301
|
style: [styles.absCountBadge, {
|
|
245
302
|
backgroundColor: macOSColors.semantic.warning + "22",
|
|
246
303
|
borderColor: macOSColors.semantic.warning + "55"
|
|
@@ -269,6 +326,11 @@ const getStorageTypeColor = storageType => {
|
|
|
269
326
|
}
|
|
270
327
|
};
|
|
271
328
|
const styles = StyleSheet.create({
|
|
329
|
+
badgeRow: {
|
|
330
|
+
flexDirection: "row",
|
|
331
|
+
alignItems: "center",
|
|
332
|
+
gap: 6
|
|
333
|
+
},
|
|
272
334
|
expandedContainer: {
|
|
273
335
|
gap: 8
|
|
274
336
|
},
|
|
@@ -342,6 +404,33 @@ const styles = StyleSheet.create({
|
|
|
342
404
|
fontWeight: "700",
|
|
343
405
|
fontFamily: "monospace"
|
|
344
406
|
},
|
|
407
|
+
actionRow: {
|
|
408
|
+
flexDirection: "row",
|
|
409
|
+
alignItems: "center",
|
|
410
|
+
flexWrap: "wrap",
|
|
411
|
+
gap: 8,
|
|
412
|
+
marginTop: 4
|
|
413
|
+
},
|
|
414
|
+
actionChip: {
|
|
415
|
+
flexDirection: "row",
|
|
416
|
+
alignItems: "center",
|
|
417
|
+
gap: 6,
|
|
418
|
+
paddingVertical: 6,
|
|
419
|
+
paddingHorizontal: 10,
|
|
420
|
+
borderRadius: 6,
|
|
421
|
+
backgroundColor: macOSColors.background.card,
|
|
422
|
+
borderWidth: 1,
|
|
423
|
+
borderColor: macOSColors.border.default
|
|
424
|
+
},
|
|
425
|
+
actionChipActive: {
|
|
426
|
+
backgroundColor: macOSColors.semantic.info + "15",
|
|
427
|
+
borderColor: macOSColors.semantic.info + "44"
|
|
428
|
+
},
|
|
429
|
+
actionChipText: {
|
|
430
|
+
fontSize: 11,
|
|
431
|
+
fontWeight: "600",
|
|
432
|
+
fontFamily: "monospace"
|
|
433
|
+
},
|
|
345
434
|
viewHistoryButton: {
|
|
346
435
|
marginLeft: 4
|
|
347
436
|
},
|