beautiful-text 0.0.1-security → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of beautiful-text might be problematic. Click here for more details.

@@ -0,0 +1,837 @@
1
+ /*
2
+
3
+ Terminal Kit
4
+
5
+
6
+ Copyright (c) 2009 - 2022 Cédric Ronvel
7
+
8
+
9
+ The MIT License (MIT)
10
+
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+
14
+ of this software and associated documentation files (the "Software"), to deal
15
+
16
+ in the Software without restriction, including without limitation the rights
17
+
18
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
+
20
+ copies of the Software, and to permit persons to whom the Software is
21
+
22
+ furnished to do so, subject to the following conditions:
23
+
24
+
25
+ The above copyright notice and this permission notice shall be included in all
26
+
27
+ copies or substantial portions of the Software.
28
+
29
+
30
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31
+
32
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+
34
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35
+
36
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
+
38
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39
+
40
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
41
+
42
+ SOFTWARE.
43
+
44
+ */
45
+
46
+
47
+ "use strict" ;
48
+
49
+
50
+
51
+
52
+ //var string = require( 'string-kit' ) ;
53
+
54
+
55
+
56
+
57
+ /*
58
+
59
+ progressBar( options )
60
+
61
+ * options `object` of options, all of them are OPTIONAL, where:
62
+
63
+ * width: `number` the total width of the progress bar, default to the max available width
64
+
65
+ * percent: `boolean` if true, it shows the progress in percent alongside with the progress bar
66
+
67
+ * eta: `boolean` if true, it shows the Estimated Time of Arrival alongside with the progress bar
68
+
69
+ * items `number` the number of items, turns the 'item mode' on
70
+
71
+ * title `string` the title of the current progress bar, turns the 'title mode' on
72
+
73
+ * barStyle `function` the style of the progress bar items, default to `term.cyan`
74
+
75
+ * barBracketStyle `function` the style of the progress bar bracket character, default to options.barStyle if given
76
+
77
+ or `term.blue`
78
+
79
+ * percentStyle `function` the style of percent value string, default to `term.yellow`
80
+
81
+ * etaStyle `function` the style of the ETA display, default to `term.bold`
82
+
83
+ * itemStyle `function` the style of the item display, default to `term.dim`
84
+
85
+ * titleStyle `function` the style of the title display, default to `term.bold`
86
+
87
+ * itemSize `number` the size of the item status, default to 33% of width
88
+
89
+ * titleSize `number` the size of the title, default to 33% of width or title.length depending on context
90
+
91
+ * barChar `string` the char used for the bar, default to '='
92
+
93
+ * barHeadChar `string` the char used for the bar, default to '>'
94
+
95
+ * maxRefreshTime `number` the maximum time between two refresh in ms, default to 500ms
96
+
97
+ * minRefreshTime `number` the minimum time between two refresh in ms, default to 100ms
98
+
99
+ * inline `boolean` (default: false) if true it is not locked in place, i.e. it redraws itself on the current line
100
+
101
+ * syncMode `boolean` true if it should work in sync mode
102
+
103
+ * y `integer` if set, display the progressBar on that line y-coord
104
+
105
+ * x `integer` if set and the 'y' option is set, display the progressBar starting on that x-coord
106
+
107
+ */
108
+
109
+ module.exports = function progressBar_( options ) {
110
+
111
+ if ( ! options || typeof options !== 'object' ) { options = {} ; }
112
+
113
+
114
+ var controller = {} , progress , ready = false , pause = false ,
115
+
116
+ maxItems , itemsDone = 0 , itemsStarted = [] , itemFiller ,
117
+
118
+ title , titleFiller ,
119
+
120
+ width , y , startX , endX , oldWidth ,
121
+
122
+ wheel , wheelCounter = 0 , itemRollCounter = 0 ,
123
+
124
+ updateCount = 0 , progressUpdateCount = 0 ,
125
+
126
+ lastUpdateTime , lastRedrawTime ,
127
+
128
+ startingTime , redrawTimer ,
129
+
130
+ etaStartingTime , lastEta , etaFiller ;
131
+
132
+
133
+ etaStartingTime = startingTime = ( new Date() ).getTime() ;
134
+
135
+
136
+ wheel = [ '|' , '/' , '-' , '\\' ] ;
137
+
138
+
139
+ options.syncMode = !! options.syncMode ;
140
+
141
+
142
+ width = options.width || this.width - 1 ;
143
+
144
+
145
+ if ( ! options.barBracketStyle ) {
146
+
147
+ if ( options.barStyle ) { options.barBracketStyle = options.barStyle ; }
148
+
149
+ else { options.barBracketStyle = this.blue ; }
150
+
151
+ }
152
+
153
+
154
+ if ( ! options.barStyle ) { options.barStyle = this.cyan ; }
155
+
156
+ if ( ! options.percentStyle ) { options.percentStyle = this.yellow ; }
157
+
158
+ if ( ! options.etaStyle ) { options.etaStyle = this.bold ; }
159
+
160
+ if ( ! options.itemStyle ) { options.itemStyle = this.dim ; }
161
+
162
+ if ( ! options.titleStyle ) { options.titleStyle = this.bold ; }
163
+
164
+
165
+ if ( ! options.barChar ) { options.barChar = '=' ; }
166
+
167
+ else { options.barChar = options.barChar[ 0 ] ; }
168
+
169
+
170
+ if ( ! options.barHeadChar ) { options.barHeadChar = '>' ; }
171
+
172
+ else { options.barHeadChar = options.barHeadChar[ 0 ] ; }
173
+
174
+
175
+ if ( typeof options.maxRefreshTime !== 'number' ) { options.maxRefreshTime = 500 ; }
176
+
177
+ if ( typeof options.minRefreshTime !== 'number' ) { options.minRefreshTime = 100 ; }
178
+
179
+
180
+ if ( typeof options.items === 'number' ) { maxItems = options.items ; }
181
+
182
+ if ( maxItems && typeof options.itemSize !== 'number' ) { options.itemSize = Math.round( width / 3 ) ; }
183
+
184
+
185
+ itemFiller = ' '.repeat( options.itemSize ) ;
186
+
187
+
188
+
189
+ if ( options.title && typeof options.title === 'string' ) {
190
+
191
+ title = options.title ;
192
+
193
+
194
+ if ( typeof options.titleSize !== 'number' ) {
195
+
196
+ options.titleSize = Math.round( Math.min( options.title.length + 1 , width / 3 ) ) ;
197
+
198
+ }
199
+
200
+ }
201
+
202
+
203
+ titleFiller = ' '.repeat( options.titleSize ) ;
204
+
205
+
206
+
207
+ etaFiller = ' ' ; // 11 chars
208
+
209
+
210
+ // This is a naive ETA for instance...
211
+
212
+ var etaString = updated => {
213
+
214
+ var eta = '' , elapsedTime , elapsedEtaTime , remainingTime ,
215
+
216
+ averageUpdateDelay , averageUpdateProgress , lastUpdateElapsedTime , fakeProgress ;
217
+
218
+
219
+ if ( progress >= 1 ) {
220
+
221
+ eta = ' done' ;
222
+
223
+ }
224
+
225
+ else if ( progress > 0 ) {
226
+
227
+ elapsedTime = ( new Date() ).getTime() - startingTime ;
228
+
229
+ elapsedEtaTime = ( new Date() ).getTime() - etaStartingTime ;
230
+
231
+
232
+ if ( ! updated && progressUpdateCount > 1 ) {
233
+
234
+ lastUpdateElapsedTime = ( new Date() ).getTime() - lastUpdateTime ;
235
+
236
+ averageUpdateDelay = elapsedEtaTime / progressUpdateCount ;
237
+
238
+ averageUpdateProgress = progress / progressUpdateCount ;
239
+
240
+
241
+ //console.log( '\n' , elapsedEtaTime , lastUpdateElapsedTime , averageUpdateDelay , averageUpdateProgress , '\n' ) ;
242
+
243
+
244
+ // Do not update ETA if it's not an update, except if update time is too long
245
+
246
+ if ( lastUpdateElapsedTime < averageUpdateDelay ) {
247
+
248
+ fakeProgress = progress + averageUpdateProgress * lastUpdateElapsedTime / averageUpdateDelay ;
249
+
250
+ }
251
+
252
+ else {
253
+
254
+ fakeProgress = progress + averageUpdateProgress ;
255
+
256
+ }
257
+
258
+
259
+ if ( fakeProgress > 0.99 ) { fakeProgress = 0.99 ; }
260
+
261
+ }
262
+
263
+ else {
264
+
265
+ fakeProgress = progress ;
266
+
267
+ }
268
+
269
+
270
+ remainingTime = elapsedEtaTime * ( ( 1 - fakeProgress ) / fakeProgress ) / 1000 ;
271
+
272
+
273
+ eta = ' in ' ;
274
+
275
+
276
+ if ( remainingTime < 10 ) { eta += Math.round( remainingTime * 10 ) / 10 + 's' ; }
277
+
278
+ else if ( remainingTime < 120 ) { eta += Math.round( remainingTime ) + 's' ; }
279
+
280
+ else if ( remainingTime < 7200 ) { eta += Math.round( remainingTime / 60 ) + 'min' ; }
281
+
282
+ else if ( remainingTime < 172800 ) { eta += Math.round( remainingTime / 3600 ) + 'hours' ; }
283
+
284
+ else if ( remainingTime < 31536000 ) { eta += Math.round( remainingTime / 86400 ) + 'days' ; }
285
+
286
+ else { eta = 'few years' ; }
287
+
288
+ }
289
+
290
+ else {
291
+
292
+ etaStartingTime = ( new Date() ).getTime() ;
293
+
294
+ }
295
+
296
+
297
+ eta = ( eta + etaFiller ).slice( 0 , etaFiller.length ) ;
298
+
299
+ lastEta = eta ;
300
+
301
+
302
+ return eta ;
303
+
304
+ } ;
305
+
306
+
307
+
308
+
309
+ var redraw = updated => {
310
+
311
+ var time , itemIndex , itemName = itemFiller , titleName = titleFiller ,
312
+
313
+ innerBarSize , progressSize , voidSize ,
314
+
315
+ progressBar = '' , voidBar = '' , percent = '' , eta = '' ;
316
+
317
+
318
+ if ( ! ready || pause ) { return ; }
319
+
320
+
321
+ time = ( new Date() ).getTime() ;
322
+
323
+
324
+ // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit)
325
+
326
+ if ( ( ! progress || progress < 1 ) && lastRedrawTime && time < lastRedrawTime + options.minRefreshTime ) {
327
+
328
+ if ( ! options.syncMode ) {
329
+
330
+ if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
331
+
332
+ redrawTimer = setTimeout( redraw.bind( this , updated ) , lastRedrawTime + options.minRefreshTime - time ) ;
333
+
334
+ }
335
+
336
+ return ;
337
+
338
+ }
339
+
340
+
341
+
342
+ this.saveCursor() ;
343
+
344
+
345
+ // If 'y' is null, we are in the blind mode, we haven't get the cursor location
346
+
347
+ if ( y === null ) { this.column( startX ) ; }
348
+
349
+ else { this.moveTo( startX , y ) ; }
350
+
351
+
352
+ //this.noFormat( Math.floor( progress * 100 ) + '%' ) ;
353
+
354
+
355
+ innerBarSize = width - 2 ;
356
+
357
+
358
+ if ( options.percent ) {
359
+
360
+ innerBarSize -= 4 ;
361
+
362
+ percent = ( ' ' + Math.round( ( progress || 0 ) * 100 ) + '%' ).slice( -4 ) ;
363
+
364
+ }
365
+
366
+
367
+ if ( options.eta ) {
368
+
369
+ eta = etaString( updated ) ;
370
+
371
+ innerBarSize -= eta.length ;
372
+
373
+ }
374
+
375
+
376
+ innerBarSize -= options.itemSize || 0 ;
377
+
378
+ if ( maxItems ) {
379
+
380
+ if ( ! itemsStarted.length ) {
381
+
382
+ itemName = '' ;
383
+
384
+ }
385
+
386
+ else if ( itemsStarted.length === 1 ) {
387
+
388
+ itemName = ' ' + itemsStarted[ 0 ] ;
389
+
390
+ }
391
+
392
+ else {
393
+
394
+ itemIndex = ( itemRollCounter ++ ) % itemsStarted.length ;
395
+
396
+ itemName = ' [' + ( itemIndex + 1 ) + '/' + itemsStarted.length + '] ' + itemsStarted[ itemIndex ] ;
397
+
398
+ }
399
+
400
+
401
+ if ( itemName.length > itemFiller.length ) { itemName = itemName.slice( 0 , itemFiller.length - 1 ) + '…' ; }
402
+
403
+ else if ( itemName.length < itemFiller.length ) { itemName = ( itemName + itemFiller ).slice( 0 , itemFiller.length ) ; }
404
+
405
+ }
406
+
407
+
408
+ innerBarSize -= options.titleSize || 0 ;
409
+
410
+ if ( title ) {
411
+
412
+ titleName = title ;
413
+
414
+
415
+ if ( titleName.length >= titleFiller.length ) { titleName = titleName.slice( 0 , titleFiller.length - 2 ) + '… ' ; }
416
+
417
+ else { titleName = ( titleName + titleFiller ).slice( 0 , titleFiller.length ) ; }
418
+
419
+ }
420
+
421
+
422
+ progressSize = progress === undefined ? 1 : Math.round( innerBarSize * Math.max( Math.min( progress , 1 ) , 0 ) ) ;
423
+
424
+ voidSize = innerBarSize - progressSize ;
425
+
426
+
427
+ /*
428
+
429
+ console.log( "Size:" , width ,
430
+
431
+ voidSize , innerBarSize , progressSize , eta.length , title.length , itemName.length ,
432
+
433
+ voidSize + progressSize + eta.length + title.length + itemName.length
434
+
435
+ ) ;
436
+
437
+ //*/
438
+
439
+
440
+ if ( progressSize ) {
441
+
442
+ if ( progress === undefined ) {
443
+
444
+ progressBar = wheel[ ++ wheelCounter % wheel.length ] ;
445
+
446
+ }
447
+
448
+ else {
449
+
450
+ progressBar += options.barChar.repeat( progressSize - 1 ) ;
451
+
452
+ progressBar += options.barHeadChar ;
453
+
454
+ }
455
+
456
+ }
457
+
458
+
459
+ voidBar += ' '.repeat( voidSize ) ;
460
+
461
+
462
+ options.titleStyle( titleName ) ;
463
+
464
+
465
+ if ( percent ) { options.percentStyle( percent ) ; }
466
+
467
+
468
+ if ( progress === undefined ) { this( ' ' ) ; }
469
+
470
+ else { options.barBracketStyle( '[' ) ; }
471
+
472
+
473
+ options.barStyle( progressBar ) ;
474
+
475
+ this( voidBar ) ;
476
+
477
+
478
+ if ( progress === undefined ) { this( ' ' ) ; /*this( '+' ) ;*/ }
479
+
480
+ else { options.barBracketStyle( ']' ) ; }
481
+
482
+
483
+ options.etaStyle( eta ) ;
484
+
485
+ //this( '*' ) ;
486
+
487
+ options.itemStyle( itemName ) ;
488
+
489
+ //this( '&' ) ;
490
+
491
+
492
+ this.restoreCursor() ;
493
+
494
+
495
+ if ( ! options.syncMode ) {
496
+
497
+ if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
498
+
499
+ if ( ! progress || progress < 1 ) { redrawTimer = setTimeout( redraw , options.maxRefreshTime ) ; }
500
+
501
+ }
502
+
503
+
504
+ lastRedrawTime = time ;
505
+
506
+ } ;
507
+
508
+
509
+
510
+ if ( options.syncMode || options.inline || options.y ) {
511
+
512
+ oldWidth = width ;
513
+
514
+
515
+ if ( options.y ) {
516
+
517
+ startX = + options.x || 1 ;
518
+
519
+ y = + options.y || 1 ;
520
+
521
+ }
522
+
523
+ else {
524
+
525
+ startX = 1 ;
526
+
527
+ y = null ;
528
+
529
+ }
530
+
531
+
532
+ endX = Math.min( startX + width , this.width ) ;
533
+
534
+ width = endX - startX ;
535
+
536
+
537
+ if ( width !== oldWidth ) {
538
+
539
+ // Should resize all part here
540
+
541
+ if ( options.titleSize ) { options.titleSize = Math.floor( options.titleSize * width / oldWidth ) ; }
542
+
543
+ if ( options.itemSize ) { options.itemSize = Math.floor( options.itemSize * width / oldWidth ) ; }
544
+
545
+ }
546
+
547
+
548
+ ready = true ;
549
+
550
+ redraw() ;
551
+
552
+ }
553
+
554
+ else {
555
+
556
+ // Get the cursor location before getting started
557
+
558
+ this.getCursorLocation( ( error , x_ , y_ ) => {
559
+
560
+ if ( error ) {
561
+
562
+ // Some bad terminals (windows...) doesn't support cursor location request, we should fallback to a decent behavior.
563
+
564
+ // So we just move to the last line and create a new line.
565
+
566
+ //cleanup( error ) ; return ;
567
+
568
+ this.row.eraseLineAfter( this.height )( '\n' ) ;
569
+
570
+ x_ = 1 ;
571
+
572
+ y_ = this.height ;
573
+
574
+ }
575
+
576
+
577
+ var oldWidth_ = width ;
578
+
579
+
580
+ startX = x_ ;
581
+
582
+ endX = Math.min( x_ + width , this.width ) ;
583
+
584
+ y = y_ ;
585
+
586
+ width = endX - startX ;
587
+
588
+
589
+ if ( width !== oldWidth_ ) {
590
+
591
+ // Should resize all part here
592
+
593
+ if ( options.titleSize ) { options.titleSize = Math.floor( options.titleSize * width / oldWidth_ ) ; }
594
+
595
+ if ( options.itemSize ) { options.itemSize = Math.floor( options.itemSize * width / oldWidth_ ) ; }
596
+
597
+ }
598
+
599
+
600
+ ready = true ;
601
+
602
+ redraw() ;
603
+
604
+ } ) ;
605
+
606
+ }
607
+
608
+
609
+ controller.startItem = name => {
610
+
611
+ itemsStarted.push( name ) ;
612
+
613
+
614
+ // No need to redraw NOW if there are other items running.
615
+
616
+ // Let the timer do the job.
617
+
618
+ if ( itemsStarted.length === 1 ) {
619
+
620
+ // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit)
621
+
622
+ if ( progress >= 1 ) { redraw() ; return ; }
623
+
624
+
625
+ if ( options.syncMode ) {
626
+
627
+ redraw() ;
628
+
629
+ }
630
+
631
+ else {
632
+
633
+ // Using a setTimeout with a 0ms time and redrawTimer clearing has a nice effect:
634
+
635
+ // if multiple synchronous update are performed, redraw will be called once
636
+
637
+ if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
638
+
639
+ redrawTimer = setTimeout( redraw , 0 ) ;
640
+
641
+ }
642
+
643
+ }
644
+
645
+ } ;
646
+
647
+
648
+ controller.itemDone = name => {
649
+
650
+ var index ;
651
+
652
+
653
+ itemsDone ++ ;
654
+
655
+
656
+ if ( maxItems ) { progress = itemsDone / maxItems ; }
657
+
658
+ else { progress = undefined ; }
659
+
660
+
661
+ lastUpdateTime = ( new Date() ).getTime() ;
662
+
663
+ updateCount ++ ;
664
+
665
+ progressUpdateCount ++ ;
666
+
667
+
668
+ index = itemsStarted.indexOf( name ) ;
669
+
670
+ if ( index >= 0 ) { itemsStarted.splice( index , 1 ) ; }
671
+
672
+
673
+ // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit)
674
+
675
+ if ( progress >= 1 ) { redraw( true ) ; return ; }
676
+
677
+
678
+ if ( options.syncMode ) {
679
+
680
+ redraw() ;
681
+
682
+ }
683
+
684
+ else {
685
+
686
+ // Using a setTimeout with a 0ms time and redrawTimer clearing has a nice effect:
687
+
688
+ // if multiple synchronous update are performed, redraw will be called once
689
+
690
+ if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
691
+
692
+ redrawTimer = setTimeout( redraw.bind( this , true ) , 0 ) ;
693
+
694
+ }
695
+
696
+ } ;
697
+
698
+
699
+ controller.update = toUpdate => {
700
+
701
+ if ( ! toUpdate ) { toUpdate = {} ; }
702
+
703
+ else if ( typeof toUpdate === 'number' ) { toUpdate = { progress: toUpdate } ; }
704
+
705
+
706
+ if ( 'progress' in toUpdate ) {
707
+
708
+ if ( typeof toUpdate.progress !== 'number' ) {
709
+
710
+ progress = undefined ;
711
+
712
+ }
713
+
714
+ else {
715
+
716
+ // Not sure if it is a good thing to let the user set progress to a value that is lesser than the current one
717
+
718
+ progress = toUpdate.progress ;
719
+
720
+
721
+ if ( progress > 1 ) { progress = 1 ; }
722
+
723
+ else if ( progress < 0 ) { progress = 0 ; }
724
+
725
+
726
+ if ( progress > 0 ) { progressUpdateCount ++ ; }
727
+
728
+
729
+ lastUpdateTime = ( new Date() ).getTime() ;
730
+
731
+ updateCount ++ ;
732
+
733
+ }
734
+
735
+ }
736
+
737
+
738
+ if ( typeof toUpdate.items === 'number' ) {
739
+
740
+ maxItems = toUpdate.items ;
741
+
742
+ if ( maxItems ) { progress = itemsDone / maxItems ; }
743
+
744
+
745
+ if ( typeof options.itemSize !== 'number' ) {
746
+
747
+ options.itemSize = Math.round( width / 3 ) ;
748
+
749
+ itemFiller = ' '.repeat( options.itemSize ) ;
750
+
751
+ }
752
+
753
+ }
754
+
755
+
756
+ if ( typeof toUpdate.title === 'string' ) {
757
+
758
+ title = toUpdate.title ;
759
+
760
+
761
+ if ( typeof options.titleSize !== 'number' ) {
762
+
763
+ options.titleSize = Math.round( width / 3 ) ;
764
+
765
+ titleFiller = ' '.repeat( options.titleSize ) ;
766
+
767
+ }
768
+
769
+ }
770
+
771
+
772
+ // If progress is >= 1, then it's finished, so we should redraw NOW (before the program eventually quit)
773
+
774
+ if ( progress >= 1 ) { redraw( true ) ; return ; }
775
+
776
+
777
+ if ( options.syncMode ) {
778
+
779
+ redraw() ;
780
+
781
+ }
782
+
783
+ else {
784
+
785
+ // Using a setTimeout with a 0ms time and redrawTimer clearing has a nice effect:
786
+
787
+ // if multiple synchronous update are performed, redraw will be called once
788
+
789
+ if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
790
+
791
+ redrawTimer = setTimeout( redraw.bind( this , true ) , 0 ) ;
792
+
793
+ }
794
+
795
+ } ;
796
+
797
+
798
+ controller.pause = controller.stop = () => {
799
+
800
+ pause = true ;
801
+
802
+ } ;
803
+
804
+
805
+ controller.resume = () => {
806
+
807
+ if ( pause ) {
808
+
809
+ pause = false ;
810
+
811
+ redraw() ;
812
+
813
+ }
814
+
815
+ } ;
816
+
817
+
818
+ controller.reset = () => {
819
+
820
+ etaStartingTime = startingTime = ( new Date() ).getTime() ;
821
+
822
+ itemsDone = 0 ;
823
+
824
+ progress = undefined ;
825
+
826
+ itemsStarted.length = 0 ;
827
+
828
+ wheelCounter = itemRollCounter = updateCount = progressUpdateCount = 0 ;
829
+
830
+ redraw() ;
831
+
832
+ } ;
833
+
834
+
835
+ return controller ;
836
+
837
+ } ;