rn-backstage 1.2.0 → 1.3.0
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 +84 -2
- package/lib/commonjs/Backstage.js +46 -33
- package/lib/commonjs/Backstage.js.map +1 -1
- package/lib/commonjs/ThemeContext.js +43 -0
- package/lib/commonjs/ThemeContext.js.map +1 -0
- package/lib/commonjs/components/BackstagePanel.js +35 -12
- package/lib/commonjs/components/BackstagePanel.js.map +1 -1
- package/lib/commonjs/components/FlagsTab.js +60 -57
- package/lib/commonjs/components/FlagsTab.js.map +1 -1
- package/lib/commonjs/components/FloatingPill.js +86 -24
- package/lib/commonjs/components/FloatingPill.js.map +1 -1
- package/lib/commonjs/components/InfoTab.js +77 -107
- package/lib/commonjs/components/InfoTab.js.map +1 -1
- package/lib/commonjs/components/JsonTreeView.js +39 -34
- package/lib/commonjs/components/JsonTreeView.js.map +1 -1
- package/lib/commonjs/components/LogItem.js +39 -29
- package/lib/commonjs/components/LogItem.js.map +1 -1
- package/lib/commonjs/components/LogsTab.js +39 -42
- package/lib/commonjs/components/LogsTab.js.map +1 -1
- package/lib/commonjs/components/NetworkItem.js +111 -135
- package/lib/commonjs/components/NetworkItem.js.map +1 -1
- package/lib/commonjs/components/NetworkTab.js +50 -49
- package/lib/commonjs/components/NetworkTab.js.map +1 -1
- package/lib/commonjs/components/StorageTab.js +655 -0
- package/lib/commonjs/components/StorageTab.js.map +1 -0
- package/lib/commonjs/components/TabBar.js +8 -5
- package/lib/commonjs/components/TabBar.js.map +1 -1
- package/lib/commonjs/constants.js +96 -7
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/index.js +96 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js +1 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/Backstage.js +46 -33
- package/lib/module/Backstage.js.map +1 -1
- package/lib/module/ThemeContext.js +37 -0
- package/lib/module/ThemeContext.js.map +1 -0
- package/lib/module/components/BackstagePanel.js +37 -14
- package/lib/module/components/BackstagePanel.js.map +1 -1
- package/lib/module/components/FlagsTab.js +61 -58
- package/lib/module/components/FlagsTab.js.map +1 -1
- package/lib/module/components/FloatingPill.js +90 -28
- package/lib/module/components/FloatingPill.js.map +1 -1
- package/lib/module/components/InfoTab.js +77 -107
- package/lib/module/components/InfoTab.js.map +1 -1
- package/lib/module/components/JsonTreeView.js +42 -38
- package/lib/module/components/JsonTreeView.js.map +1 -1
- package/lib/module/components/LogItem.js +41 -31
- package/lib/module/components/LogItem.js.map +1 -1
- package/lib/module/components/LogsTab.js +40 -43
- package/lib/module/components/LogsTab.js.map +1 -1
- package/lib/module/components/NetworkItem.js +113 -137
- package/lib/module/components/NetworkItem.js.map +1 -1
- package/lib/module/components/NetworkTab.js +51 -50
- package/lib/module/components/NetworkTab.js.map +1 -1
- package/lib/module/components/StorageTab.js +650 -0
- package/lib/module/components/StorageTab.js.map +1 -0
- package/lib/module/components/TabBar.js +10 -7
- package/lib/module/components/TabBar.js.map +1 -1
- package/lib/module/constants.js +95 -6
- package/lib/module/constants.js.map +1 -1
- package/lib/module/index.js +14 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/Backstage.d.ts.map +1 -1
- package/lib/typescript/ThemeContext.d.ts +11 -0
- package/lib/typescript/ThemeContext.d.ts.map +1 -0
- package/lib/typescript/components/BackstagePanel.d.ts +3 -1
- package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
- package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
- package/lib/typescript/components/FloatingPill.d.ts +2 -0
- package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
- package/lib/typescript/components/InfoTab.d.ts +1 -0
- package/lib/typescript/components/InfoTab.d.ts.map +1 -1
- package/lib/typescript/components/JsonTreeView.d.ts.map +1 -1
- package/lib/typescript/components/LogItem.d.ts +1 -0
- package/lib/typescript/components/LogItem.d.ts.map +1 -1
- package/lib/typescript/components/LogsTab.d.ts +1 -0
- package/lib/typescript/components/LogsTab.d.ts.map +1 -1
- package/lib/typescript/components/NetworkItem.d.ts +1 -0
- package/lib/typescript/components/NetworkItem.d.ts.map +1 -1
- package/lib/typescript/components/NetworkTab.d.ts +1 -0
- package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
- package/lib/typescript/components/StorageTab.d.ts +9 -0
- package/lib/typescript/components/StorageTab.d.ts.map +1 -0
- package/lib/typescript/components/TabBar.d.ts.map +1 -1
- package/lib/typescript/constants.d.ts +60 -4
- package/lib/typescript/constants.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +14 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +20 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/Backstage.tsx +45 -33
- package/src/ThemeContext.tsx +38 -0
- package/src/components/BackstagePanel.tsx +89 -61
- package/src/components/FlagsTab.tsx +125 -174
- package/src/components/FloatingPill.tsx +127 -30
- package/src/components/InfoTab.tsx +153 -186
- package/src/components/JsonTreeView.tsx +53 -73
- package/src/components/LogItem.tsx +64 -100
- package/src/components/LogsTab.tsx +110 -132
- package/src/components/NetworkItem.tsx +306 -417
- package/src/components/NetworkTab.tsx +129 -163
- package/src/components/StorageTab.tsx +607 -0
- package/src/components/TabBar.tsx +51 -47
- package/src/constants.ts +103 -4
- package/src/index.ts +17 -0
- package/src/types.ts +28 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
2
|
import { Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
|
3
|
-
import {
|
|
3
|
+
import { MonospaceFont, TestIDs } from '../constants'
|
|
4
|
+
import { useBackstageTheme } from '../ThemeContext'
|
|
4
5
|
import { JsonTreeView } from './JsonTreeView'
|
|
5
|
-
import type { AppInfoItem, BackstageStyleOverrides, QuickAction } from '../types'
|
|
6
|
+
import type { AppInfoItem, BackstageStyleOverrides, BackstageTheme, QuickAction } from '../types'
|
|
6
7
|
|
|
7
8
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
8
9
|
|
|
@@ -13,76 +14,12 @@ interface InfoTabProps {
|
|
|
13
14
|
deviceInfo?: AppInfoItem[]
|
|
14
15
|
state?: Record<string, unknown>
|
|
15
16
|
quickActions?: QuickAction[]
|
|
17
|
+
jsonMaxDepth?: number
|
|
16
18
|
onClosePanel?: () => void
|
|
17
19
|
styles?: BackstageStyleOverrides
|
|
18
20
|
children?: React.ReactNode
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
// ─── Info Row ────────────────────────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
const InfoRow: React.FC<{
|
|
24
|
-
label: string
|
|
25
|
-
value: string
|
|
26
|
-
labelStyle?: BackstageStyleOverrides['infoLabelStyle']
|
|
27
|
-
valueStyle?: BackstageStyleOverrides['infoValueStyle']
|
|
28
|
-
}> = ({ label, value, labelStyle, valueStyle }) => (
|
|
29
|
-
<View style={styles.infoRow}>
|
|
30
|
-
<Text style={[styles.infoLabel, labelStyle]} numberOfLines={1}>
|
|
31
|
-
{label}
|
|
32
|
-
</Text>
|
|
33
|
-
<Text style={[styles.infoValue, valueStyle]} numberOfLines={2} selectable>
|
|
34
|
-
{value}
|
|
35
|
-
</Text>
|
|
36
|
-
</View>
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
// ─── Section Header ──────────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
const SectionHeader: React.FC<{
|
|
42
|
-
title: string
|
|
43
|
-
titleStyle?: BackstageStyleOverrides['sectionTitleStyle']
|
|
44
|
-
}> = ({ title, titleStyle }) => <Text style={[styles.sectionTitle, titleStyle]}>{title}</Text>
|
|
45
|
-
|
|
46
|
-
// ─── Action Button ───────────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
const ActionButton: React.FC<{
|
|
49
|
-
action: QuickAction
|
|
50
|
-
onClosePanel?: () => void
|
|
51
|
-
buttonStyle?: BackstageStyleOverrides['actionButtonStyle']
|
|
52
|
-
buttonTitleStyle?: BackstageStyleOverrides['actionButtonTitleStyle']
|
|
53
|
-
}> = ({ action, onClosePanel, buttonStyle, buttonTitleStyle }) => {
|
|
54
|
-
const handlePress = () => {
|
|
55
|
-
action.onPress()
|
|
56
|
-
if (action.closeOnPress && onClosePanel) {
|
|
57
|
-
onClosePanel()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<TouchableOpacity
|
|
63
|
-
testID={action.testID}
|
|
64
|
-
style={[
|
|
65
|
-
styles.actionButton,
|
|
66
|
-
action.destructive && styles.actionButtonDestructive,
|
|
67
|
-
buttonStyle,
|
|
68
|
-
]}
|
|
69
|
-
onPress={handlePress}
|
|
70
|
-
activeOpacity={0.7}
|
|
71
|
-
>
|
|
72
|
-
{action.icon && <Text style={styles.actionIcon}>{action.icon}</Text>}
|
|
73
|
-
<Text
|
|
74
|
-
style={[
|
|
75
|
-
styles.actionButtonTitle,
|
|
76
|
-
action.destructive && styles.actionButtonTitleDestructive,
|
|
77
|
-
buttonTitleStyle,
|
|
78
|
-
]}
|
|
79
|
-
>
|
|
80
|
-
{action.title}
|
|
81
|
-
</Text>
|
|
82
|
-
</TouchableOpacity>
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
23
|
// ─── Main Component ──────────────────────────────────────────────────────────
|
|
87
24
|
|
|
88
25
|
export const InfoTab: React.FC<InfoTabProps> = ({
|
|
@@ -92,10 +29,14 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
92
29
|
deviceInfo = [],
|
|
93
30
|
state,
|
|
94
31
|
quickActions = [],
|
|
32
|
+
jsonMaxDepth,
|
|
95
33
|
onClosePanel,
|
|
96
34
|
styles: propStyles,
|
|
97
35
|
children,
|
|
98
36
|
}) => {
|
|
37
|
+
const theme = useBackstageTheme()
|
|
38
|
+
const s = useMemo(() => createStyles(theme), [theme])
|
|
39
|
+
|
|
99
40
|
// Built-in device info from Platform API
|
|
100
41
|
const builtInInfo: AppInfoItem[] = [
|
|
101
42
|
{ label: 'Platform', value: Platform.OS.toUpperCase() },
|
|
@@ -116,23 +57,26 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
116
57
|
|
|
117
58
|
return (
|
|
118
59
|
<ScrollView
|
|
119
|
-
|
|
120
|
-
|
|
60
|
+
testID={TestIDs.infoTab.container}
|
|
61
|
+
style={s.container}
|
|
62
|
+
contentContainerStyle={s.scrollContent}
|
|
121
63
|
showsVerticalScrollIndicator={false}
|
|
122
64
|
>
|
|
123
65
|
{/* ── Device Info Section ─────────────────────────────────── */}
|
|
124
|
-
<View style={
|
|
125
|
-
<
|
|
126
|
-
<View style={
|
|
66
|
+
<View testID={TestIDs.infoTab.deviceInfo} style={s.section}>
|
|
67
|
+
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>DEVICE INFO</Text>
|
|
68
|
+
<View style={s.card}>
|
|
127
69
|
{allInfo.map((item, index) => (
|
|
128
70
|
<React.Fragment key={`info_${index}`}>
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
71
|
+
<View style={s.infoRow}>
|
|
72
|
+
<Text style={[s.infoLabel, propStyles?.infoLabelStyle]} numberOfLines={1}>
|
|
73
|
+
{item.label}
|
|
74
|
+
</Text>
|
|
75
|
+
<Text style={[s.infoValue, propStyles?.infoValueStyle]} selectable>
|
|
76
|
+
{item.value}
|
|
77
|
+
</Text>
|
|
78
|
+
</View>
|
|
79
|
+
{index < allInfo.length - 1 && <View style={s.divider} />}
|
|
136
80
|
</React.Fragment>
|
|
137
81
|
))}
|
|
138
82
|
</View>
|
|
@@ -140,125 +84,148 @@ export const InfoTab: React.FC<InfoTabProps> = ({
|
|
|
140
84
|
|
|
141
85
|
{/* ── State Tree Section ──────────────────────────────────── */}
|
|
142
86
|
{state && Object.keys(state).length > 0 && (
|
|
143
|
-
<View style={
|
|
144
|
-
<
|
|
145
|
-
<View style={
|
|
146
|
-
<JsonTreeView data={state} hideRoot />
|
|
87
|
+
<View testID={TestIDs.infoTab.stateTree} style={s.section}>
|
|
88
|
+
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>STATE TREE</Text>
|
|
89
|
+
<View style={s.card}>
|
|
90
|
+
<JsonTreeView data={state} hideRoot maxDepth={jsonMaxDepth} />
|
|
147
91
|
</View>
|
|
148
92
|
</View>
|
|
149
93
|
)}
|
|
150
94
|
|
|
151
|
-
{/* ── Quick Actions Section
|
|
95
|
+
{/* ── Quick Actions Section ─────────────────────────────────── */}
|
|
152
96
|
{quickActions.length > 0 && (
|
|
153
|
-
<View style={
|
|
154
|
-
<
|
|
155
|
-
<View style={
|
|
156
|
-
{quickActions.map((action, index) =>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
action
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
97
|
+
<View testID={TestIDs.infoTab.quickActions} style={s.section}>
|
|
98
|
+
<Text style={[s.sectionTitle, propStyles?.sectionTitleStyle]}>QUICK ACTIONS</Text>
|
|
99
|
+
<View style={s.actionsGrid}>
|
|
100
|
+
{quickActions.map((action, index) => {
|
|
101
|
+
const handlePress = () => {
|
|
102
|
+
action.onPress()
|
|
103
|
+
if (action.closeOnPress && onClosePanel) {
|
|
104
|
+
onClosePanel()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return (
|
|
108
|
+
<TouchableOpacity
|
|
109
|
+
key={`action_${index}`}
|
|
110
|
+
testID={action.testID || TestIDs.infoTab.actionButton(index)}
|
|
111
|
+
style={[
|
|
112
|
+
s.actionButton,
|
|
113
|
+
action.destructive && s.actionButtonDestructive,
|
|
114
|
+
propStyles?.actionButtonStyle,
|
|
115
|
+
]}
|
|
116
|
+
onPress={handlePress}
|
|
117
|
+
activeOpacity={0.7}
|
|
118
|
+
>
|
|
119
|
+
{action.icon && <Text style={s.actionIcon}>{action.icon}</Text>}
|
|
120
|
+
<Text
|
|
121
|
+
style={[
|
|
122
|
+
s.actionButtonTitle,
|
|
123
|
+
action.destructive && s.actionButtonTitleDestructive,
|
|
124
|
+
propStyles?.actionButtonTitleStyle,
|
|
125
|
+
]}
|
|
126
|
+
>
|
|
127
|
+
{action.title}
|
|
128
|
+
</Text>
|
|
129
|
+
</TouchableOpacity>
|
|
130
|
+
)
|
|
131
|
+
})}
|
|
165
132
|
</View>
|
|
166
133
|
</View>
|
|
167
134
|
)}
|
|
168
135
|
|
|
169
136
|
{/* ── Custom Children ─────────────────────────────────────── */}
|
|
170
|
-
{children && <View style={
|
|
137
|
+
{children && <View style={s.section}>{children}</View>}
|
|
171
138
|
</ScrollView>
|
|
172
139
|
)
|
|
173
140
|
}
|
|
174
141
|
|
|
175
142
|
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
176
143
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
})
|
|
144
|
+
const createStyles = (t: BackstageTheme) =>
|
|
145
|
+
StyleSheet.create({
|
|
146
|
+
container: {
|
|
147
|
+
flex: 1,
|
|
148
|
+
},
|
|
149
|
+
scrollContent: {
|
|
150
|
+
padding: 16,
|
|
151
|
+
paddingBottom: 32,
|
|
152
|
+
},
|
|
153
|
+
section: {
|
|
154
|
+
marginBottom: 20,
|
|
155
|
+
},
|
|
156
|
+
sectionTitle: {
|
|
157
|
+
fontFamily: MonospaceFont,
|
|
158
|
+
fontSize: 11,
|
|
159
|
+
fontWeight: '700',
|
|
160
|
+
color: t.textMuted,
|
|
161
|
+
letterSpacing: 1.5,
|
|
162
|
+
marginBottom: 8,
|
|
163
|
+
paddingLeft: 4,
|
|
164
|
+
},
|
|
165
|
+
card: {
|
|
166
|
+
backgroundColor: t.surfaceElevated,
|
|
167
|
+
borderRadius: 12,
|
|
168
|
+
borderWidth: 1,
|
|
169
|
+
borderColor: t.border,
|
|
170
|
+
padding: 12,
|
|
171
|
+
overflow: 'hidden',
|
|
172
|
+
},
|
|
173
|
+
infoRow: {
|
|
174
|
+
flexDirection: 'row',
|
|
175
|
+
justifyContent: 'space-between',
|
|
176
|
+
alignItems: 'center',
|
|
177
|
+
paddingVertical: 8,
|
|
178
|
+
paddingHorizontal: 4,
|
|
179
|
+
},
|
|
180
|
+
infoLabel: {
|
|
181
|
+
fontFamily: MonospaceFont,
|
|
182
|
+
fontSize: 13,
|
|
183
|
+
color: t.textSecondary,
|
|
184
|
+
},
|
|
185
|
+
infoValue: {
|
|
186
|
+
fontFamily: MonospaceFont,
|
|
187
|
+
fontSize: 13,
|
|
188
|
+
color: t.text,
|
|
189
|
+
fontWeight: '600',
|
|
190
|
+
textAlign: 'right',
|
|
191
|
+
flex: 1,
|
|
192
|
+
marginLeft: 12,
|
|
193
|
+
},
|
|
194
|
+
divider: {
|
|
195
|
+
height: 1,
|
|
196
|
+
backgroundColor: t.border,
|
|
197
|
+
marginHorizontal: 4,
|
|
198
|
+
},
|
|
199
|
+
actionsGrid: {
|
|
200
|
+
flexDirection: 'row',
|
|
201
|
+
flexWrap: 'wrap',
|
|
202
|
+
gap: 10,
|
|
203
|
+
},
|
|
204
|
+
actionButton: {
|
|
205
|
+
backgroundColor: t.accentDim,
|
|
206
|
+
borderRadius: 10,
|
|
207
|
+
borderWidth: 1,
|
|
208
|
+
borderColor: t.accent,
|
|
209
|
+
paddingVertical: 10,
|
|
210
|
+
paddingHorizontal: 18,
|
|
211
|
+
flexDirection: 'row',
|
|
212
|
+
alignItems: 'center',
|
|
213
|
+
},
|
|
214
|
+
actionButtonDestructive: {
|
|
215
|
+
backgroundColor: t.errorDim,
|
|
216
|
+
borderColor: t.error,
|
|
217
|
+
},
|
|
218
|
+
actionIcon: {
|
|
219
|
+
fontSize: 14,
|
|
220
|
+
marginRight: 6,
|
|
221
|
+
},
|
|
222
|
+
actionButtonTitle: {
|
|
223
|
+
fontFamily: MonospaceFont,
|
|
224
|
+
fontSize: 13,
|
|
225
|
+
fontWeight: '600',
|
|
226
|
+
color: t.accent,
|
|
227
|
+
},
|
|
228
|
+
actionButtonTitleDestructive: {
|
|
229
|
+
color: t.error,
|
|
230
|
+
},
|
|
231
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useState } from 'react'
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
LayoutAnimation,
|
|
4
4
|
Platform,
|
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
UIManager,
|
|
9
9
|
View,
|
|
10
10
|
} from 'react-native'
|
|
11
|
-
import {
|
|
11
|
+
import { MonospaceFont } from '../constants'
|
|
12
|
+
import { useBackstageTheme } from '../ThemeContext'
|
|
13
|
+
import type { BackstageTheme } from '../types'
|
|
12
14
|
|
|
13
15
|
// Enable LayoutAnimation on Android
|
|
14
16
|
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
|
@@ -35,12 +37,12 @@ interface JsonNodeProps {
|
|
|
35
37
|
|
|
36
38
|
// ─── Color Mapping ───────────────────────────────────────────────────────────
|
|
37
39
|
|
|
38
|
-
function getValueColor(value: unknown): string {
|
|
39
|
-
if (value === null || value === undefined) return
|
|
40
|
-
if (typeof value === 'string') return '#98C379'
|
|
41
|
-
if (typeof value === 'number') return '#61AFEF'
|
|
42
|
-
if (typeof value === 'boolean') return '#C678DD'
|
|
43
|
-
return
|
|
40
|
+
function getValueColor(value: unknown, t: BackstageTheme): string {
|
|
41
|
+
if (value === null || value === undefined) return t.textMuted
|
|
42
|
+
if (typeof value === 'string') return '#98C379'
|
|
43
|
+
if (typeof value === 'number') return '#61AFEF'
|
|
44
|
+
if (typeof value === 'boolean') return '#C678DD'
|
|
45
|
+
return t.text
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
function getPreview(value: unknown): string {
|
|
@@ -79,7 +81,9 @@ const JsonNode: React.FC<JsonNodeProps> = ({
|
|
|
79
81
|
isLast,
|
|
80
82
|
hideKey = false,
|
|
81
83
|
}) => {
|
|
82
|
-
const
|
|
84
|
+
const theme = useBackstageTheme()
|
|
85
|
+
const s = useMemo(() => createStyles(theme), [theme])
|
|
86
|
+
const [expanded, setExpanded] = useState(false)
|
|
83
87
|
const expandable = isExpandable(value)
|
|
84
88
|
const reachedMaxDepth = depth >= maxDepth
|
|
85
89
|
|
|
@@ -95,18 +99,18 @@ const JsonNode: React.FC<JsonNodeProps> = ({
|
|
|
95
99
|
const displayValue = reachedMaxDepth && expandable ? getPreview(value) : formatValue(value)
|
|
96
100
|
|
|
97
101
|
return (
|
|
98
|
-
<View style={[
|
|
99
|
-
{!hideKey && keyName !== null && <Text style={
|
|
102
|
+
<View style={[s.row, { paddingLeft: indent }]}>
|
|
103
|
+
{!hideKey && keyName !== null && <Text style={s.key}>{`${keyName}: `}</Text>}
|
|
100
104
|
<Text
|
|
101
105
|
style={[
|
|
102
|
-
|
|
103
|
-
{ color: reachedMaxDepth ?
|
|
106
|
+
s.value,
|
|
107
|
+
{ color: reachedMaxDepth ? theme.textMuted : getValueColor(value, theme) },
|
|
104
108
|
]}
|
|
105
109
|
numberOfLines={3}
|
|
106
110
|
>
|
|
107
111
|
{displayValue}
|
|
108
112
|
</Text>
|
|
109
|
-
{!isLast && <Text style={
|
|
113
|
+
{!isLast && <Text style={s.comma}>,</Text>}
|
|
110
114
|
</View>
|
|
111
115
|
)
|
|
112
116
|
}
|
|
@@ -124,16 +128,16 @@ const JsonNode: React.FC<JsonNodeProps> = ({
|
|
|
124
128
|
<TouchableOpacity
|
|
125
129
|
onPress={toggleExpand}
|
|
126
130
|
activeOpacity={0.7}
|
|
127
|
-
style={[
|
|
131
|
+
style={[s.row, { paddingLeft: indent }]}
|
|
128
132
|
>
|
|
129
|
-
<Text style={
|
|
130
|
-
{!hideKey && keyName !== null && <Text style={
|
|
131
|
-
<Text style={
|
|
133
|
+
<Text style={s.chevron}>{expanded ? '▼' : '▶'}</Text>
|
|
134
|
+
{!hideKey && keyName !== null && <Text style={s.key}>{`${keyName}: `}</Text>}
|
|
135
|
+
<Text style={s.bracket}>{bracketOpen}</Text>
|
|
132
136
|
{!expanded && (
|
|
133
137
|
<>
|
|
134
|
-
<Text style={
|
|
135
|
-
<Text style={
|
|
136
|
-
{!isLast && <Text style={
|
|
138
|
+
<Text style={s.preview}>{` ${getPreview(value)} `}</Text>
|
|
139
|
+
<Text style={s.bracket}>{bracketClose}</Text>
|
|
140
|
+
{!isLast && <Text style={s.comma}>,</Text>}
|
|
137
141
|
</>
|
|
138
142
|
)}
|
|
139
143
|
</TouchableOpacity>
|
|
@@ -150,9 +154,9 @@ const JsonNode: React.FC<JsonNodeProps> = ({
|
|
|
150
154
|
isLast={i === entries.length - 1}
|
|
151
155
|
/>
|
|
152
156
|
))}
|
|
153
|
-
<View style={[
|
|
154
|
-
<Text style={
|
|
155
|
-
{!isLast && <Text style={
|
|
157
|
+
<View style={[s.row, { paddingLeft: indent }]}>
|
|
158
|
+
<Text style={s.bracket}>{bracketClose}</Text>
|
|
159
|
+
{!isLast && <Text style={s.comma}>,</Text>}
|
|
156
160
|
</View>
|
|
157
161
|
</>
|
|
158
162
|
)}
|
|
@@ -167,12 +171,15 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
|
|
|
167
171
|
hideRoot = false,
|
|
168
172
|
maxDepth = 10,
|
|
169
173
|
}) => {
|
|
174
|
+
const theme = useBackstageTheme()
|
|
175
|
+
const s = useMemo(() => createStyles(theme), [theme])
|
|
176
|
+
|
|
170
177
|
if (data === null || data === undefined) {
|
|
171
|
-
return <Text style={[
|
|
178
|
+
return <Text style={[s.value, { color: theme.textMuted }]}>{String(data)}</Text>
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
if (!isExpandable(data)) {
|
|
175
|
-
return <Text style={[
|
|
182
|
+
return <Text style={[s.value, { color: getValueColor(data, theme) }]}>{formatValue(data)}</Text>
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
if (hideRoot) {
|
|
@@ -181,7 +188,7 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
|
|
|
181
188
|
: Object.entries(data as Record<string, unknown>)
|
|
182
189
|
|
|
183
190
|
return (
|
|
184
|
-
<View style={
|
|
191
|
+
<View style={s.container}>
|
|
185
192
|
{entries.map(([k, v], i) => (
|
|
186
193
|
<JsonNode
|
|
187
194
|
key={`${k}_${i}`}
|
|
@@ -197,7 +204,7 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
|
|
|
197
204
|
}
|
|
198
205
|
|
|
199
206
|
return (
|
|
200
|
-
<View style={
|
|
207
|
+
<View style={s.container}>
|
|
201
208
|
<JsonNode
|
|
202
209
|
keyName={null}
|
|
203
210
|
value={data}
|
|
@@ -212,48 +219,21 @@ export const JsonTreeView: React.FC<JsonTreeViewProps> = ({
|
|
|
212
219
|
|
|
213
220
|
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
214
221
|
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
paddingVertical: 4,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
fontSize:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
key: {
|
|
234
|
-
fontFamily: MonospaceFont,
|
|
235
|
-
fontSize: 13,
|
|
236
|
-
color: '#E06C75', // red-ish for keys
|
|
237
|
-
},
|
|
238
|
-
value: {
|
|
239
|
-
fontFamily: MonospaceFont,
|
|
240
|
-
fontSize: 13,
|
|
241
|
-
flexShrink: 1,
|
|
242
|
-
},
|
|
243
|
-
bracket: {
|
|
244
|
-
fontFamily: MonospaceFont,
|
|
245
|
-
fontSize: 13,
|
|
246
|
-
color: DarkTheme.textSecondary,
|
|
247
|
-
},
|
|
248
|
-
preview: {
|
|
249
|
-
fontFamily: MonospaceFont,
|
|
250
|
-
fontSize: 13,
|
|
251
|
-
color: DarkTheme.textMuted,
|
|
252
|
-
fontStyle: 'italic',
|
|
253
|
-
},
|
|
254
|
-
comma: {
|
|
255
|
-
fontFamily: MonospaceFont,
|
|
256
|
-
fontSize: 13,
|
|
257
|
-
color: DarkTheme.textMuted,
|
|
258
|
-
},
|
|
259
|
-
})
|
|
222
|
+
const createStyles = (t: BackstageTheme) =>
|
|
223
|
+
StyleSheet.create({
|
|
224
|
+
container: { paddingVertical: 4 },
|
|
225
|
+
row: { flexDirection: 'row', alignItems: 'flex-start', paddingVertical: 2, flexWrap: 'wrap' },
|
|
226
|
+
chevron: {
|
|
227
|
+
fontFamily: MonospaceFont,
|
|
228
|
+
fontSize: 10,
|
|
229
|
+
color: t.textMuted,
|
|
230
|
+
marginRight: 6,
|
|
231
|
+
marginTop: 2,
|
|
232
|
+
width: 12,
|
|
233
|
+
},
|
|
234
|
+
key: { fontFamily: MonospaceFont, fontSize: 13, color: '#E06C75' },
|
|
235
|
+
value: { fontFamily: MonospaceFont, fontSize: 13, flexShrink: 1 },
|
|
236
|
+
bracket: { fontFamily: MonospaceFont, fontSize: 13, color: t.textSecondary },
|
|
237
|
+
preview: { fontFamily: MonospaceFont, fontSize: 13, color: t.textMuted, fontStyle: 'italic' },
|
|
238
|
+
comma: { fontFamily: MonospaceFont, fontSize: 13, color: t.textMuted },
|
|
239
|
+
})
|