react-native-debug-toolkit 0.3.0 → 0.4.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.
@@ -19,15 +19,15 @@ export const DebugColors = {
19
19
  export const getLogLevelColor = (level) => {
20
20
  switch (level?.toLowerCase()) {
21
21
  case 'log':
22
- return '#333';
22
+ return '#333'; // Dark gray for standard log
23
23
  case 'info':
24
- return '#0D96F2';
24
+ return '#0D96F2'; // Blue for info
25
25
  case 'warn':
26
- return '#FCA130';
26
+ return '#FCA130'; // Orange for warning
27
27
  case 'error':
28
- return '#F93E3E';
28
+ return '#F93E3E'; // Red for error
29
29
  default:
30
- return '#666666';
30
+ return '#666666'; // Default gray
31
31
  }
32
32
  };
33
33
  export const DebugImgs = {
@@ -47,3 +47,21 @@ export const getZustandActionColor = (action) => {
47
47
 
48
48
  return '#0D96F2'; // Default blue for other actions
49
49
  };
50
+
51
+ // Navigation action colors
52
+ export const getNavigationActionColor = (action) => {
53
+ switch (action?.toLowerCase()) {
54
+ case 'push':
55
+ return '#0D96F2'; // Blue for push
56
+ case 'pop':
57
+ return '#6E7072'; // Gray for pop
58
+ case 'replace':
59
+ return '#4CAF50'; // Green for replace
60
+ case 'reset':
61
+ return '#FCA130'; // Orange for reset
62
+ case 'goback':
63
+ return '#9C27B0'; // Purple for go back
64
+ default:
65
+ return '#0D96F2'; // Default blue for navigation
66
+ }
67
+ };
@@ -12,6 +12,7 @@ const STORAGE_KEYS = {
12
12
  NETWORK_LOGS: 'network_logs',
13
13
  PERFORMANCE_DATA: 'performance_data',
14
14
  ZUSTAND_LOGS: 'zustand_logs',
15
+ NAVIGATION_LOGS: 'navigation_logs',
15
16
  };
16
17
 
17
18
  /**
@@ -8,15 +8,19 @@ import {
8
8
  Dimensions,
9
9
  Pressable,
10
10
  SafeAreaView,
11
+ ScrollView,
12
+ TouchableOpacity,
13
+ Modal,
14
+ FlatList,
11
15
  } from 'react-native'
12
16
  // import AsyncStorage from '@react-native-async-storage/async-storage'
13
17
  import { IconRadius, DebugColors } from '../utils/DebugConst'
14
18
  import SubViewHTTPLogs from './SubViewHTTPLogs'
15
19
  import SubViewPerformance from './SubViewPerformance'
16
- import TabView from './TabView'
17
20
  import SubViewConsoleLogs from './SubViewConsoleLogs'
18
21
  import SubViewZustandLogs from './SubViewZustandLogs'
19
-
22
+ import SubViewNavigationLogs from './SubViewNavigationLogs'
23
+ import SubViewThirdPartyLibs from './SubViewThirdPartyLibs'
20
24
  const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
21
25
 
22
26
  export default class FloatPanelView extends Component {
@@ -38,8 +42,12 @@ export default class FloatPanelView extends Component {
38
42
  activeTab: 0,
39
43
  panelTranslateY: new Animated.Value(screenHeight),
40
44
  backdropOpacity: new Animated.Value(0),
45
+ isTabMenuVisible: false,
46
+ panelLayout: { width: 0, height: 0 },
41
47
  }
42
48
 
49
+ this.tabScrollViewRef = React.createRef()
50
+
43
51
  this.gestureResponder = PanResponder.create({
44
52
  onStartShouldSetPanResponder: () => true,
45
53
  onMoveShouldSetPanResponder: () => true,
@@ -197,6 +205,8 @@ export default class FloatPanelView extends Component {
197
205
  }
198
206
 
199
207
  _closePanel = () => {
208
+ this.setState({ isTabMenuVisible: false })
209
+
200
210
  Animated.parallel([
201
211
  Animated.spring(this.state.panelTranslateY, {
202
212
  toValue: screenHeight,
@@ -214,6 +224,26 @@ export default class FloatPanelView extends Component {
214
224
  })
215
225
  }
216
226
 
227
+ _toggleTabMenu = () => {
228
+ this.setState(prevState => ({ isTabMenuVisible: !prevState.isTabMenuVisible }))
229
+ }
230
+
231
+ _handleTabChange = (index) => {
232
+ this.setState({
233
+ activeTab: index,
234
+ isTabMenuVisible: false
235
+ }, () => {
236
+ // Scroll to make active tab visible
237
+ if (this.tabScrollViewRef.current) {
238
+ const tabWidth = 80; // Approximate tab width
239
+ this.tabScrollViewRef.current.scrollTo({
240
+ x: Math.max(0, index * tabWidth - 50), // Center the tab
241
+ animated: true
242
+ });
243
+ }
244
+ })
245
+ }
246
+
217
247
  renderFloatBtn() {
218
248
  const { isOpen, toFloat } = this.state
219
249
 
@@ -287,6 +317,14 @@ export default class FloatPanelView extends Component {
287
317
  return <SubViewZustandLogs logs={data} />
288
318
  }
289
319
 
320
+ if (feature.name === 'navigation') {
321
+ return <SubViewNavigationLogs logs={data} />
322
+ }
323
+
324
+ if (feature.name === 'thirdPartyLibs') {
325
+ return <SubViewThirdPartyLibs libraries={data} />
326
+ }
327
+
290
328
  // Generic fallback for other feature types
291
329
  return (
292
330
  <View style={styles.genericContent}>
@@ -309,9 +347,46 @@ export default class FloatPanelView extends Component {
309
347
  return this.renderFeatureContent(tabs[activeTab].id)
310
348
  }
311
349
 
350
+ renderTabBar() {
351
+ const tabs = this.getFeatureTabs()
352
+ const { activeTab } = this.state
353
+
354
+ if (!tabs.length) return null
355
+
356
+ return (
357
+ <View style={styles.tabBarContainer}>
358
+ <View style={styles.tabsRow}>
359
+ <ScrollView
360
+ ref={this.tabScrollViewRef}
361
+ horizontal
362
+ showsHorizontalScrollIndicator={false}
363
+ contentContainerStyle={styles.tabsScrollContainer}>
364
+ {tabs.map((tab, index) => {
365
+ const isActive = index === activeTab
366
+
367
+ return (
368
+ <TouchableOpacity
369
+ key={`tab-${tab.id}`}
370
+ style={[styles.tab, isActive && styles.activeTab]}
371
+ onPress={() => this._handleTabChange(index)}>
372
+ <Text
373
+ style={[styles.tabText, isActive && styles.activeTabText]}
374
+ numberOfLines={1}
375
+ ellipsizeMode="tail">
376
+ {tab.label}
377
+ </Text>
378
+ </TouchableOpacity>
379
+ )
380
+ })}
381
+ </ScrollView>
382
+
383
+ </View>
384
+ </View>
385
+ )
386
+ }
387
+
312
388
  renderPanel() {
313
389
  const { isOpen, panelTranslateY, backdropOpacity } = this.state
314
- const tabs = this.getFeatureTabs()
315
390
 
316
391
  if (!isOpen) {
317
392
  return null
@@ -329,7 +404,11 @@ export default class FloatPanelView extends Component {
329
404
  style={[
330
405
  styles.panel,
331
406
  { transform: [{ translateY: panelTranslateY }] },
332
- ]}>
407
+ ]}
408
+ onLayout={(e) => {
409
+ const { width, height } = e.nativeEvent.layout
410
+ this.setState({ panelLayout: { width, height } })
411
+ }}>
333
412
  <View {...this.panelResponder.panHandlers} style={styles.dragHandle}>
334
413
  <View style={styles.dragIndicator} />
335
414
  </View>
@@ -340,12 +419,12 @@ export default class FloatPanelView extends Component {
340
419
  <Text style={styles.closeButtonText}>×</Text>
341
420
  </Pressable>
342
421
  </View>
343
- <TabView
344
- tabs={tabs}
345
- activeTab={this.state.activeTab}
346
- onTabChange={(index) => this.setState({ activeTab: index })}>
422
+
423
+ {this.renderTabBar()}
424
+
425
+ <View style={styles.contentContainer}>
347
426
  {this.renderContent()}
348
- </TabView>
427
+ </View>
349
428
  </SafeAreaView>
350
429
  </Animated.View>
351
430
  </View>
@@ -445,6 +524,9 @@ const styles = StyleSheet.create({
445
524
  panelContent: {
446
525
  flex: 1,
447
526
  },
527
+ contentContainer: {
528
+ flex: 1,
529
+ },
448
530
  header: {
449
531
  flexDirection: 'row',
450
532
  justifyContent: 'space-between',
@@ -467,6 +549,41 @@ const styles = StyleSheet.create({
467
549
  color: DebugColors.text,
468
550
  fontWeight: 'bold',
469
551
  },
552
+ tabBarContainer: {
553
+ borderBottomWidth: 1,
554
+ borderBottomColor: DebugColors.border,
555
+ backgroundColor: DebugColors.background,
556
+ },
557
+ tabsRow: {
558
+ flexDirection: 'row',
559
+ alignItems: 'center',
560
+ },
561
+ tabsScrollContainer: {
562
+ paddingHorizontal: 15,
563
+ flexDirection: 'row',
564
+ alignItems: 'center',
565
+ },
566
+ tab: {
567
+ paddingVertical: 12,
568
+ paddingHorizontal: 8,
569
+ marginRight: 4,
570
+ alignItems: 'center',
571
+ justifyContent: 'center',
572
+ minWidth: 50,
573
+ },
574
+ activeTab: {
575
+ borderBottomWidth: 2,
576
+ borderBottomColor: DebugColors.blue,
577
+ },
578
+ tabText: {
579
+ fontSize: 13,
580
+ color: DebugColors.textLight,
581
+ textAlign: 'center',
582
+ },
583
+ activeTabText: {
584
+ color: DebugColors.blue,
585
+ fontWeight: '600',
586
+ },
470
587
  emptyText: {
471
588
  padding: 20,
472
589
  textAlign: 'center',
@@ -501,5 +618,5 @@ const styles = StyleSheet.create({
501
618
  fontFamily: 'monospace',
502
619
  fontSize: 12,
503
620
  color: DebugColors.text,
504
- },
621
+ }
505
622
  })
@@ -0,0 +1,294 @@
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
+ import { getNavigationActionColor } from '../utils/DebugConst'
6
+
7
+ // Re-using the theme from ConsoleLogDetails for consistency
8
+ const theme = {
9
+ scheme: 'monokai',
10
+ author: 'wimer hazenberg (http://www.monokai.nl)',
11
+ base00: '#272822',
12
+ base01: '#383830',
13
+ base02: '#49483e',
14
+ base03: '#75715e',
15
+ base04: '#a59f85',
16
+ base05: '#f8f8f2',
17
+ base06: '#f5f4f1',
18
+ base07: '#f9f8f5',
19
+ base08: '#f92672',
20
+ base09: '#fd971f',
21
+ base0A: '#f4bf75',
22
+ base0B: '#a6e22e',
23
+ base0C: '#a1efe4',
24
+ base0D: '#66d9ef',
25
+ base0E: '#ae81ff',
26
+ base0F: '#cc6633'
27
+ };
28
+
29
+ const CopyButton = ({ text, style }) => {
30
+ const [copied, setCopied] = React.useState(false)
31
+
32
+ const handleCopy = async () => {
33
+ await Clipboard.setString(text)
34
+ setCopied(true)
35
+ setTimeout(() => setCopied(false), 2000)
36
+ }
37
+
38
+ return (
39
+ <Pressable style={[styles.copyButton, style]} onPress={handleCopy}>
40
+ <Text style={styles.copyButtonText}>{copied ? 'Copied!' : 'Copy'}</Text>
41
+ </Pressable>
42
+ )
43
+ }
44
+
45
+ const CollapsibleSection = ({ title, children, initiallyExpanded = false }) => {
46
+ const [expanded, setExpanded] = React.useState(initiallyExpanded)
47
+
48
+ return (
49
+ <View style={styles.collapsibleSection}>
50
+ <Pressable
51
+ style={styles.sectionHeader}
52
+ onPress={() => setExpanded(!expanded)}>
53
+ <Text style={styles.sectionTitle}>{title}</Text>
54
+ <Text style={styles.expandIcon}>{expanded ? '▼' : '▶'}</Text>
55
+ </Pressable>
56
+ {expanded && children}
57
+ </View>
58
+ )
59
+ }
60
+
61
+ // Basic JSONValue renderer
62
+ const JSONValue = ({ value }) => {
63
+ if (value === null) {
64
+ return <Text style={styles.jsonNull}>null</Text>
65
+ }
66
+
67
+ if (value === undefined) {
68
+ return <Text style={styles.jsonNull}>undefined</Text>
69
+ }
70
+
71
+ if (typeof value === 'boolean') {
72
+ return <Text style={styles.jsonBoolean}>{value.toString()}</Text>
73
+ }
74
+
75
+ if (typeof value === 'number') {
76
+ return <Text style={styles.jsonNumber}>{value}</Text>
77
+ }
78
+
79
+ if (typeof value === 'string') {
80
+ return (
81
+ <Text style={styles.jsonString} selectable={true}>
82
+ "{value}"
83
+ </Text>
84
+ )
85
+ }
86
+
87
+ if (typeof value === 'object') {
88
+ return (
89
+ <JSONTree
90
+ data={value}
91
+ theme={theme}
92
+ invertTheme={true}
93
+ hideRoot={true}
94
+ shouldExpandNode={(keyPath, nodeData, currentLevel) => currentLevel < 1}
95
+ />
96
+ )
97
+ }
98
+
99
+ return <Text style={styles.jsonOther}>{String(value)}</Text>
100
+ }
101
+
102
+ const NavigationLogDetails = ({ log }) => {
103
+ if (!log) {
104
+ return (
105
+ <View style={styles.errorContainer}>
106
+ <Text style={styles.errorText}>Navigation log data is missing</Text>
107
+ </View>
108
+ )
109
+ }
110
+
111
+ const { action, from, to, timestamp, startTime, duration } = log
112
+ const actionColor = getNavigationActionColor(action)
113
+
114
+ // Format the log data for text display and copying
115
+ const formatLogForCopy = () => {
116
+ try {
117
+ return `Navigation: ${action}
118
+ From: ${JSON.stringify(from, null, 2)}
119
+ To: ${JSON.stringify(to, null, 2)}
120
+ Time: ${timestamp ? new Date(timestamp).toLocaleString() : 'Unknown'}
121
+ Duration: ${duration || 'N/A'} ms`;
122
+ } catch (e) {
123
+ return 'Failed to format navigation log';
124
+ }
125
+ };
126
+
127
+ return (
128
+ <ScrollView
129
+ style={styles.container}
130
+ contentContainerStyle={styles.contentContainer}
131
+ showsVerticalScrollIndicator={true}
132
+ scrollEventThrottle={16}
133
+ keyboardShouldPersistTaps='handled'>
134
+
135
+ <View style={styles.header}>
136
+ <View style={styles.headerInfo}>
137
+ <Text style={[styles.actionIndicator, { color: actionColor }]}>
138
+ {action?.toUpperCase() || 'UNKNOWN'}
139
+ </Text>
140
+ <Text style={styles.timestamp}>
141
+ {timestamp
142
+ ? new Date(timestamp).toLocaleString()
143
+ : 'Unknown time'}
144
+ </Text>
145
+ {duration && (
146
+ <Text style={styles.duration}>
147
+ {`${duration} ms`}
148
+ </Text>
149
+ )}
150
+ </View>
151
+ <CopyButton text={formatLogForCopy()} />
152
+ </View>
153
+
154
+ <CollapsibleSection title='From Route' initiallyExpanded={true}>
155
+ <View style={styles.dataContentWrapper}>
156
+ <View style={styles.dataContent}>
157
+ <JSONValue value={from} />
158
+ </View>
159
+ </View>
160
+ </CollapsibleSection>
161
+
162
+ <CollapsibleSection title='To Route' initiallyExpanded={true}>
163
+ <View style={styles.dataContentWrapper}>
164
+ <View style={styles.dataContent}>
165
+ <JSONValue value={to} />
166
+ </View>
167
+ </View>
168
+ </CollapsibleSection>
169
+
170
+ </ScrollView>
171
+ )
172
+ }
173
+
174
+ // Styles adapted from ConsoleLogDetails
175
+ const styles = StyleSheet.create({
176
+ container: {
177
+ flex: 1,
178
+ backgroundColor: '#fff',
179
+ },
180
+ contentContainer: {
181
+ paddingBottom: 20,
182
+ },
183
+ header: {
184
+ flexDirection: 'row',
185
+ padding: 15,
186
+ borderBottomWidth: 1,
187
+ borderBottomColor: '#eee',
188
+ alignItems: 'center',
189
+ justifyContent: 'space-between',
190
+ },
191
+ headerInfo: {
192
+ flexShrink: 1,
193
+ marginRight: 10,
194
+ },
195
+ actionIndicator: {
196
+ fontSize: 14,
197
+ fontWeight: 'bold',
198
+ marginBottom: 4,
199
+ },
200
+ timestamp: {
201
+ fontSize: 13,
202
+ color: '#666',
203
+ marginBottom: 2,
204
+ },
205
+ duration: {
206
+ fontSize: 12,
207
+ color: '#888',
208
+ },
209
+ collapsibleSection: {
210
+ marginBottom: 1,
211
+ backgroundColor: '#fff',
212
+ },
213
+ sectionHeader: {
214
+ flexDirection: 'row',
215
+ justifyContent: 'space-between',
216
+ alignItems: 'center',
217
+ padding: 15,
218
+ backgroundColor: '#f5f5f5',
219
+ },
220
+ sectionTitle: {
221
+ fontSize: 15,
222
+ fontWeight: 'bold',
223
+ color: '#333',
224
+ },
225
+ expandIcon: {
226
+ fontSize: 14,
227
+ color: '#666',
228
+ },
229
+ content: {
230
+ padding: 15,
231
+ },
232
+ dataContentWrapper: {
233
+ flex: 1,
234
+ padding: 10,
235
+ },
236
+ dataContent: {
237
+ backgroundColor: '#f8f9fa',
238
+ padding: 10,
239
+ borderRadius: 4,
240
+ borderWidth: 1,
241
+ borderColor: '#e9ecef',
242
+ },
243
+ errorContainer: {
244
+ flex: 1,
245
+ justifyContent: 'center',
246
+ alignItems: 'center',
247
+ padding: 20,
248
+ },
249
+ errorText: {
250
+ color: '#ff4444',
251
+ fontSize: 16,
252
+ },
253
+ copyButton: {
254
+ backgroundColor: '#e9ecef',
255
+ paddingHorizontal: 10,
256
+ paddingVertical: 5,
257
+ borderRadius: 4,
258
+ flexShrink: 0,
259
+ },
260
+ copyButtonText: {
261
+ fontSize: 12,
262
+ color: '#666',
263
+ },
264
+ // JSON Value Styles (reused from ConsoleLogDetails)
265
+ jsonString: {
266
+ color: '#CB772F',
267
+ fontFamily: 'monospace',
268
+ fontSize: 13,
269
+ },
270
+ jsonNumber: {
271
+ color: '#AE81FF',
272
+ fontFamily: 'monospace',
273
+ fontSize: 13,
274
+ },
275
+ jsonBoolean: {
276
+ color: '#66D9EF',
277
+ fontWeight: 'bold',
278
+ fontFamily: 'monospace',
279
+ fontSize: 13,
280
+ },
281
+ jsonNull: {
282
+ color: '#F92672',
283
+ fontStyle: 'italic',
284
+ fontFamily: 'monospace',
285
+ fontSize: 13,
286
+ },
287
+ jsonOther: {
288
+ color: '#75715e',
289
+ fontFamily: 'monospace',
290
+ fontSize: 13,
291
+ },
292
+ });
293
+
294
+ export default NavigationLogDetails