react-native-debug-toolkit 0.2.2 → 0.3.1

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/index.js CHANGED
@@ -8,6 +8,9 @@ import { initializeDebugToolkit } from './lib'
8
8
  import { createNetworkFeature } from './lib/features/NetworkFeature'
9
9
  import { createPerformanceFeature } from './lib/features/PerformanceFeature'
10
10
  import { createConsoleLogFeature } from './lib/features/ConsoleLogFeature'
11
+ import { createZustandLogFeature, addZustandLog } from './lib/features/ZustandLogFeature'
12
+ import { createNavigationLogFeature, addNavigationLog } from './lib/features/NavigationLogFeature'
13
+ import { useNavigationLogger } from './lib/hooks/useNavigationLogger'
11
14
 
12
15
  export {
13
16
  DebugToolKit,
@@ -15,6 +18,11 @@ export {
15
18
  createNetworkFeature,
16
19
  createPerformanceFeature,
17
20
  createConsoleLogFeature,
21
+ createZustandLogFeature,
22
+ addZustandLog,
23
+ createNavigationLogFeature,
24
+ addNavigationLog,
25
+ useNavigationLogger
18
26
  }
19
27
 
20
28
  export default DebugToolKit
@@ -1,4 +1,4 @@
1
- const MAX_LOGS = 100; // Max number of console logs to store
1
+ const MAX_LOGS = 200; // Max number of console logs to store
2
2
  const logs = [];
3
3
  const originalConsole = {}; // Store original console methods
4
4
 
@@ -0,0 +1,47 @@
1
+ const MAX_LOGS = 200; // Max number of navigation logs to store
2
+ const logs = [];
3
+ const originalNavigationMethods = {}; // Store original navigation methods
4
+
5
+ const setup = () => {
6
+ if (!__DEV__) {
7
+ return;
8
+ }
9
+ // Note: Actual navigation interception will be done in the main app code
10
+ // This is just a setup function for compatibility with the feature API
11
+ };
12
+
13
+ // Function to add navigation log
14
+ export const addNavigationLog = (action, from, to, startTime, duration) => {
15
+ // Store log data
16
+ logs.push({
17
+ timestamp: new Date(),
18
+ action: action, // push, pop, replace, etc.
19
+ from: from, // source route info
20
+ to: to, // destination route info
21
+ startTime: startTime,
22
+ duration: duration,
23
+ });
24
+
25
+ // Trim logs if they exceed the maximum limit
26
+ if (logs.length > MAX_LOGS) {
27
+ logs.splice(0, logs.length - MAX_LOGS);
28
+ }
29
+ };
30
+
31
+ const getData = () => {
32
+ return logs;
33
+ };
34
+
35
+ const cleanup = () => {
36
+ logs.length = 0; // Clear array
37
+ };
38
+
39
+ export const createNavigationLogFeature = () => {
40
+ return {
41
+ name: 'navigation',
42
+ label: 'Navigation Logs',
43
+ setup: setup,
44
+ getData: getData,
45
+ cleanup: cleanup,
46
+ };
47
+ };
@@ -2,7 +2,7 @@ import React from 'react'
2
2
 
3
3
  class NetworkFeature {
4
4
  static instance = null
5
- static MAX_LOGS = 100
5
+ static MAX_LOGS = 200
6
6
 
7
7
  constructor() {
8
8
  if (NetworkFeature.instance) {
@@ -0,0 +1,46 @@
1
+ const MAX_LOGS = 200; // Max number of Zustand logs to store
2
+ const logs = [];
3
+
4
+ // Zustand middleware to capture state changes
5
+ export const addZustandLog = (action, prevState, nextState, actionCompleteTime, storeName) => {
6
+ // Store log data
7
+ logs.push({
8
+ timestamp: new Date(),
9
+ action: action,
10
+ prevState: prevState,
11
+ nextState: nextState,
12
+ actionCompleteTime: actionCompleteTime,
13
+ storeName: storeName,
14
+ });
15
+
16
+ // Trim logs if they exceed the maximum limit
17
+ if (logs.length > MAX_LOGS) {
18
+ logs.splice(0, logs.length - MAX_LOGS);
19
+ }
20
+ };
21
+
22
+ const setup = () => {
23
+ if (!__DEV__) {
24
+ return;
25
+ }
26
+ // Note: The actual middleware setup happens in the store creation
27
+ // This function is mainly for compatibility with the feature API
28
+ };
29
+
30
+ const getData = () => {
31
+ return logs;
32
+ };
33
+
34
+ const cleanup = () => {
35
+ logs.length = 0; // Clear array
36
+ };
37
+
38
+ export const createZustandLogFeature = () => {
39
+ return {
40
+ name: 'zustand',
41
+ label: 'Zustand Logs',
42
+ setup: setup,
43
+ getData: getData,
44
+ cleanup: cleanup,
45
+ };
46
+ };
@@ -0,0 +1,92 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { addNavigationLog } from '../features/NavigationLogFeature';
3
+
4
+ /**
5
+ * Custom hook to log React Navigation events for debugging
6
+ *
7
+ * Usage:
8
+ *
9
+ * // In your navigation container
10
+ * import { useNavigationLogger } from 'react-native-debug-toolkit';
11
+ *
12
+ * function App() {
13
+ * const navigationRef = useRef();
14
+ *
15
+ * // Enable navigation logging
16
+ * useNavigationLogger(navigationRef);
17
+ *
18
+ * return (
19
+ * <NavigationContainer ref={navigationRef}>
20
+ * // ... your navigation structure
21
+ * </NavigationContainer>
22
+ * );
23
+ * }
24
+ */
25
+ export function useNavigationLogger(navigationRef) {
26
+ // Track previous state
27
+ const routeRef = useRef(null);
28
+
29
+ useEffect(() => {
30
+ if (!__DEV__ || !navigationRef.current) {
31
+ return;
32
+ }
33
+
34
+ // Create listeners for navigation events
35
+ const unsubscribeReady = navigationRef.current?.addListener('state', (e) => {
36
+ if (!navigationRef.current) return;
37
+
38
+ const state = navigationRef.current.getRootState();
39
+ if (!state) return;
40
+
41
+ const currentRoute = navigationRef.current.getCurrentRoute();
42
+
43
+ if (!currentRoute) return;
44
+
45
+ // First render won't have a previous state
46
+ if (!routeRef.current) {
47
+ routeRef.current = currentRoute;
48
+ return;
49
+ }
50
+
51
+ // Determine the navigation action
52
+ let action = 'navigate';
53
+
54
+ // Check if this was a push (adding to history) or replace
55
+ const routes = state.routes;
56
+ if (routes.length > 0) {
57
+ const prevIndex = routes.findIndex(route => route.key === routeRef.current.key);
58
+ const currentIndex = routes.findIndex(route => route.key === currentRoute.key);
59
+
60
+ if (currentIndex > prevIndex) {
61
+ action = 'push';
62
+ } else if (currentIndex < prevIndex) {
63
+ action = 'pop';
64
+ } else if (currentIndex === prevIndex) {
65
+ action = 'replace';
66
+ }
67
+ }
68
+
69
+ // Log the navigation
70
+ addNavigationLog(
71
+ action,
72
+ {
73
+ name: routeRef.current.name,
74
+ params: routeRef.current.params,
75
+ },
76
+ {
77
+ name: currentRoute.name,
78
+ params: currentRoute.params,
79
+ },
80
+ performance.now(), // Approximate time
81
+ 0 // No duration measurement available for this event type
82
+ );
83
+
84
+ // Update the reference
85
+ routeRef.current = currentRoute;
86
+ });
87
+
88
+ return () => {
89
+ unsubscribeReady && unsubscribeReady();
90
+ };
91
+ }, [navigationRef]);
92
+ }
package/lib/index.js CHANGED
@@ -2,18 +2,22 @@ import DebugToolKit from './DebugToolKit'
2
2
  import { createNetworkFeature } from './features/NetworkFeature'
3
3
  import { createPerformanceFeature} from './features/PerformanceFeature'
4
4
  import { createConsoleLogFeature } from './features/ConsoleLogFeature'
5
+ import { createZustandLogFeature, zustandLogMiddleware } from './features/ZustandLogFeature'
6
+ import { createNavigationLogFeature, addNavigationLog } from './features/NavigationLogFeature'
5
7
 
6
8
  // Registry mapping feature names (strings) to their creator functions
7
9
  const featureRegistry = {
8
10
  network: createNetworkFeature,
9
11
  console: createConsoleLogFeature,
10
12
  performance: createPerformanceFeature,
13
+ zustand: createZustandLogFeature,
14
+ navigation: createNavigationLogFeature,
11
15
  // Add other built-in features here
12
16
  // 'storage': createStorageFeature,
13
17
  }
14
18
 
15
19
  // List of default features (can use strings now)
16
- const defaultFeatures = ['network', 'console']
20
+ const defaultFeatures = ['network', 'console', 'zustand', 'navigation']
17
21
 
18
22
  /**
19
23
  * Initializes and shows the Debug ToolKit panel with specified features.
@@ -73,4 +77,14 @@ export function initializeDebugToolkit(features = defaultFeatures) {
73
77
  }
74
78
 
75
79
  // Export existing stuff and the new initializer
76
- export { DebugToolKit, createNetworkFeature, createPerformanceFeature, createConsoleLogFeature, featureRegistry };
80
+ export {
81
+ DebugToolKit,
82
+ createNetworkFeature,
83
+ createPerformanceFeature,
84
+ createConsoleLogFeature,
85
+ createZustandLogFeature,
86
+ createNavigationLogFeature,
87
+ addNavigationLog,
88
+ zustandLogMiddleware, // Export middleware for use in Zustand stores
89
+ featureRegistry
90
+ };
@@ -0,0 +1 @@
1
+
@@ -19,17 +19,49 @@ export const DebugColors = {
19
19
  export const getLogLevelColor = (level) => {
20
20
  switch (level?.toLowerCase()) {
21
21
  case 'log':
22
- return '#666';
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 = {
34
34
  iconLink: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVGhD7ZgxDoMwDEV9/xvQtReoGBASYmFhYmNiYmNhYWJiQQIJqUh8y05wlAYw+T/pD5Dg9xwnhPR6vV6v1+v1er3eGvuOb7yIhxEQvjAC4hRGQJzGCIhLGAFxGSMgbmEExC2MgLiFERC3MALiFkZA3MIIiFsYAXELIyBuYQTELYyAuIURELcwAuIWRkDcwgiIWxgBcQsjIG5hBMQtjIC4hREQtzAC4hZGQNzCCIhbGAFxCyMgbmEExC2MgLiFERC3MALiFkZA3MIIiFsYAXELIyBuYQTELYyAuIURELcwAuIWRkDcwgiIW/+PYBgfhPtL9WBrfKUAAAAASUVORK5CYII='
35
35
  };
36
+
37
+ // Function to get color for Zustand action types
38
+ export const getZustandActionColor = (action) => {
39
+ if (!action) return '#666666'; // Default gray
40
+
41
+ // You can customize this based on common Zustand action patterns
42
+ if (action.startsWith('set')) return '#0D96F2'; // Blue for setters
43
+ if (action.startsWith('update')) return '#4CAF50'; // Green for updates
44
+ if (action.startsWith('delete') || action.startsWith('remove')) return '#F93E3E'; // Red for deletions
45
+ if (action.startsWith('toggle')) return '#FCA130'; // Orange for toggles
46
+ if (action.startsWith('init')) return '#9C27B0'; // Purple for initialization
47
+
48
+ return '#0D96F2'; // Default blue for other actions
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
+ };
@@ -0,0 +1,80 @@
1
+ import { MMKV } from 'react-native-mmkv';
2
+
3
+ // Create MMKV instance for storing debug logs
4
+ const storage = new MMKV({
5
+ id: 'debug-toolkit-storage',
6
+ encryptionKey: 'debug-toolkit-encryption-key'
7
+ });
8
+
9
+ // Storage keys for different features
10
+ const STORAGE_KEYS = {
11
+ CONSOLE_LOGS: 'console_logs',
12
+ NETWORK_LOGS: 'network_logs',
13
+ PERFORMANCE_DATA: 'performance_data',
14
+ ZUSTAND_LOGS: 'zustand_logs',
15
+ NAVIGATION_LOGS: 'navigation_logs',
16
+ };
17
+
18
+ /**
19
+ * Utility class for storing and retrieving logs using MMKV
20
+ */
21
+ export class StorageUtils {
22
+ /**
23
+ * Save data to MMKV storage
24
+ * @param {string} key - Storage key
25
+ * @param {any} data - Data to store
26
+ */
27
+ static saveData(key, data) {
28
+ try {
29
+ const jsonString = JSON.stringify(data);
30
+ storage.set(key, jsonString);
31
+ } catch (error) {
32
+ console.error(`Error saving data for key ${key}:`, error);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Retrieve data from MMKV storage
38
+ * @param {string} key - Storage key
39
+ * @param {any} defaultValue - Default value if data doesn't exist
40
+ * @returns {any} - Retrieved data or default value
41
+ */
42
+ static getData(key, defaultValue = null) {
43
+ try {
44
+ const jsonString = storage.getString(key);
45
+ if (!jsonString) return defaultValue;
46
+ return JSON.parse(jsonString);
47
+ } catch (error) {
48
+ console.error(`Error retrieving data for key ${key}:`, error);
49
+ return defaultValue;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Delete data from MMKV storage
55
+ * @param {string} key - Storage key
56
+ */
57
+ static deleteData(key) {
58
+ try {
59
+ storage.delete(key);
60
+ } catch (error) {
61
+ console.error(`Error deleting data for key ${key}:`, error);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Clear all debug toolkit data from storage
67
+ */
68
+ static clearAllData() {
69
+ try {
70
+ Object.values(STORAGE_KEYS).forEach(key => {
71
+ storage.delete(key);
72
+ });
73
+ } catch (error) {
74
+ console.error('Error clearing all data:', error);
75
+ }
76
+ }
77
+ }
78
+
79
+ // Export storage keys for use in features
80
+ export { STORAGE_KEYS };
@@ -8,14 +8,18 @@ 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'
22
+ import SubViewNavigationLogs from './SubViewNavigationLogs'
19
23
  const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
20
24
 
21
25
  export default class FloatPanelView extends Component {
@@ -37,8 +41,12 @@ export default class FloatPanelView extends Component {
37
41
  activeTab: 0,
38
42
  panelTranslateY: new Animated.Value(screenHeight),
39
43
  backdropOpacity: new Animated.Value(0),
44
+ isTabMenuVisible: false,
45
+ panelLayout: { width: 0, height: 0 },
40
46
  }
41
47
 
48
+ this.tabScrollViewRef = React.createRef()
49
+
42
50
  this.gestureResponder = PanResponder.create({
43
51
  onStartShouldSetPanResponder: () => true,
44
52
  onMoveShouldSetPanResponder: () => true,
@@ -196,6 +204,8 @@ export default class FloatPanelView extends Component {
196
204
  }
197
205
 
198
206
  _closePanel = () => {
207
+ this.setState({ isTabMenuVisible: false })
208
+
199
209
  Animated.parallel([
200
210
  Animated.spring(this.state.panelTranslateY, {
201
211
  toValue: screenHeight,
@@ -213,6 +223,26 @@ export default class FloatPanelView extends Component {
213
223
  })
214
224
  }
215
225
 
226
+ _toggleTabMenu = () => {
227
+ this.setState(prevState => ({ isTabMenuVisible: !prevState.isTabMenuVisible }))
228
+ }
229
+
230
+ _handleTabChange = (index) => {
231
+ this.setState({
232
+ activeTab: index,
233
+ isTabMenuVisible: false
234
+ }, () => {
235
+ // Scroll to make active tab visible
236
+ if (this.tabScrollViewRef.current) {
237
+ const tabWidth = 80; // Approximate tab width
238
+ this.tabScrollViewRef.current.scrollTo({
239
+ x: Math.max(0, index * tabWidth - 50), // Center the tab
240
+ animated: true
241
+ });
242
+ }
243
+ })
244
+ }
245
+
216
246
  renderFloatBtn() {
217
247
  const { isOpen, toFloat } = this.state
218
248
 
@@ -281,6 +311,14 @@ export default class FloatPanelView extends Component {
281
311
  if (feature.name === 'console') {
282
312
  return <SubViewConsoleLogs logs={data} />
283
313
  }
314
+
315
+ if (feature.name === 'zustand') {
316
+ return <SubViewZustandLogs logs={data} />
317
+ }
318
+
319
+ if (feature.name === 'navigation') {
320
+ return <SubViewNavigationLogs logs={data} />
321
+ }
284
322
 
285
323
  // Generic fallback for other feature types
286
324
  return (
@@ -304,9 +342,46 @@ export default class FloatPanelView extends Component {
304
342
  return this.renderFeatureContent(tabs[activeTab].id)
305
343
  }
306
344
 
345
+ renderTabBar() {
346
+ const tabs = this.getFeatureTabs()
347
+ const { activeTab } = this.state
348
+
349
+ if (!tabs.length) return null
350
+
351
+ return (
352
+ <View style={styles.tabBarContainer}>
353
+ <View style={styles.tabsRow}>
354
+ <ScrollView
355
+ ref={this.tabScrollViewRef}
356
+ horizontal
357
+ showsHorizontalScrollIndicator={false}
358
+ contentContainerStyle={styles.tabsScrollContainer}>
359
+ {tabs.map((tab, index) => {
360
+ const isActive = index === activeTab
361
+
362
+ return (
363
+ <TouchableOpacity
364
+ key={`tab-${tab.id}`}
365
+ style={[styles.tab, isActive && styles.activeTab]}
366
+ onPress={() => this._handleTabChange(index)}>
367
+ <Text
368
+ style={[styles.tabText, isActive && styles.activeTabText]}
369
+ numberOfLines={1}
370
+ ellipsizeMode="tail">
371
+ {tab.label}
372
+ </Text>
373
+ </TouchableOpacity>
374
+ )
375
+ })}
376
+ </ScrollView>
377
+
378
+ </View>
379
+ </View>
380
+ )
381
+ }
382
+
307
383
  renderPanel() {
308
384
  const { isOpen, panelTranslateY, backdropOpacity } = this.state
309
- const tabs = this.getFeatureTabs()
310
385
 
311
386
  if (!isOpen) {
312
387
  return null
@@ -324,7 +399,11 @@ export default class FloatPanelView extends Component {
324
399
  style={[
325
400
  styles.panel,
326
401
  { transform: [{ translateY: panelTranslateY }] },
327
- ]}>
402
+ ]}
403
+ onLayout={(e) => {
404
+ const { width, height } = e.nativeEvent.layout
405
+ this.setState({ panelLayout: { width, height } })
406
+ }}>
328
407
  <View {...this.panelResponder.panHandlers} style={styles.dragHandle}>
329
408
  <View style={styles.dragIndicator} />
330
409
  </View>
@@ -335,12 +414,12 @@ export default class FloatPanelView extends Component {
335
414
  <Text style={styles.closeButtonText}>×</Text>
336
415
  </Pressable>
337
416
  </View>
338
- <TabView
339
- tabs={tabs}
340
- activeTab={this.state.activeTab}
341
- onTabChange={(index) => this.setState({ activeTab: index })}>
417
+
418
+ {this.renderTabBar()}
419
+
420
+ <View style={styles.contentContainer}>
342
421
  {this.renderContent()}
343
- </TabView>
422
+ </View>
344
423
  </SafeAreaView>
345
424
  </Animated.View>
346
425
  </View>
@@ -440,6 +519,9 @@ const styles = StyleSheet.create({
440
519
  panelContent: {
441
520
  flex: 1,
442
521
  },
522
+ contentContainer: {
523
+ flex: 1,
524
+ },
443
525
  header: {
444
526
  flexDirection: 'row',
445
527
  justifyContent: 'space-between',
@@ -462,6 +544,41 @@ const styles = StyleSheet.create({
462
544
  color: DebugColors.text,
463
545
  fontWeight: 'bold',
464
546
  },
547
+ tabBarContainer: {
548
+ borderBottomWidth: 1,
549
+ borderBottomColor: DebugColors.border,
550
+ backgroundColor: DebugColors.background,
551
+ },
552
+ tabsRow: {
553
+ flexDirection: 'row',
554
+ alignItems: 'center',
555
+ },
556
+ tabsScrollContainer: {
557
+ paddingHorizontal: 15,
558
+ flexDirection: 'row',
559
+ alignItems: 'center',
560
+ },
561
+ tab: {
562
+ paddingVertical: 12,
563
+ paddingHorizontal: 8,
564
+ marginRight: 4,
565
+ alignItems: 'center',
566
+ justifyContent: 'center',
567
+ minWidth: 50,
568
+ },
569
+ activeTab: {
570
+ borderBottomWidth: 2,
571
+ borderBottomColor: DebugColors.blue,
572
+ },
573
+ tabText: {
574
+ fontSize: 13,
575
+ color: DebugColors.textLight,
576
+ textAlign: 'center',
577
+ },
578
+ activeTabText: {
579
+ color: DebugColors.blue,
580
+ fontWeight: '600',
581
+ },
465
582
  emptyText: {
466
583
  padding: 20,
467
584
  textAlign: 'center',
@@ -496,5 +613,5 @@ const styles = StyleSheet.create({
496
613
  fontFamily: 'monospace',
497
614
  fontSize: 12,
498
615
  color: DebugColors.text,
499
- },
616
+ }
500
617
  })