react-native-debug-toolkit 0.5.2 → 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 +4 -1
- package/lib/DebugToolKit.js +3 -1
- package/lib/features/TrackFeature.js +97 -0
- package/lib/index.js +7 -2
- package/lib/types/TrackTypes.ts +92 -0
- package/lib/views/FloatPanelView.js +6 -0
- package/lib/views/SubViewTrackLogs.js +318 -0
- package/lib/views/TrackLogDetails.js +481 -0
- package/package.json +1 -1
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
|
package/lib/DebugToolKit.js
CHANGED
|
@@ -13,7 +13,9 @@ export default class DebugToolKit {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// Set the debug mode flag directly from NativeDebugLibs
|
|
16
|
-
this.isDebugMode = NativeDebugLibs.isDebugBuild()
|
|
16
|
+
// this.isDebugMode = NativeDebugLibs.isDebugBuild()
|
|
17
|
+
this.isDebugMode = true
|
|
18
|
+
|
|
17
19
|
this.floatPanel = null
|
|
18
20
|
this.features = []
|
|
19
21
|
this.isPanelVisible = false
|
|
@@ -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,10 +22,11 @@ const featureRegistry = {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// List of default features (can use strings now)
|
|
23
|
-
const defaultFeatures = ['network', '
|
|
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
|
-
const isDebugMode = NativeDebugLibs.isDebugBuild();
|
|
28
|
+
// const isDebugMode = NativeDebugLibs.isDebugBuild();
|
|
29
|
+
const isDebugMode = true
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Initializes and shows the Debug ToolKit panel with specified features.
|
|
@@ -103,6 +106,8 @@ export {
|
|
|
103
106
|
createNavigationLogFeature,
|
|
104
107
|
createThirdPartyLibsFeature,
|
|
105
108
|
addNavigationLog,
|
|
109
|
+
createTrackFeature,
|
|
110
|
+
addTrackLog,
|
|
106
111
|
zustandLogMiddleware, // Export middleware for use in Zustand stores
|
|
107
112
|
featureRegistry,
|
|
108
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