@@ -21,11 +21,25 @@ const SystemLogs = memo(({ serviceName }: { serviceName?: ServiceNames }) => {
21
21
const containerRef = useRef < HTMLDivElement > ( null ) ;
22
22
const showScrollDownButton = useBoolState ( false ) ;
23
23
const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
24
+ const [ debouncedSearchQuery , setDebouncedSearchQuery ] = useState ( '' ) ;
24
25
const [ currentMatch , setCurrentMatch ] = useState ( 0 ) ;
25
26
const [ totalMatches , setTotalMatches ] = useState ( 0 ) ;
26
27
const [ highlightedElements , setHighlightedElements ] = useState < HTMLElement [ ] > ( [ ] ) ;
27
28
const currentHighlightRef = useRef < HTMLElement | null > ( null ) ;
28
29
const isInitialLoad = useRef ( true ) ;
30
+ const isSearchingRef = useRef ( false ) ;
31
+ const currentMatchRef = useRef ( 0 ) ;
32
+ const totalMatchesRef = useRef ( 0 ) ;
33
+ const highlightedElementsRef = useRef < HTMLElement [ ] > ( [ ] ) ;
34
+ const isNavigatingRef = useRef ( false ) ;
35
+
36
+ // Debounce search query updates
37
+ useEffect ( ( ) => {
38
+ const timer = setTimeout ( ( ) => {
39
+ setDebouncedSearchQuery ( searchQuery ) ;
40
+ } , 300 ) ;
41
+ return ( ) => clearTimeout ( timer ) ;
42
+ } , [ searchQuery ] ) ;
29
43
30
44
const scrollToBottom = useCallback ( ( ) => {
31
45
if ( containerRef . current ) {
@@ -53,80 +67,94 @@ const SystemLogs = memo(({ serviceName }: { serviceName?: ServiceNames }) => {
53
67
54
68
const handleSearch = useCallback ( ( query : string ) => {
55
69
setSearchQuery ( query ) ;
70
+ isSearchingRef . current = ! ! query ;
56
71
57
- // Reset states only when query is empty or below a threshold
58
72
if ( ! query || query . length < 3 ) {
59
73
setCurrentMatch ( 0 ) ;
74
+ currentMatchRef . current = 0 ;
60
75
setTotalMatches ( 0 ) ;
76
+ totalMatchesRef . current = 0 ;
61
77
setHighlightedElements ( [ ] ) ;
78
+ highlightedElementsRef . current = [ ] ;
62
79
updateHighlight ( null ) ;
63
80
return ;
64
81
}
65
-
66
- // Let the effect handle DOM updates
67
82
} , [ updateHighlight ] ) ;
68
83
69
84
useLayoutEffect ( ( ) => {
70
- if ( ! searchQuery || searchQuery . length < 3 ) return ;
85
+ if ( ! debouncedSearchQuery || debouncedSearchQuery . length < 3 ) return ;
71
86
72
87
const elements = Array . from (
73
88
containerRef . current ?. querySelectorAll ( 'span[style*="background-color: yellow"]' ) ?? [ ]
74
89
) as HTMLElement [ ] ;
75
90
76
91
setHighlightedElements ( elements ) ;
77
- setTotalMatches ( elements . length ) ;
78
- setCurrentMatch ( elements . length ? 1 : 0 ) ;
79
-
80
- if ( elements . length ) {
81
- updateHighlight ( elements [ 0 ] ) ;
82
- elements [ 0 ] . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
92
+ highlightedElementsRef . current = elements ;
93
+ const newTotalMatches = elements . length ;
94
+ setTotalMatches ( newTotalMatches ) ;
95
+ totalMatchesRef . current = newTotalMatches ;
96
+
97
+ if ( currentMatchRef . current === 0 ) {
98
+ setCurrentMatch ( newTotalMatches ? 1 : 0 ) ;
99
+ currentMatchRef . current = newTotalMatches ? 1 : 0 ;
83
100
}
84
- } , [ searchQuery , logs , updateHighlight ] ) ;
85
-
101
+ } , [ debouncedSearchQuery ] ) ;
86
102
87
103
const handleNavigate = useCallback ( ( direction : 'prev' | 'next' ) => {
88
- if ( highlightedElements . length === 0 ) return ;
89
-
90
- let newIndex = currentMatch ;
91
- if ( direction === 'next' ) {
92
- newIndex = currentMatch < totalMatches ? currentMatch + 1 : 1 ;
93
- } else {
94
- newIndex = currentMatch > 1 ? currentMatch - 1 : totalMatches ;
95
- }
104
+ if ( highlightedElementsRef . current . length === 0 || isNavigatingRef . current ) return ;
105
+
106
+ isNavigatingRef . current = true ;
107
+ requestAnimationFrame ( ( ) => {
108
+ let newIndex = currentMatchRef . current ;
109
+ if ( direction === 'next' ) {
110
+ newIndex = currentMatchRef . current < totalMatchesRef . current ? currentMatchRef . current + 1 : 1 ;
111
+ } else {
112
+ newIndex = currentMatchRef . current > 1 ? currentMatchRef . current - 1 : totalMatchesRef . current ;
113
+ }
96
114
97
- setCurrentMatch ( newIndex ) ;
98
- const element = highlightedElements [ newIndex - 1 ] ;
99
- if ( element ) {
100
- element . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
101
- updateHighlight ( element ) ;
102
- }
103
- } , [ currentMatch , totalMatches , highlightedElements , updateHighlight ] ) ;
115
+ setCurrentMatch ( newIndex ) ;
116
+ currentMatchRef . current = newIndex ;
117
+ const element = highlightedElementsRef . current [ newIndex - 1 ] ;
118
+ if ( element ) {
119
+ element . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
120
+ updateHighlight ( element ) ;
121
+ }
122
+ isNavigatingRef . current = false ;
123
+ } ) ;
124
+ } , [ updateHighlight ] ) ;
104
125
105
126
useEffect ( ( ) => {
106
127
if ( ! loading && logs . length && containerRef . current && isInitialLoad . current ) {
107
128
isInitialLoad . current = false ;
108
129
requestAnimationFrame ( ( ) => {
109
- containerRef . current ?. scrollTo ( {
110
- top : containerRef . current . scrollHeight ,
111
- behavior : 'auto'
112
- } ) ;
130
+ if ( containerRef . current ) {
131
+ containerRef . current . scrollTo ( {
132
+ top : containerRef . current . scrollHeight ,
133
+ behavior : 'auto'
134
+ } ) ;
135
+ }
113
136
} ) ;
114
137
}
115
138
} , [ loading , logs ] ) ;
116
139
117
140
const renderLogs = useCallback ( ( ) => {
118
141
return logs . map ( ( log , index ) => {
119
142
const parsedLog = parseLogLine ( log ) ;
120
- if ( ! parsedLog ) {
121
- return < PlainLog log = { log } index = { index } key = { index } searchQuery = { searchQuery } /> ;
122
- }
123
- return < FormattedLog log = { parsedLog } index = { index } key = { index } searchQuery = { searchQuery } /> ;
143
+ return (
144
+ < div key = { index } >
145
+ { ! parsedLog ? (
146
+ < PlainLog log = { log } index = { index } searchQuery = { debouncedSearchQuery } />
147
+ ) : (
148
+ < FormattedLog log = { parsedLog } index = { index } searchQuery = { debouncedSearchQuery } />
149
+ ) }
150
+ </ div >
151
+ ) ;
124
152
} ) ;
125
- } , [ logs , searchQuery ] ) ;
153
+ } , [ logs , debouncedSearchQuery ] ) ;
126
154
127
155
return (
128
156
< ErrorBoundary
129
- FallbackComponent = { ( { error } ) => (
157
+ FallbackComponent = { ( { error } : { error : Error } ) => (
130
158
< SystemLogsErrorFallback error = { error } serviceName = { serviceName } />
131
159
) }
132
160
>
@@ -174,5 +202,4 @@ const SystemLogs = memo(({ serviceName }: { serviceName?: ServiceNames }) => {
174
202
) ;
175
203
} ) ;
176
204
177
-
178
205
export { SystemLogs } ;
0 commit comments