19
19
import React , { Component } from 'react' ;
20
20
21
21
import List , { ListItem } from 'material-ui/List' ;
22
+ import escapeHtml from 'escape-html' ;
22
23
import type { Record , Content , LogsMessage , Logs as LogsType } from '../types/content' ;
23
24
24
25
// requestBand says how wide is the top/bottom zone, eg. 0.1 means 10% of the container height.
@@ -83,8 +84,8 @@ const createChunk = (records: Array<Record>) => {
83
84
content += `<span style="color:${ color } ">${ lvl } </span>[${ month } -${ date } |${ hours } :${ minutes } :${ seconds } ] ${ msg } ` ;
84
85
85
86
for ( let i = 0 ; i < ctx . length ; i += 2 ) {
86
- const key = ctx [ i ] ;
87
- const val = ctx [ i + 1 ] ;
87
+ const key = escapeHtml ( ctx [ i ] ) ;
88
+ const val = escapeHtml ( ctx [ i + 1 ] ) ;
88
89
let padding = fieldPadding . get ( key ) ;
89
90
if ( typeof padding !== 'number' || padding < val . length ) {
90
91
padding = val . length ;
@@ -101,11 +102,17 @@ const createChunk = (records: Array<Record>) => {
101
102
return content ;
102
103
} ;
103
104
105
+ // ADDED, SAME and REMOVED are used to track the change of the log chunk array.
106
+ // The scroll position is set using these values.
107
+ const ADDED = 1 ;
108
+ const SAME = 0 ;
109
+ const REMOVED = - 1 ;
110
+
104
111
// inserter is a state updater function for the main component, which inserts the new log chunk into the chunk array.
105
112
// limit is the maximum length of the chunk array, used in order to prevent the browser from OOM.
106
113
export const inserter = ( limit : number ) => ( update : LogsMessage , prev : LogsType ) => {
107
- prev . topChanged = 0 ;
108
- prev . bottomChanged = 0 ;
114
+ prev . topChanged = SAME ;
115
+ prev . bottomChanged = SAME ;
109
116
if ( ! Array . isArray ( update . chunk ) || update . chunk . length < 1 ) {
110
117
return prev ;
111
118
}
@@ -123,7 +130,7 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
123
130
return [ { content, name : '00000000000000.log' } ] ;
124
131
}
125
132
prev . chunks [ prev . chunks . length - 1 ] . content += content ;
126
- prev . bottomChanged = 1 ;
133
+ prev . bottomChanged = ADDED ;
127
134
return prev ;
128
135
}
129
136
const chunk = {
@@ -137,10 +144,10 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
137
144
if ( prev . chunks . length >= limit ) {
138
145
prev . endBottom = false ;
139
146
prev . chunks . splice ( limit - 1 , prev . chunks . length - limit + 1 ) ;
140
- prev . bottomChanged = - 1 ;
147
+ prev . bottomChanged = REMOVED ;
141
148
}
142
149
prev . chunks = [ chunk , ...prev . chunks ] ;
143
- prev . topChanged = 1 ;
150
+ prev . topChanged = ADDED ;
144
151
return prev ;
145
152
}
146
153
if ( update . source . last ) {
@@ -149,24 +156,30 @@ export const inserter = (limit: number) => (update: LogsMessage, prev: LogsType)
149
156
if ( prev . chunks . length >= limit ) {
150
157
prev . endTop = false ;
151
158
prev . chunks . splice ( 0 , prev . chunks . length - limit + 1 ) ;
152
- prev . topChanged = - 1 ;
159
+ prev . topChanged = REMOVED ;
153
160
}
154
161
prev . chunks = [ ...prev . chunks , chunk ] ;
155
- prev . bottomChanged = 1 ;
162
+ prev . bottomChanged = ADDED ;
156
163
return prev ;
157
164
} ;
158
165
159
166
// styles contains the constant styles of the component.
160
167
const styles = {
161
168
logListItem : {
162
169
padding : 0 ,
170
+ lineHeight : 1.231 ,
163
171
} ,
164
172
logChunk : {
165
173
color : 'white' ,
166
174
fontFamily : 'monospace' ,
167
175
whiteSpace : 'nowrap' ,
168
176
width : 0 ,
169
177
} ,
178
+ waitMsg : {
179
+ textAlign : 'center' ,
180
+ color : 'white' ,
181
+ fontFamily : 'monospace' ,
182
+ } ,
170
183
} ;
171
184
172
185
export type Props = {
@@ -192,7 +205,17 @@ class Logs extends Component<Props, State> {
192
205
193
206
componentDidMount ( ) {
194
207
const { container} = this . props ;
208
+ if ( typeof container === 'undefined' ) {
209
+ return ;
210
+ }
195
211
container . scrollTop = container . scrollHeight - container . clientHeight ;
212
+ const { logs} = this . props . content ;
213
+ if ( typeof this . content === 'undefined' || logs . chunks . length < 1 ) {
214
+ return ;
215
+ }
216
+ if ( this . content . clientHeight < container . clientHeight && ! logs . endTop ) {
217
+ this . sendRequest ( logs . chunks [ 0 ] . name , true ) ;
218
+ }
196
219
}
197
220
198
221
// onScroll is triggered by the parent component's scroll event, and sends requests if the scroll position is
@@ -205,29 +228,23 @@ class Logs extends Component<Props, State> {
205
228
if ( logs . chunks . length < 1 ) {
206
229
return ;
207
230
}
208
- if ( this . atTop ( ) ) {
209
- if ( ! logs . endTop ) {
210
- this . setState ( { requestAllowed : false } ) ;
211
- this . props . send ( JSON . stringify ( {
212
- Logs : {
213
- Name : logs . chunks [ 0 ] . name ,
214
- Past : true ,
215
- } ,
216
- } ) ) ;
217
- }
218
- } else if ( this . atBottom ( ) ) {
219
- if ( ! logs . endBottom ) {
220
- this . setState ( { requestAllowed : false } ) ;
221
- this . props . send ( JSON . stringify ( {
222
- Logs : {
223
- Name : logs . chunks [ logs . chunks . length - 1 ] . name ,
224
- Past : false ,
225
- } ,
226
- } ) ) ;
227
- }
231
+ if ( this . atTop ( ) && ! logs . endTop ) {
232
+ this . sendRequest ( logs . chunks [ 0 ] . name , true ) ;
233
+ } else if ( this . atBottom ( ) && ! logs . endBottom ) {
234
+ this . sendRequest ( logs . chunks [ logs . chunks . length - 1 ] . name , false ) ;
228
235
}
229
236
} ;
230
237
238
+ sendRequest = ( name : string , past : boolean ) = > {
239
+ this . setState ( { requestAllowed : false } ) ;
240
+ this . props . send ( JSON . stringify ( {
241
+ Logs : {
242
+ Name : name ,
243
+ Past : past ,
244
+ } ,
245
+ } ) ) ;
246
+ } ;
247
+
231
248
// atTop checks if the scroll position it at the top of the container.
232
249
atTop = ( ) => this . props . container . scrollTop <= this . props . container . scrollHeight * requestBand ;
233
250
@@ -242,8 +259,9 @@ class Logs extends Component<Props, State> {
242
259
// and the height of the first log chunk, which can be deleted during the insertion.
243
260
beforeUpdate = ( ) => {
244
261
let firstHeight = 0 ;
245
- if ( this . content && this . content . children [ 0 ] && this . content . children [ 0 ] . children [ 0 ] ) {
246
- firstHeight = this . content . children [ 0 ] . children [ 0 ] . clientHeight ;
262
+ let chunkList = this . content . children [ 1 ] ;
263
+ if ( chunkList && chunkList . children [ 0 ] ) {
264
+ firstHeight = chunkList . children [ 0 ] . clientHeight ;
247
265
}
248
266
return {
249
267
scrollTop : this . props . container . scrollTop ,
@@ -252,8 +270,8 @@ class Logs extends Component<Props, State> {
252
270
} ;
253
271
254
272
// didUpdate is called by the parent component, which provides the container. Sends the first request if the
255
- // visible part of the container isn't full, and resets the scroll position in order to avoid jumping when new
256
- // chunk is inserted.
273
+ // visible part of the container isn't full, and resets the scroll position in order to avoid jumping when a
274
+ // chunk is inserted or removed .
257
275
didUpdate = ( prevProps , prevState , snapshot ) => {
258
276
if ( typeof this . props . shouldUpdate . logs === 'undefined' || typeof this . content === 'undefined' || snapshot === null ) {
259
277
return ;
@@ -264,27 +282,21 @@ class Logs extends Component<Props, State> {
264
282
return ;
265
283
}
266
284
if ( this . content . clientHeight < container . clientHeight ) {
267
- // Only enters here at the beginning, when there isn 't enough log to fill the container
285
+ // Only enters here at the beginning, when there aren 't enough logs to fill the container
268
286
// and the scroll bar doesn't appear.
269
287
if ( ! logs . endTop ) {
270
- this . setState ( { requestAllowed : false } ) ;
271
- this . props . send ( JSON . stringify ( {
272
- Logs : {
273
- Name : logs . chunks [ 0 ] . name ,
274
- Past : true ,
275
- } ,
276
- } ) ) ;
288
+ this . sendRequest ( logs . chunks [ 0 ] . name , true ) ;
277
289
}
278
290
return ;
279
291
}
280
- const chunks = this . content . children [ 0 ] . children ;
281
292
let { scrollTop} = snapshot ;
282
- if ( logs . topChanged > 0 ) {
283
- scrollTop += chunks [ 0 ] . clientHeight ;
284
- } else if ( logs . bottomChanged > 0 ) {
285
- if ( logs . topChanged < 0 ) {
293
+ if ( logs . topChanged === ADDED ) {
294
+ // It would be safer to use a ref to the list, but ref doesn't work well with HOCs.
295
+ scrollTop += this . content . children [ 1 ] . children [ 0 ] . clientHeight ;
296
+ } else if ( logs . bottomChanged === ADDED ) {
297
+ if ( logs . topChanged === REMOVED ) {
286
298
scrollTop -= snapshot . firstHeight ;
287
- } else if ( logs . endBottom && this . atBottom ( ) ) {
299
+ } else if ( this . atBottom ( ) && logs . endBottom ) {
288
300
scrollTop = container . scrollHeight - container . clientHeight ;
289
301
}
290
302
}
@@ -295,13 +307,17 @@ class Logs extends Component<Props, State> {
295
307
render ( ) {
296
308
return (
297
309
< div ref = { ( ref ) => { this . content = ref ; } } >
310
+ < div style = { styles . waitMsg } >
311
+ { this . props . content . logs . endTop ? 'No more logs.' : 'Waiting for server...' }
312
+ </ div >
298
313
< List >
299
314
{ this . props . content . logs . chunks . map ( ( c , index ) => (
300
315
< ListItem style = { styles . logListItem } key = { index } >
301
316
< div style = { styles . logChunk } dangerouslySetInnerHTML = { { __html : c . content } } />
302
317
</ ListItem >
303
318
) ) }
304
319
</ List >
320
+ { this . props . content . logs . endBottom || < div style = { styles . waitMsg } > Waiting for server...</ div > }
305
321
</ div >
306
322
) ;
307
323
}
0 commit comments