213 lines
5.8 KiB
JavaScript
213 lines
5.8 KiB
JavaScript
const _ETA = require('./eta');
|
|
const _Terminal = require('./terminal');
|
|
const _formatter = require('./formatter');
|
|
const _EventEmitter = require('events');
|
|
|
|
// Progress-Bar constructor
|
|
module.exports = class GenericBar extends _EventEmitter{
|
|
|
|
constructor(options){
|
|
super();
|
|
|
|
// store options
|
|
this.options = options;
|
|
|
|
// store terminal instance
|
|
this.terminal = (this.options.terminal) ? this.options.terminal : new _Terminal(this.options.stream);
|
|
|
|
// the current bar value
|
|
this.value = 0;
|
|
|
|
// the end value of the bar
|
|
this.total = 100;
|
|
|
|
// last drawn string - only render on change!
|
|
this.lastDrawnString = null;
|
|
|
|
// start time (used for eta calculation)
|
|
this.startTime = null;
|
|
|
|
// stop time (used for duration calculation)
|
|
this.stopTime = null;
|
|
|
|
// last update time
|
|
this.lastRedraw = Date.now();
|
|
|
|
// default eta calculator (will be re-create on start)
|
|
this.eta = new _ETA(this.options.etaBufferLength, 0, 0);
|
|
|
|
// payload data
|
|
this.payload = {};
|
|
|
|
// progress bar active ?
|
|
this.isActive = false;
|
|
|
|
// use default formatter or custom one ?
|
|
this.formatter = (typeof this.options.format === 'function') ? this.options.format : _formatter;
|
|
}
|
|
|
|
// internal render function
|
|
render(){
|
|
// calculate the normalized current progress
|
|
let progress = (this.value/this.total);
|
|
|
|
// handle NaN Errors caused by total=0. Set to complete in this case
|
|
if (isNaN(progress)){
|
|
progress = (this.options && this.options.emptyOnZero) ? 0.0 : 1.0;
|
|
}
|
|
|
|
// limiter
|
|
progress = Math.min(Math.max(progress, 0.0), 1.0);
|
|
|
|
// formatter params
|
|
const params = {
|
|
progress: progress,
|
|
eta: this.eta.getTime(),
|
|
startTime: this.startTime,
|
|
stopTime: this.stopTime,
|
|
total: this.total,
|
|
value: this.value,
|
|
maxWidth: this.terminal.getWidth()
|
|
};
|
|
|
|
// automatic eta update ? (long running processes)
|
|
if (this.options.etaAsynchronousUpdate){
|
|
this.updateETA();
|
|
}
|
|
|
|
// format string
|
|
const s = this.formatter(this.options, params, this.payload);
|
|
|
|
const forceRedraw = this.options.forceRedraw
|
|
// force redraw in notty-mode!
|
|
|| (this.options.noTTYOutput && !this.terminal.isTTY());
|
|
|
|
// string changed ? only trigger redraw on change!
|
|
if (forceRedraw || this.lastDrawnString != s){
|
|
// trigger event
|
|
this.emit('redraw-pre');
|
|
|
|
// set cursor to start of line
|
|
this.terminal.cursorTo(0, null);
|
|
|
|
// write output
|
|
this.terminal.write(s);
|
|
|
|
// clear to the right from cursor
|
|
this.terminal.clearRight();
|
|
|
|
// store string
|
|
this.lastDrawnString = s;
|
|
|
|
// set last redraw time
|
|
this.lastRedraw = Date.now();
|
|
|
|
// trigger event
|
|
this.emit('redraw-post');
|
|
}
|
|
}
|
|
|
|
// start the progress bar
|
|
start(total, startValue, payload){
|
|
// set initial values
|
|
this.value = startValue || 0;
|
|
this.total = (typeof total !== 'undefined' && total >= 0) ? total : 100;
|
|
|
|
// store payload (optional)
|
|
this.payload = payload || {};
|
|
|
|
// store start time for duration+eta calculation
|
|
this.startTime = Date.now();
|
|
|
|
// reset string line buffer (redraw detection)
|
|
this.lastDrawnString = '';
|
|
|
|
// initialize eta buffer
|
|
this.eta = new _ETA(this.options.etaBufferLength, this.startTime, this.value);
|
|
|
|
// set flag
|
|
this.isActive = true;
|
|
|
|
// start event
|
|
this.emit('start', total, startValue);
|
|
}
|
|
|
|
// stop the bar
|
|
stop(){
|
|
// set flag
|
|
this.isActive = false;
|
|
|
|
// store stop timestamp to get total duration
|
|
this.stopTime = new Date();
|
|
|
|
// stop event
|
|
this.emit('stop', this.total, this.value);
|
|
}
|
|
|
|
// update the bar value
|
|
// update(value, payload)
|
|
// update(payload)
|
|
update(arg0, arg1 = {}){
|
|
// value set ?
|
|
// update(value, [payload]);
|
|
if (typeof arg0 === 'number') {
|
|
// update value
|
|
this.value = arg0;
|
|
|
|
// add new value; recalculate eta
|
|
this.eta.update(Date.now(), arg0, this.total);
|
|
}
|
|
|
|
// extract payload
|
|
// update(value, payload)
|
|
// update(payload)
|
|
const payloadData = ((typeof arg0 === 'object') ? arg0 : arg1) || {};
|
|
|
|
// update event (before stop() is called)
|
|
this.emit('update', this.total, this.value);
|
|
|
|
// merge payload
|
|
for (const key in payloadData){
|
|
this.payload[key] = payloadData[key];
|
|
}
|
|
|
|
// limit reached ? autostop set ?
|
|
if (this.value >= this.getTotal() && this.options.stopOnComplete) {
|
|
this.stop();
|
|
}
|
|
}
|
|
|
|
// update the bar value
|
|
// increment(delta, payload)
|
|
// increment(payload)
|
|
increment(arg0 = 1, arg1 = {}){
|
|
// increment([payload]) => step=1
|
|
// handle the use case when `step` is omitted but payload is passed
|
|
if (typeof arg0 === 'object') {
|
|
this.update(this.value + 1, arg0);
|
|
|
|
// increment([step=1], [payload={}])
|
|
}else{
|
|
this.update(this.value + arg0, arg1);
|
|
}
|
|
}
|
|
|
|
// get the total (limit) value
|
|
getTotal(){
|
|
return this.total;
|
|
}
|
|
|
|
// set the total (limit) value
|
|
setTotal(total){
|
|
if (typeof total !== 'undefined' && total >= 0){
|
|
this.total = total;
|
|
}
|
|
}
|
|
|
|
// force eta calculation update (long running processes)
|
|
updateETA(){
|
|
// add new value; recalculate eta
|
|
this.eta.update(Date.now(), this.value, this.total);
|
|
}
|
|
}
|