diff --git a/.gitignore b/.gitignore index a8ac8ca..576d32d 100755 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ node_modules* .tmp* intern_* dev.js -yarn-error.log \ No newline at end of file +yarn-error.log +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index e78147a..a3aedc3 100755 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Features * Preset/Theme support * Custom bar formatters (via callback) * Logging during multibar operation +* Concurrent updatable progress information below progressbar Usage ------------ @@ -160,6 +161,28 @@ Note: you may want to increase `etaBuffer` size - otherwise it can cause `INF` e .updateETA(); ``` +### ::setConcurrentInfo() + +Sets the text that is written below the progress bar that can be updated while the process is continuing. + +For example, the text could track which file is currently being downloaded during a multi-file synchronous download process. + +```js +const currentFile = filenames[i]; +.setConcurrentInfo(` --> Downloading ${currentFile}...`); +``` + +Concurrent Info example: + +![](assets/concurrent-info.png) + +### ::clearCurrentInfo() + +Clears any concurrent text written below the progress bar. + +```js +.clearConcurrentInfo(); +``` Multi Bar Mode ----------------------------------- diff --git a/assets/concurrent-info.png b/assets/concurrent-info.png new file mode 100644 index 0000000..7fdb90a Binary files /dev/null and b/assets/concurrent-info.png differ diff --git a/examples/example-concurrent-info.js b/examples/example-concurrent-info.js new file mode 100644 index 0000000..8c6b0df --- /dev/null +++ b/examples/example-concurrent-info.js @@ -0,0 +1,36 @@ +const cli_progress = require('../cli-progress'); + +// Generate random file names using random hexadecimal names +const random_file_names = Array(10).fill(0).map(() => + Math.floor(Math.random() * Math.pow(16, 6)).toString(16) + '.txt' +); + +const bar = new cli_progress.Bar({ + format: 'Files [{bar}] | {percentage}% | {value}/{total}', + hideCursor: true, + barCompleteChar: '\u2588', + barIncompleteChar: '\u2591', + stopOnComplete: true, + + // If clear on complete then both bar and info are cleared + // clearOnComplete: true, + + // forceRedraw: true +}); + +console.log('Downloading files..'); +bar.start(200, 0); + +const timer = setInterval(() => { + if (bar.value < bar.total) { + bar.increment(); + + const current_file = random_file_names[Math.floor(bar.value / 20)]; + bar.setConcurrentInfo(` --> Downloading ${current_file}...`); + } + + if (!bar.isActive) { + clearInterval(timer); + console.log('Download complete!\n'); + } +}, 15); diff --git a/lib/generic-bar.js b/lib/generic-bar.js index 2e5d11e..a5d412e 100755 --- a/lib/generic-bar.js +++ b/lib/generic-bar.js @@ -48,6 +48,25 @@ module.exports = class GenericBar extends _EventEmitter{ // use default formatter or custom one ? this.formatter = (typeof this.options.format === 'function') ? this.options.format : _formatter; + + /** + * Whether the progress bar should be able to show concurrent info or not. + * E.g. In multibars, each bar should not be able to have concurrent text + * @type {boolean} + */ + this.allowConcurrentInfo = this.options.allowConcurrentInfo; + + /** + * The information to show below progress bar + * @type {string | null} + */ + this.concurrentInfo = null; + + /** + * The concurrent information has changed and should be rerendered next render + * @type {boolean} + */ + this.concurrentInfoUpdated = false; } // internal render function @@ -77,7 +96,7 @@ module.exports = class GenericBar extends _EventEmitter{ || (this.options.noTTYOutput && !this.terminal.isTTY()); // string changed ? only trigger redraw on change! - if (forceRedraw || this.lastDrawnString != s){ + if (forceRedraw || this.lastDrawnString != s || this.concurrentInfoUpdated) { // trigger event this.emit('redraw-pre'); @@ -93,6 +112,22 @@ module.exports = class GenericBar extends _EventEmitter{ // store string this.lastDrawnString = s; + // check if concurrent text needs to be rendered + if (this.concurrentInfo !== null) { + // reset cursor to start of next line + this.terminal.cursorRelative(-s.length, 1); + + // write concurrent info text + this.terminal.write(this.concurrentInfo); + this.terminal.clearRight(); + + // reset cursor to previous line + this.terminal.cursorRelative(0, -1); + + // concurrent info is now up to date + this.concurrentInfoUpdated = false; + } + // set last redraw time this.lastRedraw = Date.now(); @@ -231,4 +266,30 @@ module.exports = class GenericBar extends _EventEmitter{ // add new value; recalculate eta this.eta.update(Date.now(), this.value, this.total); } + + /** + * Sets the information that is given as additional concurrent information + * below the progress bar. It is cleared once the bar completes. + * @param {string} text The info shown below the progress bar + */ + setConcurrentInfo(text) { + if (!this.allowConcurrentInfo) { + return; + } + + this.concurrentInfoUpdated = this.concurrentInfo !== text; + this.concurrentInfo = text; + } + + /** + * Clears the additional concurrent information. + */ + clearConcurrentInfo() { + if (!this.allowConcurrentInfo) { + return; + } + + this.concurrentInfoUpdated = this.concurrentInfo !== ''; + this.concurrentInfo = ''; + } } diff --git a/lib/multi-bar.js b/lib/multi-bar.js index d40ccfc..248bce8 100644 --- a/lib/multi-bar.js +++ b/lib/multi-bar.js @@ -47,9 +47,12 @@ module.exports = class MultiBar extends _EventEmitter{ // global options this.options, - // terminal instance { - terminal: this.terminal + // terminal instance + terminal: this.terminal, + + // concurrent text in multibars is not defined behaviour + allowConcurrentInfo: false }, // overrides diff --git a/lib/options.js b/lib/options.js index 4671d4a..4038a42 100644 --- a/lib/options.js +++ b/lib/options.js @@ -105,6 +105,8 @@ module.exports = { // autopadding character - empty in case autopadding is disabled options.autopaddingChar = options.autopadding ? mergeOption(options.autopaddingChar, ' ') : ''; + options.allowConcurrentInfo = 'allowConcurrentInfo' in options ? options.allowConcurrentInfo : true; + return options; } }; \ No newline at end of file diff --git a/lib/single-bar.js b/lib/single-bar.js index e8235e4..85cc729 100644 --- a/lib/single-bar.js +++ b/lib/single-bar.js @@ -131,10 +131,15 @@ module.exports = class SingleBar extends _GenericBar{ // clear line on complete ? if (this.options.clearOnComplete){ + // clear progress bar (And any information below) and remain on same line this.terminal.cursorTo(0, null); + this.terminal.clearBottom(); + } else if (this.concurrentInfo) { + // clear the concurrent information and begin on new line + this.terminal.cursorRelative(0, 1); this.terminal.clearLine(); - }else{ - // new line on complete + } else { + // start on next line this.terminal.newline(); } }