Spaces:
Runtime error
Runtime error
; | |
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; | |