react-native-debug-toolkit 0.5.3 → 0.6.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/index.js CHANGED
@@ -11,6 +11,7 @@ import { createConsoleLogFeature } from './lib/features/ConsoleLogFeature'
11
11
  import { createZustandLogFeature, addZustandLog } from './lib/features/ZustandLogFeature'
12
12
  import { createNavigationLogFeature, addNavigationLog } from './lib/features/NavigationLogFeature'
13
13
  import { useNavigationLogger } from './lib/hooks/useNavigationLogger'
14
+ import { createTrackFeature, addTrackLog } from './lib/features/TrackFeature'
14
15
 
15
16
  // Function to clear all logs and close the panel
16
17
  const clearAll = () => {
@@ -30,9 +31,11 @@ export {
30
31
  addZustandLog,
31
32
  createNavigationLogFeature,
32
33
  addNavigationLog,
34
+ addTrackLog,
33
35
  useNavigationLogger,
34
36
  isDebugMode,
35
- clearAll
37
+ clearAll,
38
+ createTrackFeature
36
39
  }
37
40
 
38
41
  export default DebugToolKit
@@ -0,0 +1,97 @@
1
+ const MAX_LOGS = 200; // Max number of track events to store
2
+ const logs = [];
3
+
4
+ const setup = () => {
5
+
6
+ // Note: Actual event tracking will be done in the main app code
7
+ // This is just a setup function for compatibility with the feature API
8
+ };
9
+
10
+ // Function to add track event log
11
+ export const addTrackLog = (eventData) => {
12
+ if (!__DEV__) {
13
+ return;
14
+ }
15
+
16
+ // Store log data
17
+ const logEntry = {
18
+ timestamp: new Date(),
19
+ eventName: eventData.eventName || 'unknown_event',
20
+ entityType: eventData.entityType,
21
+ entityName: eventData.entityName,
22
+ pageId: eventData.pageId,
23
+ objId: eventData.objId,
24
+ entityPath: eventData.entityPath,
25
+ objType: eventData.objType,
26
+ objPt: eventData.objPt,
27
+ refPageLocation: eventData.refPageLocation,
28
+ position: eventData.position,
29
+ entityLocation: eventData.entityLocation,
30
+ frontOperation: eventData.frontOperation,
31
+ sessionId: eventData.sessionId,
32
+ requestId: eventData.requestId,
33
+ searchKeywored: eventData.searchKeywored,
34
+ enSearchKeywored: eventData.enSearchKeywored,
35
+ // Include any additional custom properties
36
+ ...eventData
37
+ };
38
+
39
+ logs.push(logEntry);
40
+
41
+ // Trim logs if they exceed the maximum limit
42
+ if (logs.length > MAX_LOGS) {
43
+ logs.splice(0, logs.length - MAX_LOGS);
44
+ }
45
+ };
46
+
47
+ // Get event statistics
48
+ export const getEventStats = () => {
49
+ const stats = {};
50
+ logs.forEach(log => {
51
+ const eventName = log.eventName;
52
+ if (!stats[eventName]) {
53
+ stats[eventName] = {
54
+ count: 0,
55
+ lastSeen: log.timestamp,
56
+ entityTypes: new Set(),
57
+ operations: new Set()
58
+ };
59
+ }
60
+ stats[eventName].count++;
61
+ if (log.timestamp > stats[eventName].lastSeen) {
62
+ stats[eventName].lastSeen = log.timestamp;
63
+ }
64
+ if (log.entityType) {
65
+ stats[eventName].entityTypes.add(log.entityType);
66
+ }
67
+ if (log.frontOperation) {
68
+ stats[eventName].operations.add(log.frontOperation);
69
+ }
70
+ });
71
+
72
+ // Convert Sets to Arrays for display
73
+ Object.keys(stats).forEach(eventName => {
74
+ stats[eventName].entityTypes = Array.from(stats[eventName].entityTypes);
75
+ stats[eventName].operations = Array.from(stats[eventName].operations);
76
+ });
77
+
78
+ return stats;
79
+ };
80
+
81
+ const getData = () => {
82
+ return logs;
83
+ };
84
+
85
+ const cleanup = () => {
86
+ logs.length = 0; // Clear array
87
+ };
88
+
89
+ export const createTrackFeature = () => {
90
+ return {
91
+ name: 'track',
92
+ label: 'Track Events',
93
+ setup: setup,
94
+ getData: getData,
95
+ cleanup: cleanup,
96
+ };
97
+ };
package/lib/index.js CHANGED
@@ -5,11 +5,13 @@ import { createConsoleLogFeature } from './features/ConsoleLogFeature'
5
5
  import { createZustandLogFeature, zustandLogMiddleware } from './features/ZustandLogFeature'
6
6
  import { createNavigationLogFeature, addNavigationLog } from './features/NavigationLogFeature'
7
7
  import { createThirdPartyLibsFeature } from './features/ThirdPartyLibsFeature'
8
+ import { createTrackFeature, addTrackLog } from './features/TrackFeature'
8
9
  import NativeDebugLibs from './NativeDebugLibs'
9
10
 
10
11
  // Registry mapping feature names (strings) to their creator functions
11
12
  const featureRegistry = {
12
13
  network: createNetworkFeature,
14
+ track: createTrackFeature,
13
15
  console: createConsoleLogFeature,
14
16
  // performance: createPerformanceFeature,
15
17
  zustand: createZustandLogFeature,
@@ -20,7 +22,7 @@ const featureRegistry = {
20
22
  }
21
23
 
22
24
  // List of default features (can use strings now)
23
- const defaultFeatures = ['network', 'console', 'zustand', 'navigation', 'thirdPartyLibs']
25
+ const defaultFeatures = ['network', 'track', 'navigation','zustand', 'thirdPartyLibs', 'console']
24
26
 
25
27
  // Export the debug mode status - this is used by the app
26
28
  // const isDebugMode = NativeDebugLibs.isDebugBuild();
@@ -104,6 +106,8 @@ export {
104
106
  createNavigationLogFeature,
105
107
  createThirdPartyLibsFeature,
106
108
  addNavigationLog,
109
+ createTrackFeature,
110
+ addTrackLog,
107
111
  zustandLogMiddleware, // Export middleware for use in Zustand stores
108
112
  featureRegistry,
109
113
  isDebugMode
@@ -0,0 +1,92 @@
1
+ // Define the entity types and names as constants for better type safety
2
+ export const EntityType = {
3
+ mine_module: 'mine_module',
4
+ search_module: 'search_module',
5
+ page_view: 'page_view',
6
+ error_module: 'error_module',
7
+ navigation: 'navigation',
8
+ content: 'content',
9
+ user_action: 'user_action',
10
+ } as const
11
+
12
+ export const EntityName = {
13
+ MINE_POST: 'MINE_POST',
14
+ HOME_PAGE: 'HOME_PAGE',
15
+ SEARCH_BAR: 'SEARCH_BAR',
16
+ PROFILE_PAGE: 'PROFILE_PAGE',
17
+ NAVIGATION_TAB: 'NAVIGATION_TAB',
18
+ CONTENT_ITEM: 'CONTENT_ITEM',
19
+ USER_BUTTON: 'USER_BUTTON',
20
+ } as const
21
+
22
+ export type TrackEventOptions = {
23
+ entityType: (typeof EntityType)[keyof typeof EntityType]
24
+ entityName?: (typeof EntityName)[keyof typeof EntityName]
25
+ pageId?: string
26
+ objId?: string
27
+ entityPath?: string
28
+ objType?: number
29
+ objPt?: string // 瀑布流作品pt
30
+ refPageLocation?: string
31
+ position?: string
32
+ entityLocation?: string
33
+ frontOperation?: string
34
+ sessionId?: string //搜索模块需要
35
+ requestId?: string //搜索模块需要
36
+ searchKeywored?: string //搜索模块需要
37
+ enSearchKeywored?: string //搜索模块需要
38
+ }
39
+
40
+ // Complete event structure that includes the event name and all tracking options
41
+ export type TrackEvent = {
42
+ eventName: string
43
+ timestamp: Date
44
+ } & TrackEventOptions
45
+
46
+ // Track feature interface
47
+ export interface ITrackFeature {
48
+ setup(): ITrackFeature
49
+ getData(): TrackEvent[]
50
+ cleanup(): void
51
+ logEvent(eventData: TrackEventOptions & { eventName: string }): void
52
+ getEventStats(): EventStats
53
+ addEventToBlacklist(pattern: string | RegExp): void
54
+ removeEventFromBlacklist(pattern: string | RegExp): void
55
+ clearBlacklist(): void
56
+ getBlacklist(): (string | RegExp)[]
57
+ }
58
+
59
+ // Event statistics structure
60
+ export type EventStats = {
61
+ [eventName: string]: {
62
+ count: number
63
+ lastSeen: Date
64
+ entityTypes: string[]
65
+ operations: string[]
66
+ }
67
+ }
68
+
69
+ // Common event names used in the application
70
+ export const CommonEventNames = {
71
+ LOG_ACTION_FRONT: 'log_action_front',
72
+ LOG_ERROR_FRONT: 'log_error_front',
73
+ LOG_VIEW_FRONT: 'log_view_front',
74
+ LOG_SEARCH_FRONT: 'log_search_front',
75
+ LOG_NAVIGATION_FRONT: 'log_navigation_front',
76
+ } as const
77
+
78
+ // Common front operations
79
+ export const FrontOperations = {
80
+ CLICK: 'click',
81
+ VIEW: 'view',
82
+ SEARCH: 'search',
83
+ SWIPE: 'swipe',
84
+ SCROLL: 'scroll',
85
+ ERROR: 'error',
86
+ NAVIGATE: 'navigate',
87
+ } as const
88
+
89
+ // Helper type for creating events with better type safety
90
+ export type CreateTrackEvent<T extends keyof typeof CommonEventNames> = {
91
+ eventName: (typeof CommonEventNames)[T]
92
+ } & TrackEventOptions
@@ -21,6 +21,8 @@ import SubViewConsoleLogs from './SubViewConsoleLogs'
21
21
  import SubViewZustandLogs from './SubViewZustandLogs'
22
22
  import SubViewNavigationLogs from './SubViewNavigationLogs'
23
23
  import SubViewThirdPartyLibs from './SubViewThirdPartyLibs'
24
+ import SubViewTrackLogs from './SubViewTrackLogs'
25
+
24
26
  const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
25
27
 
26
28
  export default class FloatPanelView extends Component {
@@ -320,6 +322,10 @@ export default class FloatPanelView extends Component {
320
322
  if (feature.name === 'navigation') {
321
323
  return <SubViewNavigationLogs logs={data} />
322
324
  }
325
+
326
+ if (feature.name === 'track') {
327
+ return <SubViewTrackLogs logs={data} />
328
+ }
323
329
 
324
330
  if (feature.name === 'thirdPartyLibs') {
325
331
  return <SubViewThirdPartyLibs libraries={data} />
@@ -0,0 +1,318 @@
1
+ import React, { useState } from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ FlatList,
7
+ TouchableOpacity,
8
+ } from 'react-native'
9
+ import TrackLogDetails from './TrackLogDetails'
10
+
11
+ const SubViewTrackLogs = ({ logs = [] }) => {
12
+ const [selectedLog, setSelectedLog] = useState(null)
13
+
14
+ const getEventColor = (eventName) => {
15
+ if (eventName?.includes('error')) return '#ff4444'
16
+ if (eventName?.includes('click')) return '#4CAF50'
17
+ if (eventName?.includes('view')) return '#2196F3'
18
+ if (eventName?.includes('search')) return '#FF9800'
19
+ return '#9C27B0'
20
+ }
21
+
22
+ const getTimeAgo = (timestamp) => {
23
+ const now = new Date()
24
+ const eventTime = new Date(timestamp)
25
+ const diffMs = now - eventTime
26
+ const diffSeconds = Math.floor(diffMs / 1000)
27
+ const diffMinutes = Math.floor(diffSeconds / 60)
28
+ const diffHours = Math.floor(diffMinutes / 60)
29
+
30
+ if (diffSeconds < 60) {
31
+ return `${diffSeconds}s ago`
32
+ } else if (diffMinutes < 60) {
33
+ return `${diffMinutes}m ago`
34
+ } else if (diffHours < 24) {
35
+ return `${diffHours}h ago`
36
+ } else {
37
+ const diffDays = Math.floor(diffHours / 24)
38
+ return `${diffDays}d ago`
39
+ }
40
+ }
41
+
42
+ const renderLogItem = ({ item }) => {
43
+ const eventColor = getEventColor(item.eventName)
44
+
45
+ return (
46
+ <TouchableOpacity
47
+ style={styles.logItem}
48
+ onPress={() => setSelectedLog(item)}>
49
+ <View style={styles.logItemContainer}>
50
+ <View
51
+ style={[styles.eventIndicator, { backgroundColor: eventColor }]}
52
+ />
53
+
54
+ <View style={styles.logContent}>
55
+ <View style={styles.eventRow}>
56
+ <Text style={[styles.eventName, { color: eventColor }]}>
57
+ {item.eventName || 'Unknown Event'}
58
+ </Text>
59
+ {item.entityType && (
60
+ <Text style={styles.entityType}>
61
+ {item.entityType}
62
+ </Text>
63
+ )}
64
+ </View>
65
+
66
+ {item.entityName && (
67
+ <Text style={styles.entityName} numberOfLines={1}>
68
+ {item.entityName}
69
+ </Text>
70
+ )}
71
+
72
+ {item.frontOperation && (
73
+ <Text style={styles.operation} numberOfLines={1}>
74
+ Operation: {item.frontOperation}
75
+ </Text>
76
+ )}
77
+
78
+ <View style={styles.logFooter}>
79
+ <Text style={styles.time}>
80
+ {item.timestamp
81
+ ? getTimeAgo(item.timestamp)
82
+ : 'Unknown time'}
83
+ </Text>
84
+
85
+ <View style={styles.additionalInfo}>
86
+ {item.pageId && (
87
+ <Text style={styles.pageId} numberOfLines={1}>
88
+ Page: {item.pageId}
89
+ </Text>
90
+ )}
91
+ {item.position && (
92
+ <Text style={styles.position}>
93
+ Pos: {item.position}
94
+ </Text>
95
+ )}
96
+ </View>
97
+ </View>
98
+ </View>
99
+ </View>
100
+ </TouchableOpacity>
101
+ )
102
+ }
103
+
104
+ // If a log is selected, show the details view with a back button
105
+ if (selectedLog) {
106
+ return (
107
+ <View style={styles.container}>
108
+ <View style={styles.detailsHeader}>
109
+ <TouchableOpacity
110
+ style={styles.backButton}
111
+ onPress={() => setSelectedLog(null)}>
112
+ <Text style={styles.backButtonText}>← Back</Text>
113
+ </TouchableOpacity>
114
+ <View style={styles.headerEventInfo}>
115
+ <Text
116
+ style={[
117
+ styles.headerEvent,
118
+ { color: getEventColor(selectedLog.eventName) },
119
+ ]}>
120
+ {selectedLog.eventName || 'Unknown Event'}
121
+ </Text>
122
+ {selectedLog.entityType && (
123
+ <Text style={styles.headerEntityType}>
124
+ {selectedLog.entityType}
125
+ </Text>
126
+ )}
127
+ </View>
128
+ </View>
129
+ <TrackLogDetails log={selectedLog} />
130
+ </View>
131
+ )
132
+ }
133
+
134
+ // Otherwise show the list view
135
+ return (
136
+ <View style={styles.container}>
137
+ {logs.length === 0 ? (
138
+ <View style={styles.emptyContainer}>
139
+ <Text style={styles.emptyText}>No track events logged yet</Text>
140
+ <Text style={styles.emptySubtext}>
141
+ Track events will appear here as they occur during development
142
+ </Text>
143
+ </View>
144
+ ) : (
145
+ <>
146
+ <View style={styles.statsContainer}>
147
+ <Text style={styles.statsText}>
148
+ {logs.length} events logged
149
+ </Text>
150
+ </View>
151
+ <FlatList
152
+ data={[...logs].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))}
153
+ renderItem={renderLogItem}
154
+ keyExtractor={(item, index) => `${item.timestamp}-${index}`}
155
+ style={styles.list}
156
+ showsVerticalScrollIndicator={true}
157
+ />
158
+ </>
159
+ )}
160
+ </View>
161
+ )
162
+ }
163
+
164
+ const styles = StyleSheet.create({
165
+ container: {
166
+ flex: 1,
167
+ backgroundColor: '#fff',
168
+ },
169
+ list: {
170
+ flex: 1,
171
+ },
172
+ emptyContainer: {
173
+ flex: 1,
174
+ justifyContent: 'center',
175
+ alignItems: 'center',
176
+ padding: 40,
177
+ },
178
+ emptyText: {
179
+ textAlign: 'center',
180
+ color: '#666',
181
+ fontSize: 16,
182
+ fontWeight: '500',
183
+ marginBottom: 8,
184
+ },
185
+ emptySubtext: {
186
+ textAlign: 'center',
187
+ color: '#999',
188
+ fontSize: 14,
189
+ },
190
+ statsContainer: {
191
+ padding: 12,
192
+ borderBottomWidth: 1,
193
+ borderBottomColor: '#eee',
194
+ backgroundColor: '#f8f9fa',
195
+ },
196
+ statsText: {
197
+ fontSize: 12,
198
+ color: '#666',
199
+ fontWeight: '500',
200
+ },
201
+ logItem: {
202
+ borderBottomWidth: 1,
203
+ borderBottomColor: '#eee',
204
+ },
205
+ logItemContainer: {
206
+ flexDirection: 'row',
207
+ padding: 12,
208
+ },
209
+ eventIndicator: {
210
+ width: 4,
211
+ borderRadius: 2,
212
+ marginRight: 12,
213
+ },
214
+ logContent: {
215
+ flex: 1,
216
+ },
217
+ eventRow: {
218
+ flexDirection: 'row',
219
+ alignItems: 'center',
220
+ marginBottom: 4,
221
+ },
222
+ eventName: {
223
+ fontSize: 15,
224
+ fontWeight: 'bold',
225
+ marginRight: 8,
226
+ },
227
+ entityType: {
228
+ fontSize: 11,
229
+ backgroundColor: '#f0f0f0',
230
+ color: '#666',
231
+ paddingHorizontal: 6,
232
+ paddingVertical: 2,
233
+ borderRadius: 6,
234
+ fontWeight: '500',
235
+ },
236
+ entityName: {
237
+ fontSize: 14,
238
+ color: '#333',
239
+ marginBottom: 4,
240
+ fontWeight: '500',
241
+ },
242
+ operation: {
243
+ fontSize: 13,
244
+ color: '#666',
245
+ marginBottom: 6,
246
+ fontStyle: 'italic',
247
+ },
248
+ logFooter: {
249
+ flexDirection: 'row',
250
+ justifyContent: 'space-between',
251
+ alignItems: 'flex-end',
252
+ },
253
+ time: {
254
+ fontSize: 12,
255
+ color: '#999',
256
+ },
257
+ additionalInfo: {
258
+ flexDirection: 'row',
259
+ alignItems: 'center',
260
+ gap: 8,
261
+ },
262
+ pageId: {
263
+ fontSize: 11,
264
+ color: '#666',
265
+ backgroundColor: '#f8f9fa',
266
+ paddingHorizontal: 4,
267
+ paddingVertical: 1,
268
+ borderRadius: 3,
269
+ maxWidth: 80,
270
+ },
271
+ position: {
272
+ fontSize: 11,
273
+ color: '#666',
274
+ backgroundColor: '#f8f9fa',
275
+ paddingHorizontal: 4,
276
+ paddingVertical: 1,
277
+ borderRadius: 3,
278
+ },
279
+ detailsHeader: {
280
+ flexDirection: 'row',
281
+ alignItems: 'center',
282
+ padding: 15,
283
+ borderBottomWidth: 1,
284
+ borderBottomColor: '#eee',
285
+ backgroundColor: '#f8f9fa',
286
+ },
287
+ backButton: {
288
+ paddingHorizontal: 10,
289
+ paddingVertical: 5,
290
+ borderRadius: 4,
291
+ backgroundColor: '#eee',
292
+ marginRight: 10,
293
+ },
294
+ backButtonText: {
295
+ color: '#333',
296
+ fontWeight: '500',
297
+ },
298
+ headerEventInfo: {
299
+ flexDirection: 'row',
300
+ alignItems: 'center',
301
+ },
302
+ headerEvent: {
303
+ fontSize: 16,
304
+ fontWeight: 'bold',
305
+ marginRight: 10,
306
+ },
307
+ headerEntityType: {
308
+ fontSize: 12,
309
+ backgroundColor: '#f0f0f0',
310
+ color: '#666',
311
+ paddingHorizontal: 8,
312
+ paddingVertical: 3,
313
+ borderRadius: 8,
314
+ fontWeight: '500',
315
+ },
316
+ })
317
+
318
+ export default SubViewTrackLogs
@@ -0,0 +1,481 @@
1
+ import React, { useState, useCallback } from 'react'
2
+ import { View, Text, StyleSheet, Clipboard, Dimensions } from 'react-native'
3
+ import { ScrollView, Pressable } from 'react-native'
4
+ import JSONTree from 'react-native-json-tree'
5
+
6
+ const { width: SCREEN_WIDTH } = Dimensions.get('window')
7
+
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] = 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] = 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
+ const JSONValue = ({ value, path = '', level = 0, maxExpandLevel = 2 }) => {
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
+ // For objects and arrays, use JSONTree for improved large data handling
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 < maxExpandLevel}
95
+ />
96
+ )
97
+ }
98
+
99
+ return <Text>{String(value)}</Text>
100
+ }
101
+
102
+ const EventTypeBadge = ({ eventName, entityType }) => {
103
+ const getEventColor = () => {
104
+ if (eventName?.includes('error')) return '#ff4444'
105
+ if (eventName?.includes('click')) return '#4CAF50'
106
+ if (eventName?.includes('view')) return '#2196F3'
107
+ if (eventName?.includes('search')) return '#FF9800'
108
+ return '#9C27B0'
109
+ }
110
+
111
+ return (
112
+ <View style={styles.badgeContainer}>
113
+ <Text style={[styles.eventBadge, { backgroundColor: getEventColor() }]}>
114
+ {eventName || 'Unknown Event'}
115
+ </Text>
116
+ {entityType && (
117
+ <Text style={[styles.entityBadge]}>
118
+ {entityType}
119
+ </Text>
120
+ )}
121
+ </View>
122
+ )
123
+ }
124
+
125
+ const TrackLogDetails = ({ log }) => {
126
+ const generateAnalyticsPayload = useCallback(() => {
127
+ if (!log) {
128
+ return 'No event data available'
129
+ }
130
+
131
+ const payload = {
132
+ eventName: log.eventName,
133
+ timestamp: log.timestamp,
134
+ ...Object.fromEntries(
135
+ Object.entries(log).filter(([key, value]) =>
136
+ key !== 'timestamp' && value !== undefined && value !== null
137
+ )
138
+ )
139
+ }
140
+
141
+ return JSON.stringify(payload, null, 2)
142
+ }, [log])
143
+
144
+ if (!log) {
145
+ return (
146
+ <View style={styles.errorContainer}>
147
+ <Text style={styles.errorText}>Event data is missing</Text>
148
+ </View>
149
+ )
150
+ }
151
+
152
+ const coreProperties = {
153
+ eventName: log.eventName,
154
+ entityType: log.entityType,
155
+ entityName: log.entityName,
156
+ frontOperation: log.frontOperation
157
+ }
158
+
159
+ const identificationProperties = {
160
+ pageId: log.pageId,
161
+ objId: log.objId,
162
+ entityPath: log.entityPath,
163
+ objType: log.objType,
164
+ objPt: log.objPt
165
+ }
166
+
167
+ const locationProperties = {
168
+ refPageLocation: log.refPageLocation,
169
+ position: log.position,
170
+ entityLocation: log.entityLocation
171
+ }
172
+
173
+ const searchProperties = {
174
+ sessionId: log.sessionId,
175
+ requestId: log.requestId,
176
+ searchKeywored: log.searchKeywored,
177
+ enSearchKeywored: log.enSearchKeywored
178
+ }
179
+
180
+ const filterUndefinedProperties = (obj) => {
181
+ return Object.fromEntries(
182
+ Object.entries(obj).filter(([key, value]) => value !== undefined && value !== null && value !== '')
183
+ )
184
+ }
185
+
186
+ return (
187
+ <ScrollView
188
+ style={styles.container}
189
+ contentContainerStyle={styles.contentContainer}
190
+ showsVerticalScrollIndicator={true}
191
+ scrollEventThrottle={16}
192
+ keyboardShouldPersistTaps='handled'>
193
+
194
+ <View style={styles.header}>
195
+ <View style={styles.headerInfo}>
196
+ <EventTypeBadge eventName={log.eventName} entityType={log.entityType} />
197
+ {log.entityName && (
198
+ <Text style={styles.entityName} selectable={true}>
199
+ {log.entityName}
200
+ </Text>
201
+ )}
202
+ {log.frontOperation && (
203
+ <Text style={styles.operation}>
204
+ Operation: {log.frontOperation}
205
+ </Text>
206
+ )}
207
+ </View>
208
+ <CopyButton text={generateAnalyticsPayload()} />
209
+ </View>
210
+
211
+ <CollapsibleSection title='Core Properties' initiallyExpanded={true}>
212
+ <View style={styles.content}>
213
+ <View style={styles.propertiesGrid}>
214
+ {Object.entries(filterUndefinedProperties(coreProperties)).map(([key, value]) => (
215
+ <View key={key} style={styles.propertyRow}>
216
+ <Text style={styles.propertyKey}>{key}:</Text>
217
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
218
+ </View>
219
+ ))}
220
+ </View>
221
+ </View>
222
+ </CollapsibleSection>
223
+
224
+ {Object.keys(filterUndefinedProperties(identificationProperties)).length > 0 && (
225
+ <CollapsibleSection title='Identification Properties'>
226
+ <View style={styles.content}>
227
+ <View style={styles.propertiesGrid}>
228
+ {Object.entries(filterUndefinedProperties(identificationProperties)).map(([key, value]) => (
229
+ <View key={key} style={styles.propertyRow}>
230
+ <Text style={styles.propertyKey}>{key}:</Text>
231
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
232
+ </View>
233
+ ))}
234
+ </View>
235
+ </View>
236
+ </CollapsibleSection>
237
+ )}
238
+
239
+ {Object.keys(filterUndefinedProperties(locationProperties)).length > 0 && (
240
+ <CollapsibleSection title='Location Properties'>
241
+ <View style={styles.content}>
242
+ <View style={styles.propertiesGrid}>
243
+ {Object.entries(filterUndefinedProperties(locationProperties)).map(([key, value]) => (
244
+ <View key={key} style={styles.propertyRow}>
245
+ <Text style={styles.propertyKey}>{key}:</Text>
246
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
247
+ </View>
248
+ ))}
249
+ </View>
250
+ </View>
251
+ </CollapsibleSection>
252
+ )}
253
+
254
+ {Object.keys(filterUndefinedProperties(searchProperties)).length > 0 && (
255
+ <CollapsibleSection title='Search Properties'>
256
+ <View style={styles.content}>
257
+ <View style={styles.propertiesGrid}>
258
+ {Object.entries(filterUndefinedProperties(searchProperties)).map(([key, value]) => (
259
+ <View key={key} style={styles.propertyRow}>
260
+ <Text style={styles.propertyKey}>{key}:</Text>
261
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
262
+ </View>
263
+ ))}
264
+ </View>
265
+ </View>
266
+ </CollapsibleSection>
267
+ )}
268
+
269
+ <CollapsibleSection title='Complete Event Data'>
270
+ <View style={styles.content}>
271
+ <View style={styles.dataSectionHeader}>
272
+ <Text style={styles.dataSectionTitle}>Full JSON Payload</Text>
273
+ <CopyButton text={generateAnalyticsPayload()} />
274
+ </View>
275
+ <View style={styles.dataContentWrapper}>
276
+ <ScrollView
277
+ style={styles.dataContent}
278
+ nestedScrollEnabled={true}
279
+ bounces={false}
280
+ showsVerticalScrollIndicator={true}>
281
+ <JSONValue value={log} maxExpandLevel={2} />
282
+ </ScrollView>
283
+ </View>
284
+ </View>
285
+ </CollapsibleSection>
286
+
287
+ <CollapsibleSection title='Timing Information'>
288
+ <View style={styles.content}>
289
+ <View style={styles.propertyRow}>
290
+ <Text style={styles.propertyKey}>Timestamp:</Text>
291
+ <Text style={styles.propertyValue}>
292
+ {log.timestamp
293
+ ? new Date(log.timestamp).toLocaleString()
294
+ : 'Unknown'}
295
+ </Text>
296
+ </View>
297
+ <View style={styles.propertyRow}>
298
+ <Text style={styles.propertyKey}>Time ago:</Text>
299
+ <Text style={styles.propertyValue}>
300
+ {log.timestamp
301
+ ? getTimeAgo(log.timestamp)
302
+ : 'Unknown'}
303
+ </Text>
304
+ </View>
305
+ </View>
306
+ </CollapsibleSection>
307
+ </ScrollView>
308
+ )
309
+ }
310
+
311
+ const getTimeAgo = (timestamp) => {
312
+ const now = new Date()
313
+ const eventTime = new Date(timestamp)
314
+ const diffMs = now - eventTime
315
+ const diffSeconds = Math.floor(diffMs / 1000)
316
+ const diffMinutes = Math.floor(diffSeconds / 60)
317
+ const diffHours = Math.floor(diffMinutes / 60)
318
+
319
+ if (diffSeconds < 60) {
320
+ return `${diffSeconds} seconds ago`
321
+ } else if (diffMinutes < 60) {
322
+ return `${diffMinutes} minutes ago`
323
+ } else if (diffHours < 24) {
324
+ return `${diffHours} hours ago`
325
+ } else {
326
+ const diffDays = Math.floor(diffHours / 24)
327
+ return `${diffDays} days ago`
328
+ }
329
+ }
330
+
331
+ const styles = StyleSheet.create({
332
+ container: {
333
+ flex: 1,
334
+ backgroundColor: '#fff',
335
+ },
336
+ contentContainer: {
337
+ paddingBottom: 20,
338
+ },
339
+ header: {
340
+ flexDirection: 'row',
341
+ padding: 15,
342
+ borderBottomWidth: 1,
343
+ borderBottomColor: '#eee',
344
+ alignItems: 'flex-start',
345
+ },
346
+ headerInfo: {
347
+ flex: 1,
348
+ marginRight: 10,
349
+ },
350
+ badgeContainer: {
351
+ flexDirection: 'row',
352
+ alignItems: 'center',
353
+ marginBottom: 8,
354
+ },
355
+ eventBadge: {
356
+ paddingHorizontal: 12,
357
+ paddingVertical: 4,
358
+ borderRadius: 12,
359
+ color: 'white',
360
+ fontSize: 12,
361
+ fontWeight: 'bold',
362
+ marginRight: 8,
363
+ },
364
+ entityBadge: {
365
+ paddingHorizontal: 8,
366
+ paddingVertical: 2,
367
+ borderRadius: 8,
368
+ backgroundColor: '#f0f0f0',
369
+ color: '#333',
370
+ fontSize: 11,
371
+ fontWeight: '500',
372
+ },
373
+ entityName: {
374
+ fontSize: 16,
375
+ color: '#333',
376
+ marginBottom: 4,
377
+ fontWeight: '600',
378
+ },
379
+ operation: {
380
+ fontSize: 14,
381
+ color: '#666',
382
+ fontStyle: 'italic',
383
+ },
384
+ collapsibleSection: {
385
+ marginBottom: 1,
386
+ backgroundColor: '#fff',
387
+ },
388
+ sectionHeader: {
389
+ flexDirection: 'row',
390
+ justifyContent: 'space-between',
391
+ alignItems: 'center',
392
+ padding: 15,
393
+ backgroundColor: '#f5f5f5',
394
+ },
395
+ sectionTitle: {
396
+ fontSize: 15,
397
+ fontWeight: 'bold',
398
+ color: '#333',
399
+ },
400
+ expandIcon: {
401
+ fontSize: 14,
402
+ color: '#666',
403
+ },
404
+ content: {
405
+ padding: 15,
406
+ },
407
+ propertiesGrid: {
408
+ gap: 8,
409
+ },
410
+ propertyRow: {
411
+ marginBottom: 8,
412
+ },
413
+ propertyKey: {
414
+ fontSize: 14,
415
+ fontWeight: 'bold',
416
+ color: '#666',
417
+ marginBottom: 2,
418
+ },
419
+ propertyValue: {
420
+ fontSize: 14,
421
+ color: '#333',
422
+ },
423
+ errorContainer: {
424
+ flex: 1,
425
+ justifyContent: 'center',
426
+ alignItems: 'center',
427
+ padding: 20,
428
+ },
429
+ errorText: {
430
+ color: '#ff4444',
431
+ fontSize: 16,
432
+ },
433
+ dataSectionHeader: {
434
+ flexDirection: 'row',
435
+ justifyContent: 'space-between',
436
+ alignItems: 'center',
437
+ marginBottom: 8,
438
+ },
439
+ dataSectionTitle: {
440
+ fontSize: 14,
441
+ fontWeight: 'bold',
442
+ color: '#666',
443
+ },
444
+ dataContentWrapper: {
445
+ flex: 1,
446
+ },
447
+ dataContent: {
448
+ flex: 1,
449
+ backgroundColor: '#f8f9fa',
450
+ padding: 10,
451
+ borderRadius: 4,
452
+ borderWidth: 1,
453
+ borderColor: '#e9ecef',
454
+ },
455
+ copyButton: {
456
+ backgroundColor: '#e9ecef',
457
+ paddingHorizontal: 10,
458
+ paddingVertical: 5,
459
+ borderRadius: 4,
460
+ },
461
+ copyButtonText: {
462
+ fontSize: 12,
463
+ color: '#666',
464
+ },
465
+ jsonString: {
466
+ color: '#CB772F',
467
+ },
468
+ jsonNumber: {
469
+ color: '#2878D0',
470
+ },
471
+ jsonBoolean: {
472
+ color: '#2878D0',
473
+ fontWeight: 'bold',
474
+ },
475
+ jsonNull: {
476
+ color: '#A0A0A0',
477
+ fontStyle: 'italic',
478
+ },
479
+ })
480
+
481
+ export default TrackLogDetails
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-debug-toolkit",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "A simple yet powerful debugging toolkit for React Native with a convenient floating UI for development",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",