| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 | 'use strict';const colors = require('ansi-colors');const ArrayPrompt = require('../types/array');const utils = require('../utils');class LikertScale extends ArrayPrompt {  constructor(options = {}) {    super(options);    this.widths = [].concat(options.messageWidth || 50);    this.align = [].concat(options.align || 'left');    this.linebreak = options.linebreak || false;    this.edgeLength = options.edgeLength || 3;    this.newline = options.newline || '\n   ';    let start = options.startNumber || 1;    if (typeof this.scale === 'number') {      this.scaleKey = false;      this.scale = Array(this.scale).fill(0).map((v, i) => ({ name: i + start }));    }  }  async reset() {    this.tableized = false;    await super.reset();    return this.render();  }  tableize() {    if (this.tableized === true) return;    this.tableized = true;    let longest = 0;    for (let ch of this.choices) {      longest = Math.max(longest, ch.message.length);      ch.scaleIndex = ch.initial || 2;      ch.scale = [];      for (let i = 0; i < this.scale.length; i++) {        ch.scale.push({ index: i });      }    }    this.widths[0] = Math.min(this.widths[0], longest + 3);  }  async dispatch(s, key) {    if (this.multiple) {      return this[key.name] ? await this[key.name](s, key) : await super.dispatch(s, key);    }    this.alert();  }  heading(msg, item, i) {    return this.styles.strong(msg);  }  separator() {    return this.styles.muted(this.symbols.ellipsis);  }  right() {    let choice = this.focused;    if (choice.scaleIndex >= this.scale.length - 1) return this.alert();    choice.scaleIndex++;    return this.render();  }  left() {    let choice = this.focused;    if (choice.scaleIndex <= 0) return this.alert();    choice.scaleIndex--;    return this.render();  }  indent() {    return '';  }  format() {    if (this.state.submitted) {      let values = this.choices.map(ch => this.styles.info(ch.index));      return values.join(', ');    }    return '';  }  pointer() {    return '';  }  /**   * Render the scale "Key". Something like:   * @return {String}   */  renderScaleKey() {    if (this.scaleKey === false) return '';    if (this.state.submitted) return '';    let scale = this.scale.map(item => `   ${item.name} - ${item.message}`);    let key = ['', ...scale].map(item => this.styles.muted(item));    return key.join('\n');  }  /**   * Render the heading row for the scale.   * @return {String}   */  renderScaleHeading(max) {    let keys = this.scale.map(ele => ele.name);    if (typeof this.options.renderScaleHeading === 'function') {      keys = this.options.renderScaleHeading.call(this, max);    }    let diff = this.scaleLength - keys.join('').length;    let spacing = Math.round(diff / (keys.length - 1));    let names = keys.map(key => this.styles.strong(key));    let headings = names.join(' '.repeat(spacing));    let padding = ' '.repeat(this.widths[0]);    return this.margin[3] + padding + this.margin[1] + headings;  }  /**   * Render a scale indicator => ◯ or ◉ by default   */  scaleIndicator(choice, item, i) {    if (typeof this.options.scaleIndicator === 'function') {      return this.options.scaleIndicator.call(this, choice, item, i);    }    let enabled = choice.scaleIndex === item.index;    if (item.disabled) return this.styles.hint(this.symbols.radio.disabled);    if (enabled) return this.styles.success(this.symbols.radio.on);    return this.symbols.radio.off;  }  /**   * Render the actual scale => ◯────◯────◉────◯────◯   */  renderScale(choice, i) {    let scale = choice.scale.map(item => this.scaleIndicator(choice, item, i));    let padding = this.term === 'Hyper' ? '' : ' ';    return scale.join(padding + this.symbols.line.repeat(this.edgeLength));  }  /**   * Render a choice, including scale =>   *   "The website is easy to navigate. ◯───◯───◉───◯───◯"   */  async renderChoice(choice, i) {    await this.onChoice(choice, i);    let focused = this.index === i;    let pointer = await this.pointer(choice, i);    let hint = await choice.hint;    if (hint && !utils.hasColor(hint)) {      hint = this.styles.muted(hint);    }    let pad = str => this.margin[3] + str.replace(/\s+$/, '').padEnd(this.widths[0], ' ');    let newline = this.newline;    let ind = this.indent(choice);    let message = await this.resolve(choice.message, this.state, choice, i);    let scale = await this.renderScale(choice, i);    let margin = this.margin[1] + this.margin[3];    this.scaleLength = colors.unstyle(scale).length;    this.widths[0] = Math.min(this.widths[0], this.width - this.scaleLength - margin.length);    let msg = utils.wordWrap(message, { width: this.widths[0], newline });    let lines = msg.split('\n').map(line => pad(line) + this.margin[1]);    if (focused) {      scale = this.styles.info(scale);      lines = lines.map(line => this.styles.info(line));    }    lines[0] += scale;    if (this.linebreak) lines.push('');    return [ind + pointer, lines.join('\n')].filter(Boolean);  }  async renderChoices() {    if (this.state.submitted) return '';    this.tableize();    let choices = this.visible.map(async(ch, i) => await this.renderChoice(ch, i));    let visible = await Promise.all(choices);    let heading = await this.renderScaleHeading();    return this.margin[0] + [heading, ...visible.map(v => v.join(' '))].join('\n');  }  async render() {    let { submitted, size } = this.state;    let prefix = await this.prefix();    let separator = await this.separator();    let message = await this.message();    let prompt = '';    if (this.options.promptLine !== false) {      prompt = [prefix, message, separator, ''].join(' ');      this.state.prompt = prompt;    }    let header = await this.header();    let output = await this.format();    let key = await this.renderScaleKey();    let help = await this.error() || await this.hint();    let body = await this.renderChoices();    let footer = await this.footer();    let err = this.emptyError;    if (output) prompt += output;    if (help && !prompt.includes(help)) prompt += ' ' + help;    if (submitted && !output && !body.trim() && this.multiple && err != null) {      prompt += this.styles.danger(err);    }    this.clear(size);    this.write([header, prompt, key, body, footer].filter(Boolean).join('\n'));    if (!this.state.submitted) {      this.write(this.margin[2]);    }    this.restore();  }  submit() {    this.value = {};    for (let choice of this.choices) {      this.value[choice.name] = choice.scaleIndex;    }    return this.base.submit.call(this);  }}module.exports = LikertScale;
 |