11import React , { useEffect , useRef , useState } from 'react' ;
2- import { ScrollView , Text , View , Button } from 'react-native' ;
2+ import { ScrollView , Text , View , Button , StyleSheet } from 'react-native' ;
33import { ConsoleView } from 'react-native-console-view' ;
44
5+ // Log level types and styling properties
6+ type LogLevel = 'log' | 'error' | 'warn' ;
7+
8+ // Emoji indicators for each log level
9+ const emojiForLevel : Record < LogLevel , string > = {
10+ log : '🟢' ,
11+ error : '🔴' ,
12+ warn : '🟡' ,
13+ } ;
14+
15+ // Colors for each log level
16+ const colorForLevel : Record < LogLevel , string > = {
17+ log : '#2ecc40' , // Green
18+ error : '#e74c3c' , // Red
19+ warn : '#f1c40f' , // Yellow
20+ } ;
21+
22+ interface LogEntry {
23+ level : LogLevel ;
24+ message : string ;
25+ timestamp : string ;
26+ }
27+
528const LogViewer = ( ) => {
6- const [ logs , setLogs ] = useState < string [ ] > ( [ ] ) ;
29+ const [ logs , setLogs ] = useState < LogEntry [ ] > ( [ ] ) ;
30+ const scrollViewRef = useRef < ScrollView > ( null ) ;
31+
32+ // Flag to prevent recursive logging
33+ const isLoggingRef = useRef ( false ) ;
734
8- const flushingRef = useRef ( false ) ;
935 useEffect ( ( ) => {
1036 const originalLog = console . log ;
1137 const originalError = console . error ;
1238 const originalWarn = console . warn ;
1339
14- const enqueue = ( level : 'log' | 'error' | 'warn' , args : any [ ] ) => {
15- // Defer state update to microtask to avoid setState during another component's render
16- Promise . resolve ( ) . then ( ( ) => {
17- setLogs ( ( prev ) => [ ...prev , `[${ level . toUpperCase ( ) } ] ${ args . map ( String ) . join ( ' ' ) } ` ] ) ;
18- } ) ;
40+ const enqueue = ( level : LogLevel , args : any [ ] ) => {
41+ // Prevent recursive logging
42+ if ( isLoggingRef . current ) {
43+ return ;
44+ }
45+
46+ try {
47+ isLoggingRef . current = true ;
48+
49+ // Format timestamp for better readability
50+ const now = new Date ( ) ;
51+ const timestamp = `${ now . getHours ( ) . toString ( ) . padStart ( 2 , '0' ) } :${ now . getMinutes ( ) . toString ( ) . padStart ( 2 , '0' ) } :${ now
52+ . getSeconds ( )
53+ . toString ( )
54+ . padStart ( 2 , '0' ) } .${ now . getMilliseconds ( ) . toString ( ) . padStart ( 3 , '0' ) } `;
55+
56+ // Safely convert args to strings
57+ let messageStr = '' ;
58+ try {
59+ messageStr = args
60+ . map ( ( arg ) => {
61+ if ( typeof arg === 'string' ) return arg ;
62+ if ( arg instanceof Error ) return arg . toString ( ) ;
63+ try {
64+ return JSON . stringify ( arg ) ;
65+ } catch ( e ) {
66+ return String ( arg ) ;
67+ }
68+ } )
69+ . join ( ' ' ) ;
70+ } catch ( err ) {
71+ messageStr = 'Error stringifying log message' ;
72+ }
73+
74+ // Defer state update to microtask to avoid setState during another component's render
75+ Promise . resolve ( ) . then ( ( ) => {
76+ setLogs ( ( prev ) => {
77+ // Add new log to the beginning (newest first)
78+ const newLogs = [
79+ {
80+ level,
81+ message : messageStr ,
82+ timestamp,
83+ } ,
84+ ...prev ,
85+ ] ;
86+
87+ // Keep only the latest 100 logs to prevent memory issues
88+ return newLogs . slice ( 0 , 100 ) ;
89+ } ) ;
90+ } ) ;
91+ } finally {
92+ isLoggingRef . current = false ;
93+ }
1994 } ;
2095
2196 console . log = ( ...args ) => {
22- enqueue ( 'log' , args ) ;
2397 originalLog ( ...args ) ;
98+ enqueue ( 'log' , args ) ;
2499 } ;
25100 console . error = ( ...args ) => {
26- enqueue ( 'error' , args ) ;
27101 originalError ( ...args ) ;
102+ enqueue ( 'error' , args ) ;
28103 } ;
29104 console . warn = ( ...args ) => {
30- enqueue ( 'warn' , args ) ;
31105 originalWarn ( ...args ) ;
106+ enqueue ( 'warn' , args ) ;
32107 } ;
33108
34109 return ( ) => {
@@ -38,29 +113,84 @@ const LogViewer = () => {
38113 } ;
39114 } , [ ] ) ;
40115
116+ // We don't need auto-scroll since newest logs are at the top
117+ useEffect ( ( ) => {
118+ if ( scrollViewRef . current && logs . length > 0 ) {
119+ scrollViewRef . current ?. scrollTo ( { y : 0 , animated : true } ) ;
120+ }
121+ } , [ logs . length ] ) ;
122+
41123 return (
42- < View
43- style = { {
44- flex : 1 ,
45- padding : 10 ,
46- backgroundColor : 'black' ,
47- borderRadius : 15 , // Rounded corners
48- borderWidth : 2 , // Border thickness
49- borderColor : 'white' , // White border
50- margin : 10 , // Margin for better visibility
51- } }
52- >
124+ < View style = { styles . container } >
53125 < ConsoleView enabled = { true } breakpoint = { 'mobile' } />
54- < ScrollView style = { { marginTop : 10 } } >
126+ < ScrollView
127+ ref = { scrollViewRef }
128+ style = { styles . scrollView }
129+ contentContainerStyle = { styles . scrollContent }
130+ showsVerticalScrollIndicator = { true }
131+ nestedScrollEnabled = { true }
132+ scrollEnabled = { true }
133+ >
55134 { logs . map ( ( log , index ) => (
56- < Text key = { index } style = { { color : 'white' , fontSize : 12 } } >
57- { log }
58- </ Text >
135+ < View key = { index } style = { styles . logItem } >
136+ < Text style = { [ styles . logHeader , { color : colorForLevel [ log . level ] } ] } >
137+ { emojiForLevel [ log . level ] } [{ log . timestamp } ] [{ log . level . toUpperCase ( ) } ]
138+ </ Text >
139+ < Text style = { styles . logMessage } > { log . message } </ Text >
140+ { index < logs . length - 1 && < View style = { styles . separator } /> }
141+ </ View >
59142 ) ) }
60143 </ ScrollView >
61- < Button title = "Clear Logs" onPress = { ( ) => setLogs ( [ ] ) } />
144+ < View style = { styles . buttonContainer } >
145+ < Button title = "Clear Logs" color = "#3498db" onPress = { ( ) => setLogs ( [ ] ) } />
146+ </ View >
62147 </ View >
63148 ) ;
64149} ;
65150
151+ const styles = StyleSheet . create ( {
152+ container : {
153+ padding : 10 ,
154+ backgroundColor : '#121212' ,
155+ borderRadius : 15 ,
156+ borderWidth : 2 ,
157+ borderColor : '#3498db' ,
158+ margin : 10 ,
159+ height : 400 , // Fixed height instead of flex: 1
160+ overflow : 'hidden' , // Prevent content from overflowing
161+ } ,
162+ scrollView : {
163+ marginTop : 10 ,
164+ height : 300 , // Fixed height to ensure scrollability
165+ flexGrow : 0 , // Prevent expanding
166+ } ,
167+ scrollContent : {
168+ paddingBottom : 10 ,
169+ } ,
170+ logItem : {
171+ marginBottom : 8 ,
172+ paddingHorizontal : 8 ,
173+ paddingVertical : 4 ,
174+ } ,
175+ logHeader : {
176+ fontSize : 13 ,
177+ fontWeight : 'bold' ,
178+ marginBottom : 4 ,
179+ } ,
180+ logMessage : {
181+ color : 'white' ,
182+ fontSize : 12 ,
183+ marginLeft : 8 ,
184+ } ,
185+ separator : {
186+ borderBottomWidth : 1 ,
187+ borderBottomColor : '#444' ,
188+ marginTop : 8 ,
189+ marginBottom : 8 ,
190+ } ,
191+ buttonContainer : {
192+ marginTop : 10 ,
193+ } ,
194+ } ) ;
195+
66196export default LogViewer ;
0 commit comments