react-native-debug-toolkit 0.1.6 → 0.2.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.
@@ -0,0 +1,314 @@
1
+ import React from 'react'
2
+ import { View, Text, StyleSheet, Clipboard } from 'react-native'
3
+ import { ScrollView, Pressable } from 'react-native'
4
+ import JSONTree from 'react-native-json-tree'
5
+
6
+ // Re-using the theme from HttpLogDetails for consistency
7
+ const theme = {
8
+ scheme: 'monokai',
9
+ author: 'wimer hazenberg (http://www.monokai.nl)',
10
+ base00: '#272822',
11
+ base01: '#383830',
12
+ base02: '#49483e',
13
+ base03: '#75715e',
14
+ base04: '#a59f85',
15
+ base05: '#f8f8f2',
16
+ base06: '#f5f4f1',
17
+ base07: '#f9f8f5',
18
+ base08: '#f92672',
19
+ base09: '#fd971f',
20
+ base0A: '#f4bf75',
21
+ base0B: '#a6e22e',
22
+ base0C: '#a1efe4',
23
+ base0D: '#66d9ef',
24
+ base0E: '#ae81ff',
25
+ base0F: '#cc6633'
26
+ };
27
+
28
+ const CopyButton = ({ text, style }) => {
29
+ const [copied, setCopied] = React.useState(false)
30
+
31
+ const handleCopy = async () => {
32
+ await Clipboard.setString(text)
33
+ setCopied(true)
34
+ setTimeout(() => setCopied(false), 2000)
35
+ }
36
+
37
+ return (
38
+ <Pressable style={[styles.copyButton, style]} onPress={handleCopy}>
39
+ <Text style={styles.copyButtonText}>{copied ? 'Copied!' : 'Copy'}</Text>
40
+ </Pressable>
41
+ )
42
+ }
43
+
44
+ const CollapsibleSection = ({ title, children, initiallyExpanded = false }) => {
45
+ const [expanded, setExpanded] = React.useState(initiallyExpanded)
46
+
47
+ return (
48
+ <View style={styles.collapsibleSection}>
49
+ <Pressable
50
+ style={styles.sectionHeader}
51
+ onPress={() => setExpanded(!expanded)}>
52
+ <Text style={styles.sectionTitle}>{title}</Text>
53
+ <Text style={styles.expandIcon}>{expanded ? '▼' : '▶'}</Text>
54
+ </Pressable>
55
+ {expanded && children}
56
+ </View>
57
+ )
58
+ }
59
+
60
+ // Basic JSONValue renderer, might need adjustments for console log specifics
61
+ const JSONValue = ({ value }) => {
62
+ if (value === null) {
63
+ return <Text style={styles.jsonNull}>null</Text>
64
+ }
65
+
66
+ if (value === undefined) {
67
+ return <Text style={styles.jsonNull}>undefined</Text>
68
+ }
69
+
70
+ if (typeof value === 'boolean') {
71
+ return <Text style={styles.jsonBoolean}>{value.toString()}</Text>
72
+ }
73
+
74
+ if (typeof value === 'number') {
75
+ return <Text style={styles.jsonNumber}>{value}</Text>
76
+ }
77
+
78
+ if (typeof value === 'string') {
79
+ return (
80
+ <Text style={styles.jsonString} selectable={true}>
81
+ "{value}"
82
+ </Text>
83
+ )
84
+ }
85
+
86
+ if (typeof value === 'object') {
87
+ return (
88
+ <JSONTree
89
+ data={value}
90
+ theme={theme}
91
+ invertTheme={true}
92
+ hideRoot={true}
93
+ shouldExpandNode={(keyPath, nodeData, currentLevel) => currentLevel < 1}
94
+ />
95
+ )
96
+ }
97
+
98
+ return <Text style={styles.jsonOther}>{String(value)}</Text>
99
+ }
100
+
101
+
102
+ const ConsoleLogDetails = ({ log }) => {
103
+ if (!log) {
104
+ return (
105
+ <View style={styles.errorContainer}>
106
+ <Text style={styles.errorText}>Log data is missing</Text>
107
+ </View>
108
+ )
109
+ }
110
+
111
+ const logData = log.data || [] // Console logs can have multiple arguments
112
+ const timestamp = log.timestamp
113
+ const level = log.level || 'log' // Default to 'log' if level is not provided
114
+
115
+ // Simple function to format log data for display and copying
116
+ const formatLogData = (dataArray) => {
117
+ return dataArray.map(item => {
118
+ if (typeof item === 'object') {
119
+ try {
120
+ return JSON.stringify(item, null, 2);
121
+ } catch (e) {
122
+ return '[unserializable object]';
123
+ }
124
+ }
125
+ return String(item);
126
+ }).join(' ');
127
+ }
128
+
129
+ const formattedLog = formatLogData(logData);
130
+
131
+ return (
132
+ <ScrollView
133
+ style={styles.container}
134
+ contentContainerStyle={styles.contentContainer}
135
+ showsVerticalScrollIndicator={true}
136
+ scrollEventThrottle={16}
137
+ keyboardShouldPersistTaps='handled'>
138
+
139
+ <View style={styles.header}>
140
+ <View style={styles.headerInfo}>
141
+ <Text style={[styles.levelIndicator, { color: getLevelColor(level) }]}>
142
+ {level.toUpperCase()}
143
+ </Text>
144
+ <Text style={styles.timestamp}>
145
+ {timestamp
146
+ ? new Date(timestamp).toLocaleString()
147
+ : 'Unknown time'}
148
+ </Text>
149
+ </View>
150
+ <CopyButton text={formattedLog} />
151
+ </View>
152
+
153
+ <CollapsibleSection title='Log Data' initiallyExpanded={true}>
154
+ <View style={styles.dataContentWrapper}>
155
+ <View style={styles.dataContent}>
156
+ {logData.map((item, index) => (
157
+ <View key={index} style={[styles.logDataItem, index === logData.length - 1 && styles.logDataItemLast]}>
158
+ <JSONValue value={item} />
159
+ </View>
160
+ ))}
161
+ </View>
162
+ </View>
163
+ </CollapsibleSection>
164
+
165
+ </ScrollView>
166
+ )
167
+ }
168
+
169
+ const getLevelColor = (level) => {
170
+ switch (level?.toLowerCase()) {
171
+ case 'log':
172
+ return '#333'; // Dark gray for standard log
173
+ case 'info':
174
+ return '#0D96F2'; // Blue for info
175
+ case 'warn':
176
+ return '#FCA130'; // Orange for warning
177
+ case 'error':
178
+ return '#F93E3E'; // Red for error
179
+ default:
180
+ return '#666666'; // Default gray
181
+ }
182
+ };
183
+
184
+ const getLevelTextStyle = (level) => {
185
+ const baseStyle = { fontSize: 14, fontFamily: 'monospace' }; // Use monospace font
186
+ return { ...baseStyle, color: getLevelColor(level) };
187
+ }
188
+
189
+ // Re-using and adapting styles from HttpLogDetails
190
+ const styles = StyleSheet.create({
191
+ container: {
192
+ flex: 1,
193
+ backgroundColor: '#fff',
194
+ },
195
+ contentContainer: {
196
+ paddingBottom: 20,
197
+ },
198
+ header: {
199
+ flexDirection: 'row',
200
+ padding: 15,
201
+ borderBottomWidth: 1,
202
+ borderBottomColor: '#eee',
203
+ alignItems: 'center', // Align items vertically
204
+ justifyContent: 'space-between', // Space out items
205
+ },
206
+ headerInfo: {
207
+ flexDirection: 'row', // Arrange level and timestamp horizontally
208
+ alignItems: 'center',
209
+ flexShrink: 1, // Allow info to shrink if needed
210
+ marginRight: 10,
211
+ },
212
+ levelIndicator: {
213
+ fontSize: 14,
214
+ fontWeight: 'bold',
215
+ marginRight: 10,
216
+ },
217
+ timestamp: {
218
+ fontSize: 13,
219
+ color: '#666',
220
+ },
221
+ collapsibleSection: {
222
+ marginBottom: 1,
223
+ backgroundColor: '#fff',
224
+ },
225
+ sectionHeader: {
226
+ flexDirection: 'row',
227
+ justifyContent: 'space-between',
228
+ alignItems: 'center',
229
+ padding: 15,
230
+ backgroundColor: '#f5f5f5',
231
+ },
232
+ sectionTitle: {
233
+ fontSize: 15,
234
+ fontWeight: 'bold',
235
+ color: '#333',
236
+ },
237
+ expandIcon: {
238
+ fontSize: 14,
239
+ color: '#666',
240
+ },
241
+ content: {
242
+ padding: 15,
243
+ },
244
+ dataContentWrapper: {
245
+ flex: 1,
246
+ padding: 10, // Add padding around the scroll view
247
+ },
248
+ dataContent: {
249
+ backgroundColor: '#f8f9fa',
250
+ padding: 10,
251
+ borderRadius: 4,
252
+ borderWidth: 1,
253
+ borderColor: '#e9ecef',
254
+ },
255
+ logDataItem: {
256
+ borderBottomWidth: 1,
257
+ borderBottomColor: '#eee',
258
+ paddingVertical: 8,
259
+ },
260
+ logDataItemLast: {
261
+ borderBottomWidth: 0,
262
+ },
263
+ errorContainer: {
264
+ flex: 1,
265
+ justifyContent: 'center',
266
+ alignItems: 'center',
267
+ padding: 20,
268
+ },
269
+ errorText: {
270
+ color: '#ff4444',
271
+ fontSize: 16,
272
+ },
273
+ copyButton: {
274
+ backgroundColor: '#e9ecef',
275
+ paddingHorizontal: 10,
276
+ paddingVertical: 5,
277
+ borderRadius: 4,
278
+ flexShrink: 0, // Prevent button from shrinking
279
+ },
280
+ copyButtonText: {
281
+ fontSize: 12,
282
+ color: '#666',
283
+ },
284
+ // JSON Value Styles (simplified)
285
+ jsonString: {
286
+ color: '#CB772F',
287
+ fontFamily: 'monospace',
288
+ fontSize: 13,
289
+ },
290
+ jsonNumber: {
291
+ color: '#AE81FF',
292
+ fontFamily: 'monospace',
293
+ fontSize: 13,
294
+ },
295
+ jsonBoolean: {
296
+ color: '#66D9EF',
297
+ fontWeight: 'bold',
298
+ fontFamily: 'monospace',
299
+ fontSize: 13,
300
+ },
301
+ jsonNull: {
302
+ color: '#F92672',
303
+ fontStyle: 'italic',
304
+ fontFamily: 'monospace',
305
+ fontSize: 13,
306
+ },
307
+ jsonOther: {
308
+ color: '#75715e',
309
+ fontFamily: 'monospace',
310
+ fontSize: 13,
311
+ },
312
+ });
313
+
314
+ export default ConsoleLogDetails
@@ -12,7 +12,9 @@ import {
12
12
  } from 'react-native'
13
13
  import { IconRadius, DebugColors } from '../utils/DebugConst'
14
14
  import SubViewHTTPLogs from './SubViewHTTPLogs'
15
+ import SubViewPerformance from './SubViewPerformance'
15
16
  import TabView from './TabView'
17
+ import SubViewConsoleLogs from './SubViewConsoleLogs'
16
18
 
17
19
  const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
18
20
 
@@ -272,6 +274,15 @@ export default class FloatPanelView extends Component {
272
274
  if (feature.name === 'network') {
273
275
  return <SubViewHTTPLogs logs={data} />
274
276
  }
277
+
278
+ // Special handling for performance view
279
+ if (feature.name === 'performance') {
280
+ return <SubViewPerformance />
281
+ }
282
+
283
+ if (feature.name === 'console') {
284
+ return <SubViewConsoleLogs logs={data} />
285
+ }
275
286
 
276
287
  // Generic fallback for other feature types
277
288
  return (
@@ -74,8 +74,6 @@ const LongTextContent = ({ text }) => {
74
74
  }
75
75
 
76
76
  const JSONValue = ({ value, path = '', level = 0, maxExpandLevel = 2 }) => {
77
- const [expanded, setExpanded] = useState(level < maxExpandLevel)
78
-
79
77
  if (value === null) {
80
78
  return <Text style={styles.jsonNull}>null</Text>
81
79
  }
@@ -95,9 +93,7 @@ const JSONValue = ({ value, path = '', level = 0, maxExpandLevel = 2 }) => {
95
93
  if (typeof value === 'string') {
96
94
  if (value.length > 150) {
97
95
  return (
98
- <CollapsibleSection
99
- title={`String (${value.length} chars)`}
100
- initiallyExpanded={false}>
96
+ <CollapsibleSection title={`String (${value.length} chars)`} initiallyExpanded={false}>
101
97
  <LongTextContent text={value} />
102
98
  </CollapsibleSection>
103
99
  )
@@ -110,88 +106,16 @@ const JSONValue = ({ value, path = '', level = 0, maxExpandLevel = 2 }) => {
110
106
  )
111
107
  }
112
108
 
113
- if (Array.isArray(value)) {
114
- if (value.length === 0) {
115
- return <Text style={styles.jsonArray}>[]</Text>
116
- }
117
-
118
- return (
119
- <View style={styles.jsonContainer}>
120
- <Pressable
121
- onPress={() => setExpanded(!expanded)}
122
- style={styles.jsonToggle}>
123
- <Text style={styles.jsonBrackets}>
124
- [{!expanded && '...'}
125
- {expanded && value.length > 0 && ''}
126
- </Text>
127
- {!expanded && (
128
- <Text style={styles.jsonCollapsed}> Array({value.length}) </Text>
129
- )}
130
- </Pressable>
131
-
132
- {expanded && (
133
- <View style={styles.jsonChildren}>
134
- {value.map((item, index) => (
135
- <View key={`${path}-${index}`} style={styles.jsonArrayItem}>
136
- <Text style={styles.jsonKey}>{index}: </Text>
137
- <View style={styles.jsonValue}>
138
- <JSONValue
139
- value={item}
140
- path={`${path}[${index}]`}
141
- level={level + 1}
142
- />
143
- </View>
144
- </View>
145
- ))}
146
- </View>
147
- )}
148
-
149
- {expanded && <Text style={styles.jsonBrackets}>]</Text>}
150
- </View>
151
- )
152
- }
153
-
109
+ // For objects and arrays, use JSONTree for improved large data handling
154
110
  if (typeof value === 'object') {
155
- const keys = Object.keys(value)
156
-
157
- if (keys.length === 0) {
158
- return <Text style={styles.jsonObject}>{'{}'}</Text>
159
- }
160
-
161
111
  return (
162
- <View style={styles.jsonContainer}>
163
- <Pressable
164
- onPress={() => setExpanded(!expanded)}
165
- style={styles.jsonToggle}>
166
- <Text style={styles.jsonBrackets}>
167
- {'{'}
168
- {!expanded && '...'}
169
- {expanded && keys.length > 0 && ''}
170
- </Text>
171
- {!expanded && (
172
- <Text style={styles.jsonCollapsed}> Object({keys.length}) </Text>
173
- )}
174
- </Pressable>
175
-
176
- {expanded && (
177
- <View style={styles.jsonChildren}>
178
- {keys.map((key) => (
179
- <View key={`${path}-${key}`} style={styles.jsonProperty}>
180
- <Text style={styles.jsonKey}>{key}: </Text>
181
- <View style={styles.jsonValue}>
182
- <JSONValue
183
- value={value[key]}
184
- path={`${path}.${key}`}
185
- level={level + 1}
186
- />
187
- </View>
188
- </View>
189
- ))}
190
- </View>
191
- )}
192
-
193
- {expanded && <Text style={styles.jsonBrackets}>{'}'}</Text>}
194
- </View>
112
+ <JSONTree
113
+ data={value}
114
+ theme={theme}
115
+ invertTheme={true}
116
+ hideRoot={true}
117
+ shouldExpandNode={(keyPath, nodeData, currentLevel) => currentLevel < maxExpandLevel}
118
+ />
195
119
  )
196
120
  }
197
121
 
@@ -407,7 +331,7 @@ const HttpLogDetails = ({ log }) => {
407
331
  nestedScrollEnabled={true}
408
332
  bounces={false}
409
333
  showsVerticalScrollIndicator={true}>
410
- <JSONTree data={responseData} theme={theme} invertTheme={true} />
334
+ <JSONValue value={responseData} maxExpandLevel={2} />
411
335
  </ScrollView>
412
336
  </View>
413
337
  </View>
@@ -0,0 +1,209 @@
1
+ import React, { useState, useMemo } from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ FlatList,
7
+ TouchableOpacity,
8
+ } from 'react-native'
9
+ import ConsoleLogDetails from './ConsoleLogDetails' // Import the new details component
10
+ import { getLogLevelColor } from '../utils/DebugConst' // Assuming you move color logic
11
+
12
+ const SubViewConsoleLogs = ({ logs = [] }) => {
13
+ const [selectedLog, setSelectedLog] = useState(null)
14
+
15
+ // Memoize the sorted logs
16
+ const sortedLogs = useMemo(() => {
17
+ // Create a stable copy before sorting if FlatList relies on reference equality
18
+ return [...logs].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
19
+ }, [logs]);
20
+
21
+ // Helper to format log data for preview
22
+ const formatLogPreview = (dataArray = []) => {
23
+ if (!Array.isArray(dataArray)) return ''; // Handle cases where data might not be an array
24
+ return dataArray.map(item => {
25
+ if (typeof item === 'string') return item;
26
+ if (typeof item === 'number' || typeof item === 'boolean') return String(item);
27
+ if (item === null) return 'null';
28
+ if (item === undefined) return 'undefined';
29
+ if (typeof item === 'object' && item !== null) {
30
+ try {
31
+ // Attempt a brief JSON stringification, handle potential circular refs?
32
+ const preview = JSON.stringify(item);
33
+ return preview.length > 50 ? preview.substring(0, 47) + '...' : preview;
34
+ } catch (e) {
35
+ return '{...}'; // Fallback for complex/circular objects
36
+ }
37
+ }
38
+ return String(item); // Fallback
39
+ }).join(' ');
40
+ };
41
+
42
+
43
+ const renderLogItem = ({ item }) => {
44
+ const level = item.level || 'log';
45
+ const levelColor = getLogLevelColor(level);
46
+ const previewText = formatLogPreview(item.data);
47
+ const timestamp = item.timestamp
48
+ ? new Date(item.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })
49
+ : '';
50
+
51
+ return (
52
+ <TouchableOpacity
53
+ style={styles.logItem}
54
+ onPress={() => setSelectedLog(item)}>
55
+ <View style={styles.logItemContainer}>
56
+ <View
57
+ style={[styles.levelIndicator, { backgroundColor: levelColor }]}
58
+ />
59
+
60
+ <View style={styles.logContent}>
61
+ <View style={styles.logHeader}>
62
+ <Text style={[styles.levelText, { color: levelColor }]}>
63
+ {level.toUpperCase()}
64
+ </Text>
65
+ <Text style={styles.time}>{timestamp}</Text>
66
+ </View>
67
+
68
+ <Text style={styles.logMessage} numberOfLines={3}>
69
+ {previewText}
70
+ </Text>
71
+
72
+ </View>
73
+ </View>
74
+ </TouchableOpacity>
75
+ )
76
+ }
77
+
78
+ // If a log is selected, show the details view
79
+ if (selectedLog) {
80
+ const selectedLevel = selectedLog.level || 'log';
81
+ const timestamp = selectedLog.timestamp
82
+ ? new Date(selectedLog.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })
83
+ : '';
84
+ return (
85
+ <View style={styles.container}>
86
+ <View style={styles.detailsHeader}>
87
+ <TouchableOpacity
88
+ style={styles.backButton}
89
+ onPress={() => setSelectedLog(null)}>
90
+ <Text style={styles.backButtonText}>← Back</Text>
91
+ </TouchableOpacity>
92
+ <View style={styles.headerLevelTime}>
93
+ <Text style={[styles.headerLevel, { color: getLogLevelColor(selectedLevel) }]}>
94
+ {selectedLevel.toUpperCase()}
95
+ </Text>
96
+ <Text style={styles.headerTimestamp}>{timestamp}</Text>
97
+ </View>
98
+ </View>
99
+ <ConsoleLogDetails log={selectedLog} />
100
+ </View>
101
+ )
102
+ }
103
+
104
+ // Otherwise show the list view
105
+ return (
106
+ <View style={styles.container}>
107
+ {sortedLogs.length === 0 ? (
108
+ <Text style={styles.emptyText}>No console messages logged yet</Text>
109
+ ) : (
110
+ <FlatList
111
+ data={sortedLogs} // Use the memoized sorted list
112
+ renderItem={renderLogItem}
113
+ keyExtractor={(item, index) => `${item.timestamp}-${index}`} // Use timestamp and index for key
114
+ style={styles.list}
115
+ />
116
+ )}
117
+ </View>
118
+ )
119
+ }
120
+
121
+ // Adapted styles from SubViewHTTPLogs
122
+ const styles = StyleSheet.create({
123
+ container: {
124
+ flex: 1,
125
+ backgroundColor: '#fff',
126
+ },
127
+ list: {
128
+ flex: 1,
129
+ },
130
+ emptyText: {
131
+ textAlign: 'center',
132
+ color: '#999',
133
+ marginTop: 20,
134
+ },
135
+ logItem: {
136
+ borderBottomWidth: 1,
137
+ borderBottomColor: '#eee',
138
+ },
139
+ logItemContainer: {
140
+ flexDirection: 'row',
141
+ paddingVertical: 10,
142
+ paddingHorizontal: 12,
143
+ },
144
+ levelIndicator: {
145
+ width: 4,
146
+ borderRadius: 2,
147
+ marginRight: 10,
148
+ },
149
+ logContent: {
150
+ flex: 1,
151
+ },
152
+ logHeader: {
153
+ flexDirection: 'row',
154
+ justifyContent: 'space-between',
155
+ alignItems: 'center',
156
+ marginBottom: 5,
157
+ },
158
+ levelText: {
159
+ fontSize: 13,
160
+ fontWeight: 'bold',
161
+ },
162
+ time: {
163
+ fontSize: 12,
164
+ color: '#888',
165
+ },
166
+ logMessage: {
167
+ fontSize: 14,
168
+ color: '#333',
169
+ lineHeight: 18,
170
+ fontFamily: 'monospace', // Use monospace for log messages
171
+ },
172
+ // Details Header Styles
173
+ detailsHeader: {
174
+ flexDirection: 'row',
175
+ alignItems: 'center',
176
+ paddingVertical: 10,
177
+ paddingHorizontal: 15,
178
+ borderBottomWidth: 1,
179
+ borderBottomColor: '#eee',
180
+ backgroundColor: '#f8f9fa',
181
+ },
182
+ backButton: {
183
+ paddingHorizontal: 10,
184
+ paddingVertical: 5,
185
+ borderRadius: 4,
186
+ backgroundColor: '#eee',
187
+ marginRight: 15, // More space after back button
188
+ },
189
+ backButtonText: {
190
+ color: '#333',
191
+ fontWeight: '500',
192
+ fontSize: 14,
193
+ },
194
+ headerLevelTime: {
195
+ flexDirection: 'row',
196
+ alignItems: 'baseline', // Align baseline of text
197
+ },
198
+ headerLevel: {
199
+ fontSize: 15,
200
+ fontWeight: 'bold',
201
+ marginRight: 8,
202
+ },
203
+ headerTimestamp: {
204
+ fontSize: 13,
205
+ color: '#666',
206
+ },
207
+ })
208
+
209
+ export default SubViewConsoleLogs