You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							289 lines
						
					
					
						
							7.3 KiB
						
					
					
				
			
		
		
	
	
							289 lines
						
					
					
						
							7.3 KiB
						
					
					
				'use strict'; | 
						|
 | 
						|
const color = require('kleur'); | 
						|
 | 
						|
const _require = require('sisteransi'), | 
						|
      cursor = _require.cursor; | 
						|
 | 
						|
const Prompt = require('./prompt'); | 
						|
 | 
						|
const _require2 = require('../util'), | 
						|
      clear = _require2.clear, | 
						|
      figures = _require2.figures, | 
						|
      style = _require2.style, | 
						|
      wrap = _require2.wrap, | 
						|
      entriesToDisplay = _require2.entriesToDisplay; | 
						|
/** | 
						|
 * MultiselectPrompt Base Element | 
						|
 * @param {Object} opts Options | 
						|
 * @param {String} opts.message Message | 
						|
 * @param {Array} opts.choices Array of choice objects | 
						|
 * @param {String} [opts.hint] Hint to display | 
						|
 * @param {String} [opts.warn] Hint shown for disabled choices | 
						|
 * @param {Number} [opts.max] Max choices | 
						|
 * @param {Number} [opts.cursor=0] Cursor start position | 
						|
 * @param {Number} [opts.optionsPerPage=10] Max options to display at once | 
						|
 * @param {Stream} [opts.stdin] The Readable stream to listen to | 
						|
 * @param {Stream} [opts.stdout] The Writable stream to write readline data to | 
						|
 */ | 
						|
 | 
						|
 | 
						|
class MultiselectPrompt extends Prompt { | 
						|
  constructor(opts = {}) { | 
						|
    super(opts); | 
						|
    this.msg = opts.message; | 
						|
    this.cursor = opts.cursor || 0; | 
						|
    this.scrollIndex = opts.cursor || 0; | 
						|
    this.hint = opts.hint || ''; | 
						|
    this.warn = opts.warn || '- This option is disabled -'; | 
						|
    this.minSelected = opts.min; | 
						|
    this.showMinError = false; | 
						|
    this.maxChoices = opts.max; | 
						|
    this.instructions = opts.instructions; | 
						|
    this.optionsPerPage = opts.optionsPerPage || 10; | 
						|
    this.value = opts.choices.map((ch, idx) => { | 
						|
      if (typeof ch === 'string') ch = { | 
						|
        title: ch, | 
						|
        value: idx | 
						|
      }; | 
						|
      return { | 
						|
        title: ch && (ch.title || ch.value || ch), | 
						|
        description: ch && ch.description, | 
						|
        value: ch && (ch.value === undefined ? idx : ch.value), | 
						|
        selected: ch && ch.selected, | 
						|
        disabled: ch && ch.disabled | 
						|
      }; | 
						|
    }); | 
						|
    this.clear = clear('', this.out.columns); | 
						|
 | 
						|
    if (!opts.overrideRender) { | 
						|
      this.render(); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  reset() { | 
						|
    this.value.map(v => !v.selected); | 
						|
    this.cursor = 0; | 
						|
    this.fire(); | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  selected() { | 
						|
    return this.value.filter(v => v.selected); | 
						|
  } | 
						|
 | 
						|
  exit() { | 
						|
    this.abort(); | 
						|
  } | 
						|
 | 
						|
  abort() { | 
						|
    this.done = this.aborted = true; | 
						|
    this.fire(); | 
						|
    this.render(); | 
						|
    this.out.write('\n'); | 
						|
    this.close(); | 
						|
  } | 
						|
 | 
						|
  submit() { | 
						|
    const selected = this.value.filter(e => e.selected); | 
						|
 | 
						|
    if (this.minSelected && selected.length < this.minSelected) { | 
						|
      this.showMinError = true; | 
						|
      this.render(); | 
						|
    } else { | 
						|
      this.done = true; | 
						|
      this.aborted = false; | 
						|
      this.fire(); | 
						|
      this.render(); | 
						|
      this.out.write('\n'); | 
						|
      this.close(); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  first() { | 
						|
    this.cursor = 0; | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  last() { | 
						|
    this.cursor = this.value.length - 1; | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  next() { | 
						|
    this.cursor = (this.cursor + 1) % this.value.length; | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  up() { | 
						|
    if (this.cursor === 0) { | 
						|
      this.cursor = this.value.length - 1; | 
						|
    } else { | 
						|
      this.cursor--; | 
						|
    } | 
						|
 | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  down() { | 
						|
    if (this.cursor === this.value.length - 1) { | 
						|
      this.cursor = 0; | 
						|
    } else { | 
						|
      this.cursor++; | 
						|
    } | 
						|
 | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  left() { | 
						|
    this.value[this.cursor].selected = false; | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  right() { | 
						|
    if (this.value.filter(e => e.selected).length >= this.maxChoices) return this.bell(); | 
						|
    this.value[this.cursor].selected = true; | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  handleSpaceToggle() { | 
						|
    const v = this.value[this.cursor]; | 
						|
 | 
						|
    if (v.selected) { | 
						|
      v.selected = false; | 
						|
      this.render(); | 
						|
    } else if (v.disabled || this.value.filter(e => e.selected).length >= this.maxChoices) { | 
						|
      return this.bell(); | 
						|
    } else { | 
						|
      v.selected = true; | 
						|
      this.render(); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  toggleAll() { | 
						|
    if (this.maxChoices !== undefined || this.value[this.cursor].disabled) { | 
						|
      return this.bell(); | 
						|
    } | 
						|
 | 
						|
    const newSelected = !this.value[this.cursor].selected; | 
						|
    this.value.filter(v => !v.disabled).forEach(v => v.selected = newSelected); | 
						|
    this.render(); | 
						|
  } | 
						|
 | 
						|
  _(c, key) { | 
						|
    if (c === ' ') { | 
						|
      this.handleSpaceToggle(); | 
						|
    } else if (c === 'a') { | 
						|
      this.toggleAll(); | 
						|
    } else { | 
						|
      return this.bell(); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  renderInstructions() { | 
						|
    if (this.instructions === undefined || this.instructions) { | 
						|
      if (typeof this.instructions === 'string') { | 
						|
        return this.instructions; | 
						|
      } | 
						|
 | 
						|
      return '\nInstructions:\n' + `    ${figures.arrowUp}/${figures.arrowDown}: Highlight option\n` + `    ${figures.arrowLeft}/${figures.arrowRight}/[space]: Toggle selection\n` + (this.maxChoices === undefined ? `    a: Toggle all\n` : '') + `    enter/return: Complete answer`; | 
						|
    } | 
						|
 | 
						|
    return ''; | 
						|
  } | 
						|
 | 
						|
  renderOption(cursor, v, i, arrowIndicator) { | 
						|
    const prefix = (v.selected ? color.green(figures.radioOn) : figures.radioOff) + ' ' + arrowIndicator + ' '; | 
						|
    let title, desc; | 
						|
 | 
						|
    if (v.disabled) { | 
						|
      title = cursor === i ? color.gray().underline(v.title) : color.strikethrough().gray(v.title); | 
						|
    } else { | 
						|
      title = cursor === i ? color.cyan().underline(v.title) : v.title; | 
						|
 | 
						|
      if (cursor === i && v.description) { | 
						|
        desc = ` - ${v.description}`; | 
						|
 | 
						|
        if (prefix.length + title.length + desc.length >= this.out.columns || v.description.split(/\r?\n/).length > 1) { | 
						|
          desc = '\n' + wrap(v.description, { | 
						|
            margin: prefix.length, | 
						|
            width: this.out.columns | 
						|
          }); | 
						|
        } | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    return prefix + title + color.gray(desc || ''); | 
						|
  } // shared with autocompleteMultiselect | 
						|
 | 
						|
 | 
						|
  paginateOptions(options) { | 
						|
    if (options.length === 0) { | 
						|
      return color.red('No matches for this query.'); | 
						|
    } | 
						|
 | 
						|
    let _entriesToDisplay = entriesToDisplay(this.cursor, options.length, this.optionsPerPage), | 
						|
        startIndex = _entriesToDisplay.startIndex, | 
						|
        endIndex = _entriesToDisplay.endIndex; | 
						|
 | 
						|
    let prefix, | 
						|
        styledOptions = []; | 
						|
 | 
						|
    for (let i = startIndex; i < endIndex; i++) { | 
						|
      if (i === startIndex && startIndex > 0) { | 
						|
        prefix = figures.arrowUp; | 
						|
      } else if (i === endIndex - 1 && endIndex < options.length) { | 
						|
        prefix = figures.arrowDown; | 
						|
      } else { | 
						|
        prefix = ' '; | 
						|
      } | 
						|
 | 
						|
      styledOptions.push(this.renderOption(this.cursor, options[i], i, prefix)); | 
						|
    } | 
						|
 | 
						|
    return '\n' + styledOptions.join('\n'); | 
						|
  } // shared with autocomleteMultiselect | 
						|
 | 
						|
 | 
						|
  renderOptions(options) { | 
						|
    if (!this.done) { | 
						|
      return this.paginateOptions(options); | 
						|
    } | 
						|
 | 
						|
    return ''; | 
						|
  } | 
						|
 | 
						|
  renderDoneOrInstructions() { | 
						|
    if (this.done) { | 
						|
      return this.value.filter(e => e.selected).map(v => v.title).join(', '); | 
						|
    } | 
						|
 | 
						|
    const output = [color.gray(this.hint), this.renderInstructions()]; | 
						|
 | 
						|
    if (this.value[this.cursor].disabled) { | 
						|
      output.push(color.yellow(this.warn)); | 
						|
    } | 
						|
 | 
						|
    return output.join(' '); | 
						|
  } | 
						|
 | 
						|
  render() { | 
						|
    if (this.closed) return; | 
						|
    if (this.firstRender) this.out.write(cursor.hide); | 
						|
    super.render(); // print prompt | 
						|
 | 
						|
    let prompt = [style.symbol(this.done, this.aborted), color.bold(this.msg), style.delimiter(false), this.renderDoneOrInstructions()].join(' '); | 
						|
 | 
						|
    if (this.showMinError) { | 
						|
      prompt += color.red(`You must select a minimum of ${this.minSelected} choices.`); | 
						|
      this.showMinError = false; | 
						|
    } | 
						|
 | 
						|
    prompt += this.renderOptions(this.value); | 
						|
    this.out.write(this.clear + prompt); | 
						|
    this.clear = clear(prompt, this.out.columns); | 
						|
  } | 
						|
 | 
						|
} | 
						|
 | 
						|
module.exports = MultiselectPrompt; |