1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | "use strict" ;
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | module.exports = function progressBar_( options ) {
|
62 | if ( ! options || typeof options !== 'object' ) { options = {} ; }
|
63 |
|
64 | var controller = {} , progress , ready = false , pause = false ,
|
65 | maxItems , itemsDone = 0 , itemsStarted = [] , itemFiller ,
|
66 | title , titleFiller ,
|
67 | width , y , startX , endX , oldWidth ,
|
68 | wheel , wheelCounter = 0 , itemRollCounter = 0 ,
|
69 | updateCount = 0 , progressUpdateCount = 0 ,
|
70 | lastUpdateTime , lastRedrawTime ,
|
71 | startingTime , redrawTimer ,
|
72 | etaStartingTime , lastEta , etaFiller ;
|
73 |
|
74 | etaStartingTime = startingTime = ( new Date() ).getTime() ;
|
75 |
|
76 | wheel = [ '|' , '/' , '-' , '\\' ] ;
|
77 |
|
78 | options.syncMode = !! options.syncMode ;
|
79 |
|
80 | width = options.width || this.width - 1 ;
|
81 |
|
82 | if ( ! options.barBracketStyle ) {
|
83 | if ( options.barStyle ) { options.barBracketStyle = options.barStyle ; }
|
84 | else { options.barBracketStyle = this.blue ; }
|
85 | }
|
86 |
|
87 | if ( ! options.barStyle ) { options.barStyle = this.cyan ; }
|
88 | if ( ! options.percentStyle ) { options.percentStyle = this.yellow ; }
|
89 | if ( ! options.etaStyle ) { options.etaStyle = this.bold ; }
|
90 | if ( ! options.itemStyle ) { options.itemStyle = this.dim ; }
|
91 | if ( ! options.titleStyle ) { options.titleStyle = this.bold ; }
|
92 |
|
93 | if ( ! options.barChar ) { options.barChar = '=' ; }
|
94 | else { options.barChar = options.barChar[ 0 ] ; }
|
95 |
|
96 | if ( ! options.barHeadChar ) { options.barHeadChar = '>' ; }
|
97 | else { options.barHeadChar = options.barHeadChar[ 0 ] ; }
|
98 |
|
99 | if ( typeof options.maxRefreshTime !== 'number' ) { options.maxRefreshTime = 500 ; }
|
100 | if ( typeof options.minRefreshTime !== 'number' ) { options.minRefreshTime = 100 ; }
|
101 |
|
102 | if ( typeof options.items === 'number' ) { maxItems = options.items ; }
|
103 | if ( maxItems && typeof options.itemSize !== 'number' ) { options.itemSize = Math.round( width / 3 ) ; }
|
104 |
|
105 | itemFiller = ' '.repeat( options.itemSize ) ;
|
106 |
|
107 |
|
108 | if ( options.title && typeof options.title === 'string' ) {
|
109 | title = options.title ;
|
110 |
|
111 | if ( typeof options.titleSize !== 'number' ) {
|
112 | options.titleSize = Math.round( Math.min( options.title.length + 1 , width / 3 ) ) ;
|
113 | }
|
114 | }
|
115 |
|
116 | titleFiller = ' '.repeat( options.titleSize ) ;
|
117 |
|
118 |
|
119 | etaFiller = ' ' ;
|
120 |
|
121 |
|
122 | var etaString = updated => {
|
123 | var eta = '' , elapsedTime , elapsedEtaTime , remainingTime ,
|
124 | averageUpdateDelay , averageUpdateProgress , lastUpdateElapsedTime , fakeProgress ;
|
125 |
|
126 | if ( progress >= 1 ) {
|
127 | eta = ' done' ;
|
128 | }
|
129 | else if ( progress > 0 ) {
|
130 | elapsedTime = ( new Date() ).getTime() - startingTime ;
|
131 | elapsedEtaTime = ( new Date() ).getTime() - etaStartingTime ;
|
132 |
|
133 | if ( ! updated && progressUpdateCount > 1 ) {
|
134 | lastUpdateElapsedTime = ( new Date() ).getTime() - lastUpdateTime ;
|
135 | averageUpdateDelay = elapsedEtaTime / progressUpdateCount ;
|
136 | averageUpdateProgress = progress / progressUpdateCount ;
|
137 |
|
138 |
|
139 |
|
140 |
|
141 | if ( lastUpdateElapsedTime < averageUpdateDelay ) {
|
142 | fakeProgress = progress + averageUpdateProgress * lastUpdateElapsedTime / averageUpdateDelay ;
|
143 | }
|
144 | else {
|
145 | fakeProgress = progress + averageUpdateProgress ;
|
146 | }
|
147 |
|
148 | if ( fakeProgress > 0.99 ) { fakeProgress = 0.99 ; }
|
149 | }
|
150 | else {
|
151 | fakeProgress = progress ;
|
152 | }
|
153 |
|
154 | remainingTime = elapsedEtaTime * ( ( 1 - fakeProgress ) / fakeProgress ) / 1000 ;
|
155 |
|
156 | eta = ' in ' ;
|
157 |
|
158 | if ( remainingTime < 10 ) { eta += Math.round( remainingTime * 10 ) / 10 + 's' ; }
|
159 | else if ( remainingTime < 120 ) { eta += Math.round( remainingTime ) + 's' ; }
|
160 | else if ( remainingTime < 7200 ) { eta += Math.round( remainingTime / 60 ) + 'min' ; }
|
161 | else if ( remainingTime < 172800 ) { eta += Math.round( remainingTime / 3600 ) + 'hours' ; }
|
162 | else if ( remainingTime < 31536000 ) { eta += Math.round( remainingTime / 86400 ) + 'days' ; }
|
163 | else { eta = 'few years' ; }
|
164 | }
|
165 | else {
|
166 | etaStartingTime = ( new Date() ).getTime() ;
|
167 | }
|
168 |
|
169 | eta = ( eta + etaFiller ).slice( 0 , etaFiller.length ) ;
|
170 | lastEta = eta ;
|
171 |
|
172 | return eta ;
|
173 | } ;
|
174 |
|
175 |
|
176 |
|
177 | var redraw = updated => {
|
178 | var time , itemIndex , itemName = itemFiller , titleName = titleFiller ,
|
179 | innerBarSize , progressSize , voidSize ,
|
180 | progressBar = '' , voidBar = '' , percent = '' , eta = '' ;
|
181 |
|
182 | if ( ! ready || pause ) { return ; }
|
183 |
|
184 | time = ( new Date() ).getTime() ;
|
185 |
|
186 |
|
187 | if ( ( ! progress || progress < 1 ) && lastRedrawTime && time < lastRedrawTime + options.minRefreshTime ) {
|
188 | if ( ! options.syncMode ) {
|
189 | if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
|
190 | redrawTimer = setTimeout( redraw.bind( this , updated ) , lastRedrawTime + options.minRefreshTime - time ) ;
|
191 | }
|
192 | return ;
|
193 | }
|
194 |
|
195 |
|
196 | this.saveCursor() ;
|
197 |
|
198 |
|
199 | if ( y === null ) { this.column( startX ) ; }
|
200 | else { this.moveTo( startX , y ) ; }
|
201 |
|
202 |
|
203 |
|
204 | innerBarSize = width - 2 ;
|
205 |
|
206 | if ( options.percent ) {
|
207 | innerBarSize -= 4 ;
|
208 | percent = ( ' ' + Math.round( ( progress || 0 ) * 100 ) + '%' ).slice( -4 ) ;
|
209 | }
|
210 |
|
211 | if ( options.eta ) {
|
212 | eta = etaString( updated ) ;
|
213 | innerBarSize -= eta.length ;
|
214 | }
|
215 |
|
216 | innerBarSize -= options.itemSize || 0 ;
|
217 | if ( maxItems ) {
|
218 | if ( ! itemsStarted.length ) {
|
219 | itemName = '' ;
|
220 | }
|
221 | else if ( itemsStarted.length === 1 ) {
|
222 | itemName = ' ' + itemsStarted[ 0 ] ;
|
223 | }
|
224 | else {
|
225 | itemIndex = ( itemRollCounter ++ ) % itemsStarted.length ;
|
226 | itemName = ' [' + ( itemIndex + 1 ) + '/' + itemsStarted.length + '] ' + itemsStarted[ itemIndex ] ;
|
227 | }
|
228 |
|
229 | if ( itemName.length > itemFiller.length ) { itemName = itemName.slice( 0 , itemFiller.length - 1 ) + '…' ; }
|
230 | else if ( itemName.length < itemFiller.length ) { itemName = ( itemName + itemFiller ).slice( 0 , itemFiller.length ) ; }
|
231 | }
|
232 |
|
233 | innerBarSize -= options.titleSize || 0 ;
|
234 | if ( title ) {
|
235 | titleName = title ;
|
236 |
|
237 | if ( titleName.length >= titleFiller.length ) { titleName = titleName.slice( 0 , titleFiller.length - 2 ) + '… ' ; }
|
238 | else { titleName = ( titleName + titleFiller ).slice( 0 , titleFiller.length ) ; }
|
239 | }
|
240 |
|
241 | progressSize = progress === undefined ? 1 : Math.round( innerBarSize * Math.max( Math.min( progress , 1 ) , 0 ) ) ;
|
242 | voidSize = innerBarSize - progressSize ;
|
243 |
|
244 | |
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 | if ( progressSize ) {
|
252 | if ( progress === undefined ) {
|
253 | progressBar = wheel[ ++ wheelCounter % wheel.length ] ;
|
254 | }
|
255 | else {
|
256 | progressBar += options.barChar.repeat( progressSize - 1 ) ;
|
257 | progressBar += options.barHeadChar ;
|
258 | }
|
259 | }
|
260 |
|
261 | voidBar += ' '.repeat( voidSize ) ;
|
262 |
|
263 | options.titleStyle( titleName ) ;
|
264 |
|
265 | if ( percent ) { options.percentStyle( percent ) ; }
|
266 |
|
267 | if ( progress === undefined ) { this( ' ' ) ; }
|
268 | else { options.barBracketStyle( '[' ) ; }
|
269 |
|
270 | options.barStyle( progressBar ) ;
|
271 | this( voidBar ) ;
|
272 |
|
273 | if ( progress === undefined ) { this( ' ' ) ; }
|
274 | else { options.barBracketStyle( ']' ) ; }
|
275 |
|
276 | options.etaStyle( eta ) ;
|
277 |
|
278 | options.itemStyle( itemName ) ;
|
279 |
|
280 |
|
281 | this.restoreCursor() ;
|
282 |
|
283 | if ( ! options.syncMode ) {
|
284 | if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
|
285 | if ( ! progress || progress < 1 ) { redrawTimer = setTimeout( redraw , options.maxRefreshTime ) ; }
|
286 | }
|
287 |
|
288 | lastRedrawTime = time ;
|
289 | } ;
|
290 |
|
291 |
|
292 | if ( options.syncMode || options.inline || options.y ) {
|
293 | oldWidth = width ;
|
294 |
|
295 | if ( options.y ) {
|
296 | startX = + options.x || 1 ;
|
297 | y = + options.y || 1 ;
|
298 | }
|
299 | else {
|
300 | startX = 1 ;
|
301 | y = null ;
|
302 | }
|
303 |
|
304 | endX = Math.min( startX + width , this.width ) ;
|
305 | width = endX - startX ;
|
306 |
|
307 | if ( width !== oldWidth ) {
|
308 |
|
309 | if ( options.titleSize ) { options.titleSize = Math.floor( options.titleSize * width / oldWidth ) ; }
|
310 | if ( options.itemSize ) { options.itemSize = Math.floor( options.itemSize * width / oldWidth ) ; }
|
311 | }
|
312 |
|
313 | ready = true ;
|
314 | redraw() ;
|
315 | }
|
316 | else {
|
317 |
|
318 | this.getCursorLocation( ( error , x_ , y_ ) => {
|
319 | if ( error ) {
|
320 |
|
321 |
|
322 |
|
323 | this.row.eraseLineAfter( this.height )( '\n' ) ;
|
324 | x_ = 1 ;
|
325 | y_ = this.height ;
|
326 | }
|
327 |
|
328 | var oldWidth_ = width ;
|
329 |
|
330 | startX = x_ ;
|
331 | endX = Math.min( x_ + width , this.width ) ;
|
332 | y = y_ ;
|
333 | width = endX - startX ;
|
334 |
|
335 | if ( width !== oldWidth_ ) {
|
336 |
|
337 | if ( options.titleSize ) { options.titleSize = Math.floor( options.titleSize * width / oldWidth_ ) ; }
|
338 | if ( options.itemSize ) { options.itemSize = Math.floor( options.itemSize * width / oldWidth_ ) ; }
|
339 | }
|
340 |
|
341 | ready = true ;
|
342 | redraw() ;
|
343 | } ) ;
|
344 | }
|
345 |
|
346 | controller.startItem = name => {
|
347 | itemsStarted.push( name ) ;
|
348 |
|
349 |
|
350 |
|
351 | if ( itemsStarted.length === 1 ) {
|
352 |
|
353 | if ( progress >= 1 ) { redraw() ; return ; }
|
354 |
|
355 | if ( options.syncMode ) {
|
356 | redraw() ;
|
357 | }
|
358 | else {
|
359 |
|
360 |
|
361 | if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
|
362 | redrawTimer = setTimeout( redraw , 0 ) ;
|
363 | }
|
364 | }
|
365 | } ;
|
366 |
|
367 | controller.itemDone = name => {
|
368 | var index ;
|
369 |
|
370 | itemsDone ++ ;
|
371 |
|
372 | if ( maxItems ) { progress = itemsDone / maxItems ; }
|
373 | else { progress = undefined ; }
|
374 |
|
375 | lastUpdateTime = ( new Date() ).getTime() ;
|
376 | updateCount ++ ;
|
377 | progressUpdateCount ++ ;
|
378 |
|
379 | index = itemsStarted.indexOf( name ) ;
|
380 | if ( index >= 0 ) { itemsStarted.splice( index , 1 ) ; }
|
381 |
|
382 |
|
383 | if ( progress >= 1 ) { redraw( true ) ; return ; }
|
384 |
|
385 | if ( options.syncMode ) {
|
386 | redraw() ;
|
387 | }
|
388 | else {
|
389 |
|
390 |
|
391 | if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
|
392 | redrawTimer = setTimeout( redraw.bind( this , true ) , 0 ) ;
|
393 | }
|
394 | } ;
|
395 |
|
396 | controller.update = toUpdate => {
|
397 | if ( ! toUpdate ) { toUpdate = {} ; }
|
398 | else if ( typeof toUpdate === 'number' ) { toUpdate = { progress: toUpdate } ; }
|
399 |
|
400 | if ( 'progress' in toUpdate ) {
|
401 | if ( typeof toUpdate.progress !== 'number' ) {
|
402 | progress = undefined ;
|
403 | }
|
404 | else {
|
405 |
|
406 | progress = toUpdate.progress ;
|
407 |
|
408 | if ( progress > 1 ) { progress = 1 ; }
|
409 | else if ( progress < 0 ) { progress = 0 ; }
|
410 |
|
411 | if ( progress > 0 ) { progressUpdateCount ++ ; }
|
412 |
|
413 | lastUpdateTime = ( new Date() ).getTime() ;
|
414 | updateCount ++ ;
|
415 | }
|
416 | }
|
417 |
|
418 | if ( typeof toUpdate.items === 'number' ) {
|
419 | maxItems = toUpdate.items ;
|
420 | if ( maxItems ) { progress = itemsDone / maxItems ; }
|
421 |
|
422 | if ( typeof options.itemSize !== 'number' ) {
|
423 | options.itemSize = Math.round( width / 3 ) ;
|
424 | itemFiller = ' '.repeat( options.itemSize ) ;
|
425 | }
|
426 | }
|
427 |
|
428 | if ( typeof toUpdate.title === 'string' ) {
|
429 | title = toUpdate.title ;
|
430 |
|
431 | if ( typeof options.titleSize !== 'number' ) {
|
432 | options.titleSize = Math.round( width / 3 ) ;
|
433 | titleFiller = ' '.repeat( options.titleSize ) ;
|
434 | }
|
435 | }
|
436 |
|
437 |
|
438 | if ( progress >= 1 ) { redraw( true ) ; return ; }
|
439 |
|
440 | if ( options.syncMode ) {
|
441 | redraw() ;
|
442 | }
|
443 | else {
|
444 |
|
445 |
|
446 | if ( redrawTimer ) { clearTimeout( redrawTimer ) ; }
|
447 | redrawTimer = setTimeout( redraw.bind( this , true ) , 0 ) ;
|
448 | }
|
449 | } ;
|
450 |
|
451 | controller.pause = controller.stop = () => {
|
452 | pause = true ;
|
453 | } ;
|
454 |
|
455 | controller.resume = () => {
|
456 | if ( pause ) {
|
457 | pause = false ;
|
458 | redraw() ;
|
459 | }
|
460 | } ;
|
461 |
|
462 | controller.reset = () => {
|
463 | etaStartingTime = startingTime = ( new Date() ).getTime() ;
|
464 | itemsDone = 0 ;
|
465 | progress = undefined ;
|
466 | itemsStarted.length = 0 ;
|
467 | wheelCounter = itemRollCounter = updateCount = progressUpdateCount = 0 ;
|
468 | redraw() ;
|
469 | } ;
|
470 |
|
471 | return controller ;
|
472 | } ;
|
473 |
|