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