1
1
import * as React from 'react' ;
2
2
import { postConstruct , injectable , inject } from 'inversify' ;
3
3
import { OptionsType } from 'react-select/src/types' ;
4
- import { isOSX } from '@theia/core/lib/common/os' ;
5
- import { Event , Emitter } from '@theia/core/lib/common/event' ;
6
- import { Key , KeyCode } from '@theia/core/lib/browser/keys' ;
7
- import {
8
- DisposableCollection ,
9
- Disposable ,
10
- } from '@theia/core/lib/common/disposable' ;
4
+ import { Emitter } from '@theia/core/lib/common/event' ;
5
+ import { Disposable } from '@theia/core/lib/common/disposable' ;
11
6
import {
12
7
ReactWidget ,
13
8
Message ,
14
9
Widget ,
15
10
MessageLoop ,
16
11
} from '@theia/core/lib/browser/widgets' ;
17
- import { Board , Port } from '../../common/protocol/boards-service' ;
18
12
import { MonitorConfig } from '../../common/protocol/monitor-service' ;
19
13
import { ArduinoSelect } from '../widgets/arduino-select' ;
20
14
import { MonitorModel } from './monitor-model' ;
21
15
import { MonitorConnection } from './monitor-connection' ;
22
- import { FixedSizeList as List } from 'react-window' ;
23
- import AutoSizer from 'react-virtualized-auto-sizer' ;
24
- import dateFormat = require( 'dateformat' ) ;
16
+ import { SerialMonitorSendInput } from './serial-monitor-send-input' ;
17
+ import { SerialMonitorOutput } from './serial-monitor-send-output' ;
25
18
26
19
@injectable ( )
27
20
export class MonitorWidget extends ReactWidget {
@@ -120,7 +113,9 @@ export class MonitorWidget extends ReactWidget {
120
113
) ;
121
114
} ;
122
115
123
- protected get lineEndings ( ) : OptionsType < SelectOption < MonitorModel . EOL > > {
116
+ protected get lineEndings ( ) : OptionsType <
117
+ SerialMonitorOutput . SelectOption < MonitorModel . EOL >
118
+ > {
124
119
return [
125
120
{
126
121
label : 'No Line Ending' ,
@@ -141,7 +136,9 @@ export class MonitorWidget extends ReactWidget {
141
136
] ;
142
137
}
143
138
144
- protected get baudRates ( ) : OptionsType < SelectOption < MonitorConfig . BaudRate > > {
139
+ protected get baudRates ( ) : OptionsType <
140
+ SerialMonitorOutput . SelectOption < MonitorConfig . BaudRate >
141
+ > {
145
142
const baudRates : Array < MonitorConfig . BaudRate > = [
146
143
300 , 1200 , 2400 , 4800 , 9600 , 19200 , 38400 , 57600 , 115200 ,
147
144
] ;
@@ -206,283 +203,14 @@ export class MonitorWidget extends ReactWidget {
206
203
}
207
204
208
205
protected readonly onChangeLineEnding = (
209
- option : SelectOption < MonitorModel . EOL >
206
+ option : SerialMonitorOutput . SelectOption < MonitorModel . EOL >
210
207
) => {
211
208
this . monitorModel . lineEnding = option . value ;
212
209
} ;
213
210
214
211
protected readonly onChangeBaudRate = (
215
- option : SelectOption < MonitorConfig . BaudRate >
212
+ option : SerialMonitorOutput . SelectOption < MonitorConfig . BaudRate >
216
213
) => {
217
214
this . monitorModel . baudRate = option . value ;
218
215
} ;
219
216
}
220
-
221
- export namespace SerialMonitorSendInput {
222
- export interface Props {
223
- readonly monitorConfig ?: MonitorConfig ;
224
- readonly onSend : ( text : string ) => void ;
225
- readonly resolveFocus : ( element : HTMLElement | undefined ) => void ;
226
- }
227
- export interface State {
228
- text : string ;
229
- }
230
- }
231
-
232
- export class SerialMonitorSendInput extends React . Component <
233
- SerialMonitorSendInput . Props ,
234
- SerialMonitorSendInput . State
235
- > {
236
- constructor ( props : Readonly < SerialMonitorSendInput . Props > ) {
237
- super ( props ) ;
238
- this . state = { text : '' } ;
239
- this . onChange = this . onChange . bind ( this ) ;
240
- this . onSend = this . onSend . bind ( this ) ;
241
- this . onKeyDown = this . onKeyDown . bind ( this ) ;
242
- }
243
-
244
- render ( ) : React . ReactNode {
245
- return (
246
- < input
247
- ref = { this . setRef }
248
- type = "text"
249
- className = { `theia-input ${ this . props . monitorConfig ? '' : 'warning' } ` }
250
- placeholder = { this . placeholder }
251
- value = { this . state . text }
252
- onChange = { this . onChange }
253
- onKeyDown = { this . onKeyDown }
254
- />
255
- ) ;
256
- }
257
-
258
- protected get placeholder ( ) : string {
259
- const { monitorConfig } = this . props ;
260
- if ( ! monitorConfig ) {
261
- return 'Not connected. Select a board and a port to connect automatically.' ;
262
- }
263
- const { board, port } = monitorConfig ;
264
- return `Message (${
265
- isOSX ? '⌘' : 'Ctrl'
266
- } +Enter to send message to '${ Board . toString ( board , {
267
- useFqbn : false ,
268
- } ) } ' on '${ Port . toString ( port ) } ')`;
269
- }
270
-
271
- protected setRef = ( element : HTMLElement | null ) => {
272
- if ( this . props . resolveFocus ) {
273
- this . props . resolveFocus ( element || undefined ) ;
274
- }
275
- } ;
276
-
277
- protected onChange ( event : React . ChangeEvent < HTMLInputElement > ) : void {
278
- this . setState ( { text : event . target . value } ) ;
279
- }
280
-
281
- protected onSend ( ) : void {
282
- this . props . onSend ( this . state . text ) ;
283
- this . setState ( { text : '' } ) ;
284
- }
285
-
286
- protected onKeyDown ( event : React . KeyboardEvent < HTMLInputElement > ) : void {
287
- const keyCode = KeyCode . createKeyCode ( event . nativeEvent ) ;
288
- if ( keyCode ) {
289
- const { key, meta, ctrl } = keyCode ;
290
- if ( key === Key . ENTER && ( ( isOSX && meta ) || ( ! isOSX && ctrl ) ) ) {
291
- this . onSend ( ) ;
292
- }
293
- }
294
- }
295
- }
296
-
297
- export type Line = { message : string ; timestamp ?: Date } ;
298
-
299
- export class SerialMonitorOutput extends React . Component <
300
- SerialMonitorOutput . Props ,
301
- SerialMonitorOutput . State
302
- > {
303
- /**
304
- * Do not touch it. It is used to be able to "follow" the serial monitor log.
305
- */
306
- protected anchor : HTMLElement | null ;
307
- protected toDisposeBeforeUnmount = new DisposableCollection ( ) ;
308
-
309
- constructor ( props : Readonly < SerialMonitorOutput . Props > ) {
310
- super ( props ) ;
311
- this . state = {
312
- lines : [ ] ,
313
- timestamp : this . props . monitorModel . timestamp ,
314
- charCount : 0 ,
315
- } ;
316
- }
317
-
318
- render ( ) : React . ReactNode {
319
- return (
320
- < React . Fragment >
321
- < AutoSizer >
322
- { ( { height, width } ) => (
323
- < List
324
- className = "List"
325
- height = { height }
326
- itemData = {
327
- {
328
- lines : this . state . lines ,
329
- timestamp : this . state . timestamp ,
330
- } as any
331
- }
332
- itemCount = { this . state . lines . length }
333
- itemSize = { 20 }
334
- width = { width }
335
- >
336
- { Row }
337
- </ List >
338
- ) }
339
- </ AutoSizer >
340
- { /* <div style={{ whiteSpace: 'pre', fontFamily: 'monospace' }}>
341
- {this.state.lines.map((line, i) => (
342
- <MonitorTextLine text={line} key={i} />
343
- ))}
344
- </div> */ }
345
- < div
346
- style = { { float : 'left' , clear : 'both' } }
347
- ref = { ( element ) => {
348
- this . anchor = element ;
349
- } }
350
- />
351
- </ React . Fragment >
352
- ) ;
353
- }
354
-
355
- shouldComponentUpdate ( ) : boolean {
356
- return true ;
357
- }
358
-
359
- componentDidMount ( ) : void {
360
- this . scrollToBottom ( ) ;
361
- this . toDisposeBeforeUnmount . pushAll ( [
362
- this . props . monitorConnection . onRead ( ( { messages } ) => {
363
- const [ newLines , charsToAddCount ] = messageToLines (
364
- messages ,
365
- this . state . lines
366
- ) ;
367
- const [ lines , charCount ] = truncateLines (
368
- newLines ,
369
- this . state . charCount + charsToAddCount
370
- ) ;
371
-
372
- this . setState ( {
373
- lines,
374
- charCount,
375
- } ) ;
376
- } ) ,
377
- this . props . clearConsoleEvent ( ( ) => this . setState ( { lines : [ ] } ) ) ,
378
- this . props . monitorModel . onChange ( ( { property } ) => {
379
- if ( property === 'timestamp' ) {
380
- const { timestamp } = this . props . monitorModel ;
381
- this . setState ( { timestamp } ) ;
382
- }
383
- } ) ,
384
- ] ) ;
385
- }
386
-
387
- componentDidUpdate ( ) : void {
388
- this . scrollToBottom ( ) ;
389
- }
390
-
391
- componentWillUnmount ( ) : void {
392
- // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
393
- this . toDisposeBeforeUnmount . dispose ( ) ;
394
- }
395
-
396
- protected scrollToBottom ( ) : void {
397
- if ( this . props . monitorModel . autoscroll && this . anchor ) {
398
- this . anchor . scrollIntoView ( ) ;
399
- // this.listRef.current.scrollToItem(this.state.lines.length);
400
- }
401
- }
402
- }
403
-
404
- const Row = ( {
405
- index,
406
- style,
407
- data,
408
- } : {
409
- index : number ;
410
- style : any ;
411
- data : { lines : Line [ ] ; timestamp : boolean } ;
412
- } ) => {
413
- const timestamp =
414
- ( data . timestamp &&
415
- `${ dateFormat ( data . lines [ index ] . timestamp , 'H:M:ss.l' ) } -> ` ) ||
416
- '' ;
417
- return (
418
- < div style = { style } >
419
- { timestamp }
420
- { data . lines [ index ] . message }
421
- </ div >
422
- ) ;
423
- } ;
424
-
425
- export interface SelectOption < T > {
426
- readonly label : string ;
427
- readonly value : T ;
428
- }
429
-
430
- export namespace SerialMonitorOutput {
431
- export interface Props {
432
- readonly monitorModel : MonitorModel ;
433
- readonly monitorConnection : MonitorConnection ;
434
- readonly clearConsoleEvent : Event < void > ;
435
- }
436
-
437
- export interface State {
438
- lines : Line [ ] ;
439
- timestamp : boolean ;
440
- charCount : number ;
441
- }
442
-
443
- export const MAX_CHARACTERS = 1_000_000 ;
444
- }
445
-
446
- function messageToLines (
447
- messages : string [ ] ,
448
- prevLines : Line [ ] ,
449
- separator = '\n'
450
- ) : [ Line [ ] , number ] {
451
- const linesToAdd : Line [ ] = prevLines . length
452
- ? [ prevLines [ prevLines . length - 1 ] ]
453
- : [ { message : '' } ] ;
454
- let charCount = 0 ;
455
-
456
- for ( const message of messages ) {
457
- charCount += message . length ;
458
- const lastLine = linesToAdd [ linesToAdd . length - 1 ] ;
459
-
460
- if ( lastLine . message . charAt ( lastLine . message . length - 1 ) === separator ) {
461
- linesToAdd . push ( { message, timestamp : new Date ( ) } ) ;
462
- } else {
463
- linesToAdd [ linesToAdd . length - 1 ] . message += message ;
464
- if ( ! linesToAdd [ linesToAdd . length - 1 ] . timestamp ) {
465
- linesToAdd [ linesToAdd . length - 1 ] . timestamp = new Date ( ) ;
466
- }
467
- }
468
- }
469
-
470
- prevLines . splice ( prevLines . length - 1 , 1 , ...linesToAdd ) ;
471
- return [ prevLines , charCount ] ;
472
- }
473
-
474
- function truncateLines ( lines : Line [ ] , charCount : number ) : [ Line [ ] , number ] {
475
- let charsToDelete = charCount - SerialMonitorOutput . MAX_CHARACTERS ;
476
- while ( charsToDelete > 0 ) {
477
- const firstLineLength = lines [ 0 ] ?. message ?. length ;
478
- const newFirstLine = lines [ 0 ] ?. message ?. substring ( charsToDelete ) ;
479
- const deletedCharsCount = firstLineLength - newFirstLine . length ;
480
- charCount -= deletedCharsCount ;
481
- charsToDelete -= deletedCharsCount ;
482
- lines [ 0 ] . message = newFirstLine ;
483
- if ( ! newFirstLine ?. length ) {
484
- lines . shift ( ) ;
485
- }
486
- }
487
- return [ lines , charCount ] ;
488
- }
0 commit comments