@buoy-gg/storage 7.0.29 → 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
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.StorageKeyDetailScreen = StorageKeyDetailScreen;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
+
var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
|
|
11
|
+
var _storageQueryUtils = require("../utils/storageQueryUtils");
|
|
12
|
+
var _valueType = require("../utils/valueType");
|
|
13
|
+
var _StorageKeyRow = require("./StorageKeyRow");
|
|
14
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
15
|
+
/**
|
|
16
|
+
* One storage key, on a screen of its own — the React Query detail-screen
|
|
17
|
+
* shape (and the Zustand tool's twin), pointed at a stored value.
|
|
18
|
+
*
|
|
19
|
+
* Tapping a key card opens this instead of expanding in place. Top to bottom:
|
|
20
|
+
*
|
|
21
|
+
* 1. VALUE EDITOR — the shared LiveExplorer. No draft, no Save: an edit
|
|
22
|
+
* re-serializes the whole value and writes it as you make it. Typing
|
|
23
|
+
* commits on a short debounce (~300ms, flushed on blur) so a word is one
|
|
24
|
+
* storage write, not one per keystroke — storage writes hit disk and
|
|
25
|
+
* land in the events timeline, which is the one way this tool differs
|
|
26
|
+
* from RQ's unlogged cache writes. A scalar key is just the root leaf's
|
|
27
|
+
* input; a boolean key is a one-tap toggle.
|
|
28
|
+
* 2. KEY DETAILS — what the expanded card used to say: status, backend,
|
|
29
|
+
* MMKV instance, updates (with the road to history), schema diagnostics.
|
|
30
|
+
* 3. RAW VALUE — the serialized string exactly as the backend holds it.
|
|
31
|
+
* 4. The card's housekeeping actions: pin, hide, delete.
|
|
32
|
+
*
|
|
33
|
+
* THE ROUND-TRIP WRINKLE zustand doesn't have: a storage write is async —
|
|
34
|
+
* write, device event, snapshot, refreshed prop. Between an edit and its
|
|
35
|
+
* echo, `storageKey.value` is STALE, so basing the next edit on the prop
|
|
36
|
+
* would silently revert the previous one (edit qty, then name, and qty snaps
|
|
37
|
+
* back). `lastWrittenRef` holds the most recent written document and is the
|
|
38
|
+
* base for every edit until the prop catches up to it — last-write-wins
|
|
39
|
+
* against the app, exactly once, and never against the editor itself.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** How long typed edits coalesce before the single write fires. */const TYPING_COMMIT_MS = 300;
|
|
43
|
+
const DETAIL_ID = "buoy-storage-detail";
|
|
44
|
+
function detailExplorerTestID(dataPath) {
|
|
45
|
+
const slug = part => part.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
46
|
+
return dataPath.length === 0 ? `${DETAIL_ID}-explorer-root` : `${DETAIL_ID}-explorer-${dataPath.map(slug).filter(Boolean).join("-")}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Same MMKV instance-color rule the card uses. */
|
|
50
|
+
const INSTANCE_COLORS = [_sharedUi.macOSColors.semantic.info, _sharedUi.macOSColors.semantic.success, _sharedUi.macOSColors.semantic.warning, _sharedUi.macOSColors.semantic.debug, "#FF6B9D", "#00D9FF"];
|
|
51
|
+
function getInstanceColor(instanceId) {
|
|
52
|
+
const hash = instanceId.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
53
|
+
return INSTANCE_COLORS[hash % INSTANCE_COLORS.length];
|
|
54
|
+
}
|
|
55
|
+
function StorageKeyDetailScreen({
|
|
56
|
+
storageKey,
|
|
57
|
+
editBlockedReason,
|
|
58
|
+
eventCount,
|
|
59
|
+
onSave,
|
|
60
|
+
onViewHistory,
|
|
61
|
+
onDeleteKey,
|
|
62
|
+
onHideKey,
|
|
63
|
+
isPinned = false,
|
|
64
|
+
onTogglePin,
|
|
65
|
+
applySafeAreaInset
|
|
66
|
+
}) {
|
|
67
|
+
const insets = (0, _sharedUi.useSafeAreaInsets)();
|
|
68
|
+
const bottomInset = applySafeAreaInset ? insets.bottom : 0;
|
|
69
|
+
const canEdit = editBlockedReason == null;
|
|
70
|
+
const config = (0, _StorageKeyRow.getStatusConfig)(storageKey.status);
|
|
71
|
+
const storageTypeLabel = (0, _storageQueryUtils.getStorageTypeLabel)(storageKey.storageType);
|
|
72
|
+
const [rawOpen, setRawOpen] = (0, _react.useState)(false);
|
|
73
|
+
// Force the explorer to re-sync focused inputs after a Raw-hatch apply.
|
|
74
|
+
const [rawVersion, setRawVersion] = (0, _react.useState)(0);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The most recent document THIS screen wrote, until the refreshed prop
|
|
78
|
+
* catches up to it — see the module docstring's round-trip wrinkle. Held
|
|
79
|
+
* as its serialized form so "caught up" is one string compare against the
|
|
80
|
+
* same serialization the write used.
|
|
81
|
+
*/
|
|
82
|
+
const lastWrittenRef = (0, _react.useRef)(null);
|
|
83
|
+
const propParsed = (0, _react.useMemo)(() => (0, _valueType.parseStorageValue)(storageKey.value), [storageKey.value]);
|
|
84
|
+
const propRaw = typeof storageKey.value === "string" ? storageKey.value : JSON.stringify(storageKey.value);
|
|
85
|
+
if (lastWrittenRef.current && lastWrittenRef.current.raw === propRaw) {
|
|
86
|
+
// The device echoed our write back — the prop is current again.
|
|
87
|
+
lastWrittenRef.current = null;
|
|
88
|
+
}
|
|
89
|
+
const baseValue = lastWrittenRef.current ? lastWrittenRef.current.value : propParsed;
|
|
90
|
+
const write = (0, _react.useCallback)(next => {
|
|
91
|
+
const raw = JSON.stringify(next);
|
|
92
|
+
lastWrittenRef.current = {
|
|
93
|
+
raw,
|
|
94
|
+
value: next
|
|
95
|
+
};
|
|
96
|
+
onSave(raw).catch(e => {
|
|
97
|
+
// The write was refused (the Pro gate, a dead backend) — the base
|
|
98
|
+
// must fall back to what's actually stored, or the tree keeps
|
|
99
|
+
// showing an edit that never landed.
|
|
100
|
+
lastWrittenRef.current = null;
|
|
101
|
+
_reactNative.Alert.alert("Couldn't save", e instanceof Error ? e.message : String(e));
|
|
102
|
+
});
|
|
103
|
+
}, [onSave]);
|
|
104
|
+
const baseRef = (0, _react.useRef)(baseValue);
|
|
105
|
+
baseRef.current = baseValue;
|
|
106
|
+
const writer = (0, _react.useMemo)(() => {
|
|
107
|
+
if (!canEdit) return undefined;
|
|
108
|
+
return {
|
|
109
|
+
update: (path, value) => {
|
|
110
|
+
const next = path.length === 0 ? value : (0, _dataViewer.updateNestedDataByPath)(baseRef.current, path, value);
|
|
111
|
+
write(next);
|
|
112
|
+
},
|
|
113
|
+
remove: path => {
|
|
114
|
+
if (path.length === 0) return;
|
|
115
|
+
write((0, _dataViewer.deleteNestedDataByPath)(baseRef.current, path));
|
|
116
|
+
}
|
|
117
|
+
// Storage replaces the whole key on every write, so any node may go.
|
|
118
|
+
};
|
|
119
|
+
}, [canEdit, write]);
|
|
120
|
+
const showHousekeeping = onTogglePin || onHideKey || canEdit && onDeleteKey;
|
|
121
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
122
|
+
style: styles.screen,
|
|
123
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
124
|
+
testID: `${DETAIL_ID}-key`,
|
|
125
|
+
accessibilityLabel: storageKey.key
|
|
126
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
127
|
+
testID: `${DETAIL_ID}-scroll`,
|
|
128
|
+
style: styles.scroll,
|
|
129
|
+
contentContainerStyle: [styles.scrollContent, {
|
|
130
|
+
paddingBottom: 24 + bottomInset
|
|
131
|
+
}],
|
|
132
|
+
keyboardShouldPersistTaps: "handled",
|
|
133
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
134
|
+
style: styles.section,
|
|
135
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
136
|
+
style: styles.editorContainer,
|
|
137
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
138
|
+
style: styles.sectionHeaderRow,
|
|
139
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
140
|
+
testID: `${DETAIL_ID}-editor-header`,
|
|
141
|
+
style: styles.sectionHeaderAccentText,
|
|
142
|
+
children: "Value Editor"
|
|
143
|
+
}), canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
144
|
+
onPress: () => setRawOpen(true),
|
|
145
|
+
testID: `${DETAIL_ID}-raw`,
|
|
146
|
+
accessibilityRole: "button",
|
|
147
|
+
accessibilityLabel: "Edit raw JSON",
|
|
148
|
+
style: styles.rawButton,
|
|
149
|
+
hitSlop: {
|
|
150
|
+
top: 6,
|
|
151
|
+
bottom: 6,
|
|
152
|
+
left: 6,
|
|
153
|
+
right: 6
|
|
154
|
+
},
|
|
155
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
|
|
156
|
+
size: 13,
|
|
157
|
+
color: _sharedUi.night.color.accent
|
|
158
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
159
|
+
style: styles.rawButtonText,
|
|
160
|
+
children: "Raw"
|
|
161
|
+
})]
|
|
162
|
+
}) : null]
|
|
163
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
164
|
+
style: styles.sectionBody,
|
|
165
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.LiveExplorer, {
|
|
166
|
+
editable: canEdit,
|
|
167
|
+
label: "Value",
|
|
168
|
+
value: baseValue,
|
|
169
|
+
defaultExpanded: ["Value"],
|
|
170
|
+
writer: writer,
|
|
171
|
+
dataVersion: rawVersion,
|
|
172
|
+
testIDForPath: detailExplorerTestID,
|
|
173
|
+
debounceMs: TYPING_COMMIT_MS
|
|
174
|
+
}), canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
175
|
+
style: styles.editorHint,
|
|
176
|
+
children: "Edits write to the device as you make them \u2014 undo lives in the key's history."
|
|
177
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
178
|
+
style: styles.blockedText,
|
|
179
|
+
testID: `${DETAIL_ID}-edit-blocked`,
|
|
180
|
+
children: editBlockedReason
|
|
181
|
+
})]
|
|
182
|
+
})]
|
|
183
|
+
})
|
|
184
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
185
|
+
style: styles.section,
|
|
186
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
187
|
+
style: styles.infoContainer,
|
|
188
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
189
|
+
style: styles.sectionHeaderPlain,
|
|
190
|
+
children: "Key Details"
|
|
191
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
192
|
+
style: styles.infoBody,
|
|
193
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
194
|
+
style: styles.infoRow,
|
|
195
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
196
|
+
style: styles.infoLabel,
|
|
197
|
+
children: "Status:"
|
|
198
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
|
|
199
|
+
color: config.color,
|
|
200
|
+
testID: `${DETAIL_ID}-status`,
|
|
201
|
+
children: config.label
|
|
202
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
203
|
+
style: styles.infoDim,
|
|
204
|
+
children: config.sublabel
|
|
205
|
+
})]
|
|
206
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
207
|
+
style: styles.infoRow,
|
|
208
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
209
|
+
style: styles.infoLabel,
|
|
210
|
+
children: "Storage:"
|
|
211
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
|
|
212
|
+
color: (0, _StorageKeyRow.getStorageTypeColor)(storageKey.storageType),
|
|
213
|
+
children: storageTypeLabel
|
|
214
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
215
|
+
style: styles.infoDim,
|
|
216
|
+
children: (0, _valueType.getValueTypeLabel)(storageKey.value)
|
|
217
|
+
})]
|
|
218
|
+
}), storageKey.storageType === "mmkv" && storageKey.instanceId && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
219
|
+
style: styles.infoRow,
|
|
220
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
221
|
+
style: styles.infoLabel,
|
|
222
|
+
children: "Instance:"
|
|
223
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
|
|
224
|
+
color: getInstanceColor(storageKey.instanceId),
|
|
225
|
+
icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.HardDrive, {
|
|
226
|
+
size: 9,
|
|
227
|
+
color: getInstanceColor(storageKey.instanceId)
|
|
228
|
+
}),
|
|
229
|
+
children: storageKey.instanceId
|
|
230
|
+
})]
|
|
231
|
+
}), eventCount != null && eventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
232
|
+
style: styles.infoRow,
|
|
233
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
234
|
+
style: styles.infoLabel,
|
|
235
|
+
children: "Updates:"
|
|
236
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
|
|
237
|
+
color: _sharedUi.macOSColors.semantic.warning,
|
|
238
|
+
children: String(eventCount)
|
|
239
|
+
}), eventCount > 1 && onViewHistory && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
240
|
+
onPress: onViewHistory,
|
|
241
|
+
testID: "buoy-storage-key-history",
|
|
242
|
+
accessibilityLabel: `View history for ${storageKey.key}`,
|
|
243
|
+
accessibilityRole: "button",
|
|
244
|
+
style: styles.inlineLinkButton,
|
|
245
|
+
hitSlop: {
|
|
246
|
+
top: 6,
|
|
247
|
+
bottom: 6,
|
|
248
|
+
left: 6,
|
|
249
|
+
right: 6
|
|
250
|
+
},
|
|
251
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
252
|
+
style: styles.inlineLinkText,
|
|
253
|
+
children: "view history \u2192"
|
|
254
|
+
})
|
|
255
|
+
})]
|
|
256
|
+
}), storageKey.status === "required_wrong_type" && storageKey.expectedType && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
257
|
+
style: styles.infoRow,
|
|
258
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
259
|
+
style: styles.infoLabel,
|
|
260
|
+
children: "Expected:"
|
|
261
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TypeBadge, {
|
|
262
|
+
type: storageKey.expectedType
|
|
263
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
264
|
+
style: styles.infoDim,
|
|
265
|
+
children: ["got ", (0, _valueType.getValueTypeLabel)(storageKey.value)]
|
|
266
|
+
})]
|
|
267
|
+
}), storageKey.status === "required_wrong_value" && storageKey.expectedValue && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
268
|
+
style: styles.infoRow,
|
|
269
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
270
|
+
style: styles.infoLabel,
|
|
271
|
+
children: "Expected:"
|
|
272
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
273
|
+
style: styles.infoExpected,
|
|
274
|
+
children: String(storageKey.expectedValue)
|
|
275
|
+
})]
|
|
276
|
+
}), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
277
|
+
style: styles.infoRow,
|
|
278
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
279
|
+
style: styles.infoLabel,
|
|
280
|
+
children: "Info:"
|
|
281
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
282
|
+
style: styles.infoDescription,
|
|
283
|
+
children: storageKey.description
|
|
284
|
+
})]
|
|
285
|
+
}) : null]
|
|
286
|
+
})]
|
|
287
|
+
})
|
|
288
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
289
|
+
style: styles.section,
|
|
290
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
291
|
+
style: styles.infoContainer,
|
|
292
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
293
|
+
style: styles.sectionHeaderPlain,
|
|
294
|
+
children: "Value Explorer"
|
|
295
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
296
|
+
style: styles.viewerBody,
|
|
297
|
+
testID: `${DETAIL_ID}-viewer`,
|
|
298
|
+
children: baseValue !== null && typeof baseValue === "object" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
|
|
299
|
+
title: "",
|
|
300
|
+
data: baseValue,
|
|
301
|
+
showTypeFilter: true,
|
|
302
|
+
rawMode: true,
|
|
303
|
+
initialExpanded: false
|
|
304
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
305
|
+
style: styles.rawValueText,
|
|
306
|
+
selectable: true,
|
|
307
|
+
children: propRaw ?? "undefined"
|
|
308
|
+
})
|
|
309
|
+
})]
|
|
310
|
+
})
|
|
311
|
+
}), showHousekeeping ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
312
|
+
style: styles.actionRow,
|
|
313
|
+
children: [onTogglePin ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
314
|
+
style: [styles.actionChip, isPinned && styles.actionChipActive],
|
|
315
|
+
testID: "buoy-storage-key-pin",
|
|
316
|
+
accessibilityLabel: isPinned ? `Unpin ${storageKey.key}` : `Pin ${storageKey.key} to top`,
|
|
317
|
+
accessibilityRole: "button",
|
|
318
|
+
onPress: () => onTogglePin(storageKey.key),
|
|
319
|
+
hitSlop: {
|
|
320
|
+
top: 6,
|
|
321
|
+
bottom: 6,
|
|
322
|
+
left: 6,
|
|
323
|
+
right: 6
|
|
324
|
+
},
|
|
325
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pin, {
|
|
326
|
+
size: 12,
|
|
327
|
+
active: isPinned,
|
|
328
|
+
color: _sharedUi.night.color.accent
|
|
329
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
330
|
+
style: [styles.actionChipText, {
|
|
331
|
+
color: _sharedUi.night.color.accent
|
|
332
|
+
}],
|
|
333
|
+
children: isPinned ? "Unpin" : "Pin to top"
|
|
334
|
+
})]
|
|
335
|
+
}) : null, onHideKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
336
|
+
style: styles.actionChip,
|
|
337
|
+
testID: "buoy-storage-key-hide",
|
|
338
|
+
accessibilityLabel: `Hide ${storageKey.key} from list`,
|
|
339
|
+
accessibilityRole: "button",
|
|
340
|
+
onPress: () => onHideKey(storageKey),
|
|
341
|
+
hitSlop: {
|
|
342
|
+
top: 6,
|
|
343
|
+
bottom: 6,
|
|
344
|
+
left: 6,
|
|
345
|
+
right: 6
|
|
346
|
+
},
|
|
347
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EyeOff, {
|
|
348
|
+
size: 12,
|
|
349
|
+
color: _sharedUi.night.color.warning
|
|
350
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
351
|
+
style: [styles.actionChipText, {
|
|
352
|
+
color: _sharedUi.night.color.warning
|
|
353
|
+
}],
|
|
354
|
+
children: "Hide from list"
|
|
355
|
+
})]
|
|
356
|
+
}) : null, canEdit && onDeleteKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
357
|
+
style: [styles.actionChip, styles.dangerChip],
|
|
358
|
+
testID: "buoy-storage-key-delete",
|
|
359
|
+
accessibilityLabel: `Delete key ${storageKey.key}`,
|
|
360
|
+
accessibilityRole: "button",
|
|
361
|
+
onPress: () => onDeleteKey(storageKey),
|
|
362
|
+
hitSlop: {
|
|
363
|
+
top: 6,
|
|
364
|
+
bottom: 6,
|
|
365
|
+
left: 6,
|
|
366
|
+
right: 6
|
|
367
|
+
},
|
|
368
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
|
|
369
|
+
size: 12,
|
|
370
|
+
color: _sharedUi.night.color.danger
|
|
371
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
372
|
+
style: [styles.actionChipText, {
|
|
373
|
+
color: _sharedUi.night.color.danger
|
|
374
|
+
}],
|
|
375
|
+
children: "Delete key"
|
|
376
|
+
})]
|
|
377
|
+
}) : null]
|
|
378
|
+
}) : null]
|
|
379
|
+
}), rawOpen && canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.ValueEditModal, {
|
|
380
|
+
path: [],
|
|
381
|
+
value: baseValue,
|
|
382
|
+
onSave: next => {
|
|
383
|
+
write(next);
|
|
384
|
+
setRawVersion(v => v + 1);
|
|
385
|
+
setRawOpen(false);
|
|
386
|
+
},
|
|
387
|
+
onCancel: () => setRawOpen(false),
|
|
388
|
+
saveLabel: "Apply",
|
|
389
|
+
testIDPrefix: "buoy-storage-value"
|
|
390
|
+
}) : null]
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
const styles = _reactNative.StyleSheet.create({
|
|
394
|
+
screen: {
|
|
395
|
+
flex: 1,
|
|
396
|
+
minHeight: 0
|
|
397
|
+
// Transparent — this is the modal's body, and the modal paints its own
|
|
398
|
+
// background (the same contract as the Zustand detail screen).
|
|
399
|
+
},
|
|
400
|
+
scroll: {
|
|
401
|
+
flex: 1
|
|
402
|
+
},
|
|
403
|
+
scrollContent: {
|
|
404
|
+
paddingHorizontal: 8,
|
|
405
|
+
paddingTop: 8
|
|
406
|
+
},
|
|
407
|
+
section: {
|
|
408
|
+
marginBottom: 16
|
|
409
|
+
},
|
|
410
|
+
editorContainer: {
|
|
411
|
+
minWidth: 200,
|
|
412
|
+
backgroundColor: _sharedUi.night.color.surface,
|
|
413
|
+
borderRadius: 6,
|
|
414
|
+
borderWidth: 1,
|
|
415
|
+
borderColor: _sharedUi.night.color.accent + "4D",
|
|
416
|
+
overflow: "hidden",
|
|
417
|
+
shadowColor: _sharedUi.night.color.accent,
|
|
418
|
+
shadowOffset: {
|
|
419
|
+
width: 0,
|
|
420
|
+
height: 0
|
|
421
|
+
},
|
|
422
|
+
shadowOpacity: 0.1,
|
|
423
|
+
shadowRadius: 6
|
|
424
|
+
},
|
|
425
|
+
sectionHeaderRow: {
|
|
426
|
+
flexDirection: "row",
|
|
427
|
+
alignItems: "center",
|
|
428
|
+
justifyContent: "space-between",
|
|
429
|
+
backgroundColor: _sharedUi.night.color.accent + "1A",
|
|
430
|
+
paddingHorizontal: 12,
|
|
431
|
+
paddingVertical: 8,
|
|
432
|
+
borderBottomWidth: 1,
|
|
433
|
+
borderBottomColor: _sharedUi.night.color.accent + "33"
|
|
434
|
+
},
|
|
435
|
+
sectionHeaderAccentText: {
|
|
436
|
+
fontWeight: "600",
|
|
437
|
+
fontSize: 12,
|
|
438
|
+
color: _sharedUi.night.color.accent,
|
|
439
|
+
letterSpacing: 0.5,
|
|
440
|
+
textTransform: "uppercase",
|
|
441
|
+
fontFamily: "monospace"
|
|
442
|
+
},
|
|
443
|
+
rawButton: {
|
|
444
|
+
flexDirection: "row",
|
|
445
|
+
alignItems: "center",
|
|
446
|
+
gap: 4
|
|
447
|
+
},
|
|
448
|
+
rawButtonText: {
|
|
449
|
+
color: _sharedUi.night.color.accent,
|
|
450
|
+
fontSize: 11,
|
|
451
|
+
fontWeight: "700"
|
|
452
|
+
},
|
|
453
|
+
sectionBody: {
|
|
454
|
+
padding: 8
|
|
455
|
+
},
|
|
456
|
+
editorHint: {
|
|
457
|
+
color: _sharedUi.night.color.textTertiary,
|
|
458
|
+
fontSize: 10,
|
|
459
|
+
paddingTop: 8,
|
|
460
|
+
paddingHorizontal: 4,
|
|
461
|
+
lineHeight: 14
|
|
462
|
+
},
|
|
463
|
+
blockedText: {
|
|
464
|
+
color: _sharedUi.night.color.textSecondary,
|
|
465
|
+
fontSize: 12,
|
|
466
|
+
padding: 12,
|
|
467
|
+
lineHeight: 18
|
|
468
|
+
},
|
|
469
|
+
infoContainer: {
|
|
470
|
+
minWidth: 200,
|
|
471
|
+
backgroundColor: _sharedUi.night.color.surface,
|
|
472
|
+
borderRadius: 6,
|
|
473
|
+
borderWidth: 1,
|
|
474
|
+
borderColor: _sharedUi.night.color.border,
|
|
475
|
+
overflow: "hidden"
|
|
476
|
+
},
|
|
477
|
+
sectionHeaderPlain: {
|
|
478
|
+
backgroundColor: _sharedUi.night.color.surfaceElevated,
|
|
479
|
+
paddingHorizontal: 12,
|
|
480
|
+
paddingVertical: 8,
|
|
481
|
+
fontWeight: "600",
|
|
482
|
+
fontSize: 12,
|
|
483
|
+
color: _sharedUi.night.color.textSecondary,
|
|
484
|
+
borderBottomWidth: 1,
|
|
485
|
+
borderBottomColor: _sharedUi.night.color.border,
|
|
486
|
+
letterSpacing: 0.5,
|
|
487
|
+
textTransform: "uppercase",
|
|
488
|
+
fontFamily: "monospace"
|
|
489
|
+
},
|
|
490
|
+
infoBody: {
|
|
491
|
+
padding: 12,
|
|
492
|
+
gap: 10
|
|
493
|
+
},
|
|
494
|
+
infoRow: {
|
|
495
|
+
flexDirection: "row",
|
|
496
|
+
alignItems: "center",
|
|
497
|
+
gap: 8
|
|
498
|
+
},
|
|
499
|
+
infoLabel: {
|
|
500
|
+
fontSize: 10,
|
|
501
|
+
color: _sharedUi.night.color.textTertiary,
|
|
502
|
+
fontWeight: "600",
|
|
503
|
+
minWidth: 64,
|
|
504
|
+
fontFamily: "monospace"
|
|
505
|
+
},
|
|
506
|
+
infoDim: {
|
|
507
|
+
color: _sharedUi.night.color.textTertiary,
|
|
508
|
+
fontSize: 10,
|
|
509
|
+
fontFamily: "monospace"
|
|
510
|
+
},
|
|
511
|
+
infoExpected: {
|
|
512
|
+
fontSize: 11,
|
|
513
|
+
color: _sharedUi.macOSColors.semantic.warning,
|
|
514
|
+
fontFamily: "monospace",
|
|
515
|
+
flex: 1
|
|
516
|
+
},
|
|
517
|
+
infoDescription: {
|
|
518
|
+
fontSize: 11,
|
|
519
|
+
color: _sharedUi.night.color.textSecondary,
|
|
520
|
+
flex: 1
|
|
521
|
+
},
|
|
522
|
+
rawValueText: {
|
|
523
|
+
color: _sharedUi.night.color.textSecondary,
|
|
524
|
+
fontSize: 11,
|
|
525
|
+
fontFamily: "monospace",
|
|
526
|
+
lineHeight: 16,
|
|
527
|
+
padding: 12
|
|
528
|
+
},
|
|
529
|
+
viewerBody: {
|
|
530
|
+
backgroundColor: _sharedUi.night.color.bg,
|
|
531
|
+
minHeight: 48
|
|
532
|
+
},
|
|
533
|
+
inlineLinkButton: {
|
|
534
|
+
marginLeft: 4
|
|
535
|
+
},
|
|
536
|
+
inlineLinkText: {
|
|
537
|
+
fontSize: 10,
|
|
538
|
+
fontWeight: "600",
|
|
539
|
+
fontFamily: "monospace",
|
|
540
|
+
color: _sharedUi.night.color.accent
|
|
541
|
+
},
|
|
542
|
+
actionRow: {
|
|
543
|
+
flexDirection: "row",
|
|
544
|
+
alignItems: "center",
|
|
545
|
+
flexWrap: "wrap",
|
|
546
|
+
gap: 8,
|
|
547
|
+
paddingHorizontal: 4
|
|
548
|
+
},
|
|
549
|
+
actionChip: {
|
|
550
|
+
flexDirection: "row",
|
|
551
|
+
alignItems: "center",
|
|
552
|
+
gap: 6,
|
|
553
|
+
paddingVertical: 6,
|
|
554
|
+
paddingHorizontal: 10,
|
|
555
|
+
borderRadius: 6,
|
|
556
|
+
backgroundColor: _sharedUi.night.color.surface,
|
|
557
|
+
borderWidth: 1,
|
|
558
|
+
borderColor: _sharedUi.night.color.border
|
|
559
|
+
},
|
|
560
|
+
dangerChip: {
|
|
561
|
+
borderColor: _sharedUi.night.color.danger + "55"
|
|
562
|
+
},
|
|
563
|
+
actionChipActive: {
|
|
564
|
+
backgroundColor: _sharedUi.night.color.accentSoft,
|
|
565
|
+
borderColor: _sharedUi.night.color.accentBorderStrong
|
|
566
|
+
},
|
|
567
|
+
actionChipText: {
|
|
568
|
+
fontSize: 11,
|
|
569
|
+
fontWeight: "600",
|
|
570
|
+
fontFamily: "monospace"
|
|
571
|
+
}
|
|
572
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.StorageKeyRow = void 0;
|
|
6
|
+
exports.getStorageTypeColor = exports.getStatusConfig = exports.StorageKeyRow = void 0;
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
@@ -33,6 +33,8 @@ function getInstanceColor(instanceId) {
|
|
|
33
33
|
const hash = instanceId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
34
34
|
return INSTANCE_COLORS[hash % INSTANCE_COLORS.length];
|
|
35
35
|
}
|
|
36
|
+
/** Exported for the key DETAIL screen, so card and detail can't disagree
|
|
37
|
+
* about what a status is called or what color it wears. */
|
|
36
38
|
const getStatusConfig = status => {
|
|
37
39
|
switch (status) {
|
|
38
40
|
case "required_present":
|
|
@@ -65,8 +67,17 @@ const getStatusConfig = status => {
|
|
|
65
67
|
color: _sharedUi.macOSColors.semantic.debug,
|
|
66
68
|
sublabel: "Optional"
|
|
67
69
|
};
|
|
70
|
+
// Runtime data can carry a status the union doesn't know (a stale
|
|
71
|
+
// snapshot, a newer adapter) — a devtool must render it, not redbox.
|
|
72
|
+
default:
|
|
73
|
+
return {
|
|
74
|
+
label: "Set",
|
|
75
|
+
color: _sharedUi.macOSColors.text.secondary,
|
|
76
|
+
sublabel: String(status)
|
|
77
|
+
};
|
|
68
78
|
}
|
|
69
79
|
};
|
|
80
|
+
exports.getStatusConfig = getStatusConfig;
|
|
70
81
|
const formatValue = value => {
|
|
71
82
|
if (value === undefined || value === null) {
|
|
72
83
|
return "undefined";
|
|
@@ -377,7 +388,7 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
|
|
|
377
388
|
testID: `buoy-storage-row-${storageKey.storageType}-${storageKey.key}`,
|
|
378
389
|
accessibilityLabel: `${storageKey.storageType} ${storageKey.key}`
|
|
379
390
|
}), eventCount != null && eventCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
380
|
-
style: [styles.absCountBadge, {
|
|
391
|
+
style: [styles.absCountBadge, isExpanded && styles.absCountBadgeExpanded, {
|
|
381
392
|
backgroundColor: _sharedUi.macOSColors.semantic.warning + "22",
|
|
382
393
|
borderColor: _sharedUi.macOSColors.semantic.warning + "55"
|
|
383
394
|
}],
|
|
@@ -392,6 +403,8 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
|
|
|
392
403
|
})]
|
|
393
404
|
});
|
|
394
405
|
});
|
|
406
|
+
|
|
407
|
+
/** Exported for the key DETAIL screen — same reason as getStatusConfig. */
|
|
395
408
|
const getStorageTypeColor = storageType => {
|
|
396
409
|
switch (storageType) {
|
|
397
410
|
case "mmkv":
|
|
@@ -404,6 +417,7 @@ const getStorageTypeColor = storageType => {
|
|
|
404
417
|
return _sharedUi.macOSColors.text.secondary;
|
|
405
418
|
}
|
|
406
419
|
};
|
|
420
|
+
exports.getStorageTypeColor = getStorageTypeColor;
|
|
407
421
|
const styles = _reactNative.StyleSheet.create({
|
|
408
422
|
badgeRow: {
|
|
409
423
|
flexDirection: "row",
|
|
@@ -478,6 +492,11 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
478
492
|
borderWidth: 1,
|
|
479
493
|
zIndex: 1
|
|
480
494
|
},
|
|
495
|
+
// The expanded CompactRow grows its wrapper margin from 3 to 12, so the
|
|
496
|
+
// badge has to drop with it or it floats above the card border.
|
|
497
|
+
absCountBadgeExpanded: {
|
|
498
|
+
top: 13
|
|
499
|
+
},
|
|
481
500
|
absCountText: {
|
|
482
501
|
fontSize: 9,
|
|
483
502
|
fontWeight: "700",
|