xprogress 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +100 -35
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -44,6 +44,7 @@ const globOpts = {
44
44
  'color:bar:separator': '',
45
45
  },
46
46
  forceFirst: !1,
47
+ keepHeader: !1,
47
48
  };
48
49
 
49
50
  const defaultOptions = {
@@ -54,7 +55,7 @@ const defaultOptions = {
54
55
  const streamOpts = {
55
56
  ...globOpts,
56
57
  pulsate: false,
57
- progress: {time: 100, pulsate: false, infinite: !1, pulsateSkip: 15, pulsateLength: 15},
58
+ progress: {time: 200, pulsate: false, infinite: !1, pulsateSkip: 15, pulsateLength: 15},
58
59
  template: [
59
60
  ':{label}',
60
61
  ' |:{slot:bar}| [:3{slot:percentage}%] (:{slot:eta}) [:{speed}] [:{slot:size}/:{slot:size:total}]',
@@ -123,7 +124,8 @@ function parseBar(opts, fillable, percentage, headers = !opts.pulsate) {
123
124
  let empty = fillable - filled;
124
125
  let {filler, blank, header} = opts.bar;
125
126
  [filled, empty] = [filled, empty].map(Math.floor);
126
- [filler, blank] = [filler, blank].map(content => (Array.isArray(content) || typeof content === 'string' ? content : ''));
127
+ [filler, blank] = [filler, blank].map(content => (typeof content === 'string' ? content : '?'));
128
+ if (!opts.keepHeader && headers && empty === 0) (filled += header.length), (headers = null);
127
129
  return stringd(
128
130
  [
129
131
  `:{color:bar:filled}${filler.repeat(filled)}`,
@@ -165,6 +167,28 @@ function pulsateBar(bar, slots, skip, sep = '') {
165
167
  return stack.map(({fillable, percentage}) => parseBar(bar.opts, fillable, percentage, false));
166
168
  }
167
169
 
170
+ // eslint-disable-next-line no-control-regex
171
+ const ANSI_RE = /\x1b\[[^m]*m/g;
172
+ function trimLine(line, cols) {
173
+ let visible = 0;
174
+ let i = 0;
175
+ let result = '';
176
+ while (i < line.length) {
177
+ ANSI_RE.lastIndex = i;
178
+ const ansi = ANSI_RE.exec(line);
179
+ if (ansi && ansi.index === i) {
180
+ result += ansi[0];
181
+ i += ansi[0].length;
182
+ continue;
183
+ }
184
+ if (visible >= cols) break;
185
+ result += line[i];
186
+ visible++;
187
+ i++;
188
+ }
189
+ return visible < line.replace(ANSI_RE, '').length ? `${result}\x1b[0m` : result;
190
+ }
191
+
168
192
  class ProgressGen extends EventEmitter {}
169
193
 
170
194
  export default class ProgressBar {
@@ -213,6 +237,20 @@ export default class ProgressBar {
213
237
  delete this.opts.label;
214
238
  delete this.opts.append;
215
239
  delete this.opts.length;
240
+ this.#resizeHandler = () => {
241
+ if (!this.hasBarredOnce || this.isEnded) return;
242
+ const stdout = this.cores.stdout;
243
+ this.#resizing = true;
244
+ stdout.moveCursor(0, -(this.oldBar.length - 1));
245
+ stdout.cursorTo(0);
246
+ stdout.clearScreenDown();
247
+ clearTimeout(this.#resizeTimer);
248
+ this.#resizeTimer = setTimeout(() => {
249
+ this.#resizing = false;
250
+ this.draw(this.oldBar);
251
+ }, 100);
252
+ };
253
+ this.cores.stdout?.on('resize', this.#resizeHandler);
216
254
  }
217
255
 
218
256
  /**
@@ -384,19 +422,28 @@ export default class ProgressBar {
384
422
  * @param {String|Object} [template] The template to use on the drawn progress bar or an array of predrawn progressbar from `this.constructBar` like `this.oldBar`
385
423
  */
386
424
  draw(template) {
425
+ if (!Array.isArray(template))
426
+ this.cores.append = this.cores.append.filter(block => !(block.bar.opts.clean && block.bar.isComplete()));
387
427
  const result = Array.isArray(template)
388
428
  ? template
389
429
  : [
390
430
  ...this.constructBar(template).split('\n'),
391
431
  ...this.cores.append.map(block => block.bar.constructBar(block.inherit ? template : null)),
392
432
  ];
433
+ const prevLength = this.hasBarredOnce ? this.oldBar.length : 0;
393
434
  this.oldBar = result;
394
- this.print(`bar${result.length ? `+${result.length - 1}` : ''}`, result.join('\n'));
435
+ if (!this.#resizing) {
436
+ this.#printLines(result, this.justLogged, prevLength ? prevLength - 1 : 0);
437
+ this.justLogged = false;
438
+ }
395
439
  this.hasBarredOnce = !0;
396
440
  return this;
397
441
  }
398
442
 
399
443
  #flipperCount = 0;
444
+ #resizeHandler = null;
445
+ #resizeTimer = null;
446
+ #resizing = false;
400
447
 
401
448
  constructBar(template) {
402
449
  const forcedFirst = [
@@ -446,45 +493,40 @@ export default class ProgressBar {
446
493
  return stringd(stringd(str, template), variables);
447
494
  }
448
495
 
496
+ #printLines(lines, dontClean, addons = 0, ending = false) {
497
+ const stdout = this.cores.stdout;
498
+ if (!dontClean) {
499
+ // check https://github.com/freeall/single-line-log/blob/515b3b99b699396c2ad5f937e4b490b6f9fbff0e/index.js#L1-L3
500
+ stdout.moveCursor(0, -addons);
501
+ stdout.cursorTo(0);
502
+ stdout.clearScreenDown();
503
+ }
504
+ const cols = stdout.columns;
505
+ // eslint-disable-next-line no-control-regex
506
+ const strip = this.opts.bar.colorize ? l => l : l => l.replace(/\x1b\[\d+m/g, '');
507
+ stdout.write(
508
+ `${dontClean && ending && (addons || this.hasBarredOnce) ? '\n' : ''}${lines
509
+ .map(line => trimLine(strip(line), cols))
510
+ .join('\n')}`,
511
+ );
512
+ }
513
+
449
514
  /**
450
515
  * Print a message after a bar `draw` interrupt
451
- * @param {'bar'|'end'} type Type of bar print or the first part of the printer
452
516
  * @param {any[]} content The contents to be formatted
453
517
  */
454
518
  print(type, ...content) {
455
- const self = this;
519
+ if (this.isChild) return this.cores.parent.print(type, ...content);
456
520
  type = format(type);
457
- if (!self.cores.stdout.isTTY) throw Error("Can't draw or print progressBar interrupts with piped output");
458
- const cleanWrite = function cleanWrite(arr, dontClean, addons = 0, ending = false, normie = false) {
459
- if (!dontClean) {
460
- // check https://github.com/freeall/single-line-log/blob/515b3b99b699396c2ad5f937e4b490b6f9fbff0e/index.js#L1-L3
461
- self.cores.stdout.moveCursor(0, -addons);
462
- self.cores.stdout.cursorTo(0);
463
- self.cores.stdout.clearScreenDown();
464
- }
465
- (normie ? process.stdout : self.cores.stdout).write(
466
- `${dontClean && ending && addons ? '\n' : ''}${self
467
- .parseString(format(...arr))
468
- // eslint-disable-next-line no-control-regex
469
- .replace(self.opts.bar.colorize ? '' : /\x1b\[\d+m/g, '')}`,
470
- );
471
- };
472
- let addonPack;
521
+ if (!this.cores.stdout.isTTY) throw Error("Can't draw or print progressBar interrupts with piped output");
473
522
  const addons = this.hasBarredOnce && !this.justLogged ? this.oldBar.length - 1 : 0;
523
+ const lines = this.parseString(
524
+ type === 'end' ? format(...content) : format((type.startsWith(':') && type.slice(1)) || type, ...content, '\n'),
525
+ ).split('\n');
474
526
  this.justLogged =
475
- type === 'bar' && content.length === 1
476
- ? !!cleanWrite(content, this.justLogged, addons)
477
- : (addonPack = type.match(/^bar\+(\d+)/)) !== null
478
- ? !!cleanWrite(content, this.justLogged, this.hasBarredOnce ? addonPack[1] : addons)
479
- : type === 'end'
480
- ? !!cleanWrite(content, !this.opts.clean, addons, true, true)
481
- : !cleanWrite(
482
- [(type.startsWith(':') && `${type.slice(1)}`) || type, ...content, '\n'],
483
- this.justLogged,
484
- addons,
485
- false,
486
- true,
487
- );
527
+ type === 'end'
528
+ ? !!this.#printLines(lines, !this.opts.clean, addons, true)
529
+ : !this.#printLines(lines, this.justLogged, addons);
488
530
  if (this.justLogged && this.hasBarredOnce) this.draw(this.oldBar);
489
531
  return this;
490
532
  }
@@ -495,8 +537,14 @@ export default class ProgressBar {
495
537
  */
496
538
  end(...message) {
497
539
  if (!this.isEnded) {
498
- if (message.length) this.print('end', ...message);
540
+ if (message.length) {
541
+ if (this.isChild) this.cores.parent.print(...message);
542
+ else this.print('end', ...message);
543
+ }
499
544
  this.isEnded = !0;
545
+ this.#resizing = false;
546
+ clearTimeout(this.#resizeTimer);
547
+ this.cores.stdout?.off('resize', this.#resizeHandler);
500
548
  }
501
549
  return this;
502
550
  }
@@ -515,6 +563,21 @@ export default class ProgressBar {
515
563
  */
516
564
  drop() {}
517
565
 
566
+ /**
567
+ * Insert a bar immediately after an existing appended bar
568
+ * @param {ProgressBar} anchor The bar after which to insert
569
+ * @param {ProgressBar} bar The bar to insert
570
+ * @param {Boolean} inherit Whether or not to inherit bar templates from `this`
571
+ */
572
+ insertAfter(anchor, bar, inherit = !1) {
573
+ const idx = this.cores.append.findIndex(a => a.bar === anchor);
574
+ if (idx === -1) throw Error('Anchor bar not found');
575
+ const tail = this.cores.append.splice(idx + 1);
576
+ this.append(bar, inherit);
577
+ this.cores.append.push(...tail);
578
+ return this;
579
+ }
580
+
518
581
  /**
519
582
  * Append the specified bar after `this`
520
583
  * @param {ProgressBar} bar The bar to be appended
@@ -524,6 +587,8 @@ export default class ProgressBar {
524
587
  if (!ProgressBar.isBar(bar) && !bar.opts.template) throw Error('The Parameter <bar> is not a progressbar or a hanger');
525
588
  this.cores.append.push({bar, inherit});
526
589
  bar.cores.isKid = !0;
590
+ bar.cores.parent = this;
591
+ bar.cores.stdout?.off('resize', bar.#resizeHandler);
527
592
  return this;
528
593
  }
529
594
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xprogress",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Dynamic, Flexible, extensible progressive CLI bar for the terminal built with NodeJS",
5
5
  "exports": "./index.js",
6
6
  "type": "module",
@@ -50,7 +50,7 @@
50
50
  "progress-stream": "^2.0.0",
51
51
  "speedometer": "^1.1.0",
52
52
  "stringd": "^2.2.0",
53
- "stringd-colors": "^1.10.0",
53
+ "stringd-colors": "^1.11.0",
54
54
  "xbytes": "^1.6.1"
55
55
  }
56
56
  }