ForestAdmin/toolbelt

View on GitHub
src/services/spinner.js

Summary

Maintainability
A
2 hrs
Test Coverage
A
97%
const { v4: uuidv4 } = require('uuid');
const chalk = require('chalk');
const Spinnies = require('spinnies');

const spinniesConstructorParameters = {
  color: 'yellow',
  failPrefix: `${chalk.bold.red('×')}`,
  spinnerColor: 'yellow',
  spinner: {
    interval: 80,
    frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
  },
  succeedPrefix: `${chalk.bold.green('√')}`,
};

// NOTICE: Singleton used here to attach all generated spinner to the same spinnies instance.
let spinniesInstance;
function getSpinniesInstance() {
  if (!spinniesInstance) spinniesInstance = new Spinnies(spinniesConstructorParameters);
  return spinniesInstance;
}

/**
 * A single instance of spinner is intended to be used as follow:
 * @example
 * // synchronously:
 * spinner.start({ text: 'my super text' })
 * // do something
 * spinner.success()
 *
 * // on promise:
 * spinner.start({ text: 'my super text' })
 * spinner.attachToPromise(mySuperPromise())
 *
 * If ever multiple instances are need to be run together:
 * @example
 * const Spinner = require('./spinner');
 *
 * const spinner1 = new Spinner();
 * const spinner2 = new Spinner();
 */
class Spinner {
  constructor() {
    this.spinnies = getSpinniesInstance();
    this.currentSpinnerOptions = null;
  }

  start(options) {
    if (this.isRunning()) {
      throw Error('A spinner is already running.');
    }

    this.currentSpinnerOptions = options;
    this.currentSpinnerUniqueKey = uuidv4();
    this.spinnies.add(this.currentSpinnerUniqueKey, options);
  }

  // NOTICE: optional parameter options to have a custom success message
  success(options = this.currentSpinnerOptions) {
    if (!this.isRunning()) {
      throw Error('No spinner is running.');
    }

    this.spinnies.succeed(this.currentSpinnerUniqueKey, options);
    this.stop();
  }

  // NOTICE: optional parameter options to have a custom fail message
  fail(options = this.currentSpinnerOptions) {
    if (!this.isRunning()) {
      throw Error('No spinner is running.');
    }

    this.spinnies.fail(this.currentSpinnerUniqueKey, options);
    this.stop();
  }

  pause() {
    if (!this.isRunning()) {
      throw Error('No spinner is running.');
    }

    this.spinnies.remove(this.currentSpinnerUniqueKey);
    // NOTICE: spinnies lib function that checks for active spinners and if none, release cli usage
    this.spinnies.checkIfActiveSpinners();
    this.pausedSpinnerOptions = this.currentSpinnerOptions;
  }

  continue() {
    if (this.isRunning()) {
      throw Error('A spinner is already running.');
    }

    if (!this.pausedSpinnerOptions) {
      throw Error('No spinner has been paused.');
    }

    this.spinnies.add(this.currentSpinnerUniqueKey, this.pausedSpinnerOptions);
    this.pausedSpinnerOptions = null;
  }

  isRunning() {
    return !!this.spinnies.pick(this.currentSpinnerUniqueKey);
  }

  // NOTICE: spinner.start needs to be called first
  attachToPromise(promise) {
    return promise
      .then(result => {
        this.success(this.currentSpinnerOptions);
        return result;
      })
      .catch(error => {
        // NOTICE: Only trigger the fail if the spinner is running (ie. not paused)
        if (this.isRunning()) {
          this.fail({ text: error });
        }
        throw error;
      });
  }

  // NOTICE: this stop method should only be used internally to reset the current spinner
  //         on success or failure.
  stop() {
    this.currentSpinnerUniqueKey = null;
    this.currentSpinnerOptions = null;
  }
}

module.exports = Spinner;