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 +8 -0
- package/lib/features/ConsoleLogFeature.js +1 -1
- package/lib/features/NavigationLogFeature.js +47 -0
- package/lib/features/NetworkFeature.js +1 -1
- package/lib/features/ZustandLogFeature.js +46 -0
- package/lib/hooks/useNavigationLogger.js +92 -0
- package/lib/index.js +16 -2
- package/lib/navigation/NavigationLogger.js +1 -0
- package/lib/utils/DebugConst.js +37 -5
- package/lib/utils/StorageUtils.js +80 -0
- package/lib/views/FloatPanelView.js +127 -10
- package/lib/views/NavigationLogDetails.js +294 -0
- package/lib/views/SubViewNavigationLogs.js +199 -0
- package/lib/views/SubViewZustandLogs.js +279 -0
- package/lib/views/ZustandLogDetails.js +355 -0
- package/package.json +2 -2
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
|
|
@@ -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
|
+
};
|
|
@@ -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 {
|
|
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
|
+
|
package/lib/utils/DebugConst.js
CHANGED
|
@@ -19,17 +19,49 @@ export const DebugColors = {
|
|
|
19
19
|
export const getLogLevelColor = (level) => {
|
|
20
20
|
switch (level?.toLowerCase()) {
|
|
21
21
|
case 'log':
|
|
22
|
-
return '#
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
417
|
+
|
|
418
|
+
{this.renderTabBar()}
|
|
419
|
+
|
|
420
|
+
<View style={styles.contentContainer}>
|
|
342
421
|
{this.renderContent()}
|
|
343
|
-
</
|
|
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
|
})
|