survey.js 4.35 KB
'use strict';

const ArrayPrompt = require('../types/array');

class Survey extends ArrayPrompt {
  constructor(options = {}) {
    super(options);
    this.emptyError = options.emptyError || 'No items were selected';
    this.term = process.env.TERM_PROGRAM;

    if (!this.options.header) {
      let header = ['', '4 - Strongly Agree', '3 - Agree', '2 - Neutral', '1 - Disagree', '0 - Strongly Disagree', ''];
      header = header.map(ele => this.styles.muted(ele));
      this.state.header = header.join('\n   ');
    }
  }

  async toChoices(...args) {
    if (this.createdScales) return false;
    this.createdScales = true;
    let choices = await super.toChoices(...args);
    for (let choice of choices) {
      choice.scale = createScale(5, this.options);
      choice.scaleIdx = 2;
    }
    return choices;
  }

  dispatch() {
    this.alert();
  }

  space() {
    let choice = this.focused;
    let ele = choice.scale[choice.scaleIdx];
    let selected = ele.selected;
    choice.scale.forEach(e => (e.selected = false));
    ele.selected = !selected;
    return this.render();
  }

  indicator() {
    return '';
  }

  pointer() {
    return '';
  }

  separator() {
    return this.styles.muted(this.symbols.ellipsis);
  }

  right() {
    let choice = this.focused;
    if (choice.scaleIdx >= choice.scale.length - 1) return this.alert();
    choice.scaleIdx++;
    return this.render();
  }

  left() {
    let choice = this.focused;
    if (choice.scaleIdx <= 0) return this.alert();
    choice.scaleIdx--;
    return this.render();
  }

  indent() {
    return '   ';
  }

  async renderChoice(item, i) {
    await this.onChoice(item, i);
    let focused = this.index === i;
    let isHyper = this.term === 'Hyper';
    let n = !isHyper ? 8 : 9;
    let s = !isHyper ? ' ' : '';
    let ln = this.symbols.line.repeat(n);
    let sp = ' '.repeat(n + (isHyper ? 0 : 1));
    let dot = enabled => (enabled ? this.styles.success('◉') : '◯') + s;

    let num = i + 1 + '.';
    let color = focused ? this.styles.heading : this.styles.noop;
    let msg = await this.resolve(item.message, this.state, item, i);
    let indent = this.indent(item);
    let scale = indent + item.scale.map((e, i) => dot(i === item.scaleIdx)).join(ln);
    let val = i => i === item.scaleIdx ? color(i) : i;
    let next = indent + item.scale.map((e, i) => val(i)).join(sp);

    let line = () => [num, msg].filter(Boolean).join(' ');
    let lines = () => [line(), scale, next, ' '].filter(Boolean).join('\n');

    if (focused) {
      scale = this.styles.cyan(scale);
      next = this.styles.cyan(next);
    }

    return lines();
  }

  async renderChoices() {
    if (this.state.submitted) return '';
    let choices = this.visible.map(async(ch, i) => await this.renderChoice(ch, i));
    let visible = await Promise.all(choices);
    if (!visible.length) visible.push(this.styles.danger('No matching choices'));
    return visible.join('\n');
  }

  format() {
    if (this.state.submitted) {
      let values = this.choices.map(ch => this.styles.info(ch.scaleIdx));
      return values.join(', ');
    }
    return '';
  }

  async render() {
    let { submitted, size } = this.state;

    let prefix = await this.prefix();
    let separator = await this.separator();
    let message = await this.message();

    let prompt = [prefix, message, separator].filter(Boolean).join(' ');
    this.state.prompt = prompt;

    let header = await this.header();
    let output = await this.format();
    let help = await this.error() || await this.hint();
    let body = await this.renderChoices();
    let footer = await this.footer();

    if (output || !help) prompt += ' ' + output;
    if (help && !prompt.includes(help)) prompt += ' ' + help;

    if (submitted && !output && !body && this.multiple && this.type !== 'form') {
      prompt += this.styles.danger(this.emptyError);
    }

    this.clear(size);
    this.write([prompt, header, body, footer].filter(Boolean).join('\n'));
    this.restore();
  }

  submit() {
    this.value = {};
    for (let choice of this.choices) {
      this.value[choice.name] = choice.scaleIdx;
    }
    return this.base.submit.call(this);
  }
}

function createScale(n, options = {}) {
  if (Array.isArray(options.scale)) {
    return options.scale.map(ele => ({ ...ele }));
  }
  let scale = [];
  for (let i = 1; i < n + 1; i++) scale.push({ i, selected: false });
  return scale;
}

module.exports = Survey;