@controlplane/cli
Version:
Control Plane Corporation CLI
125 lines • 3.77 kB
JavaScript
"use strict";
// ANCHOR - Constants
Object.defineProperty(exports, "__esModule", { value: true });
exports.TerminalSpinner = void 0;
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
const FRAME_INTERVAL_MS = 80;
// Erase the current line and return to its start, so the next write redraws in place.
const CLEAR_LINE = '\x1b[2K\r';
const HIDE_CURSOR = '\x1b[?25l';
const SHOW_CURSOR = '\x1b[?25h';
// ANCHOR - TerminalSpinner
/**
* An in-place terminal spinner: one animated line showing that work is happening while
* nothing streams. It animates on a scheduler, persists other lines above itself without
* disturbing the animation, and clears its line and restores the cursor when stopped. On
* a non-terminal stream it degrades to printing each label once, with no animation.
*/
class TerminalSpinner {
constructor(stream, opts = {}) {
var _a, _b;
this.running = false;
this.frame = 0;
this.label = '';
this.timer = null;
this.stream = stream;
this.scheduler = (_a = opts.scheduler) !== null && _a !== void 0 ? _a : systemScheduler();
this.paint = (_b = opts.paint) !== null && _b !== void 0 ? _b : plainText;
}
// Public Methods //
/** @inheritdoc */
start(label) {
this.label = label;
// Without a terminal there is no in-place line to animate; announce it once.
if (!this.stream.isTTY) {
this.stream.write(`${label}\n`);
return;
}
if (this.running) {
this.scheduler.clear(this.timer);
}
this.frame = 0;
this.running = true;
this.stream.write(HIDE_CURSOR);
this.render();
this.timer = this.scheduler.set(() => this.tick(), FRAME_INTERVAL_MS);
}
/** @inheritdoc */
setLabel(label) {
this.label = label;
if (this.running) {
this.render();
}
}
/** @inheritdoc */
persist(line) {
if (!this.running) {
this.stream.write(`${line}\n`);
return;
}
// Erase the spinner, drop the line above it, then redraw the spinner beneath.
this.stream.write(`${CLEAR_LINE}${line}\n`);
this.render();
}
/** @inheritdoc */
stop() {
if (!this.running) {
return;
}
this.running = false;
this.scheduler.clear(this.timer);
this.timer = null;
this.stream.write(`${CLEAR_LINE}${SHOW_CURSOR}`);
}
/** @inheritdoc */
get active() {
return this.running;
}
// Private Methods //
/**
* Advances to the next animation frame and redraws.
*
* @returns {void}
*/
tick() {
this.frame = (this.frame + 1) % FRAMES.length;
this.render();
}
/**
* Redraws the current frame and label in place.
*
* @returns {void}
*/
render() {
this.stream.write(`${CLEAR_LINE}${this.paint(FRAMES[this.frame])} ${this.label}`);
}
}
exports.TerminalSpinner = TerminalSpinner;
// SECTION - Functions
/**
* The production scheduler, backed by unref'd timers so a running spinner never keeps
* the process alive on its own.
*
* @returns {SpinnerScheduler} The scheduler.
*/
function systemScheduler() {
return {
set: (callback, millis) => {
const handle = setInterval(callback, millis);
handle.unref();
return handle;
},
clear: (handle) => clearInterval(handle),
};
}
/**
* The default frame styling: none.
*
* @param {string} text - The frame to style.
* @returns {string} The text unchanged.
*/
function plainText(text) {
return text;
}
// !SECTION
//# sourceMappingURL=spinner.js.map