wishthis/node_modules/inquirer/lib/prompts/checkbox.js

276 lines
6.7 KiB
JavaScript
Raw Normal View History

2022-01-21 08:28:41 +00:00
'use strict';
/**
* `list` type prompt
*/
2022-04-07 07:06:43 +00:00
const chalk = require('chalk');
const cliCursor = require('cli-cursor');
const figures = require('figures');
const { map, takeUntil } = require('rxjs/operators');
const Base = require('./base');
const observe = require('../utils/events');
const Paginator = require('../utils/paginator');
const incrementListIndex = require('../utils/incrementListIndex');
2022-01-21 08:28:41 +00:00
class CheckboxPrompt extends Base {
constructor(questions, rl, answers) {
super(questions, rl, answers);
if (!this.opt.choices) {
this.throwParamError('choices');
}
2022-04-07 07:06:43 +00:00
if (Array.isArray(this.opt.default)) {
this.opt.choices.forEach(function (choice) {
2022-01-21 08:28:41 +00:00
if (this.opt.default.indexOf(choice.value) >= 0) {
choice.checked = true;
}
}, this);
}
this.pointer = 0;
// Make sure no default is set (so it won't be printed)
this.opt.default = null;
2022-04-07 07:06:43 +00:00
const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop;
this.paginator = new Paginator(this.screen, { isInfinite: shouldLoop });
2022-01-21 08:28:41 +00:00
}
/**
* Start the Inquiry session
* @param {Function} cb Callback when prompt is done
* @return {this}
*/
_run(cb) {
this.done = cb;
2022-04-07 07:06:43 +00:00
const events = observe(this.rl);
2022-01-21 08:28:41 +00:00
2022-04-07 07:06:43 +00:00
const validation = this.handleSubmitEvents(
2022-01-21 08:28:41 +00:00
events.line.pipe(map(this.getCurrentValue.bind(this)))
);
validation.success.forEach(this.onEnd.bind(this));
validation.error.forEach(this.onError.bind(this));
events.normalizedUpKey
.pipe(takeUntil(validation.success))
.forEach(this.onUpKey.bind(this));
events.normalizedDownKey
.pipe(takeUntil(validation.success))
.forEach(this.onDownKey.bind(this));
events.numberKey
.pipe(takeUntil(validation.success))
.forEach(this.onNumberKey.bind(this));
events.spaceKey
.pipe(takeUntil(validation.success))
.forEach(this.onSpaceKey.bind(this));
events.aKey.pipe(takeUntil(validation.success)).forEach(this.onAllKey.bind(this));
events.iKey.pipe(takeUntil(validation.success)).forEach(this.onInverseKey.bind(this));
// Init the prompt
cliCursor.hide();
this.render();
this.firstRender = false;
return this;
}
/**
* Render the prompt to screen
* @return {CheckboxPrompt} self
*/
render(error) {
// Render question
2022-04-07 07:06:43 +00:00
let message = this.getQuestion();
let bottomContent = '';
2022-01-21 08:28:41 +00:00
2022-04-07 07:06:43 +00:00
if (!this.dontShowHints) {
2022-01-21 08:28:41 +00:00
message +=
'(Press ' +
chalk.cyan.bold('<space>') +
' to select, ' +
chalk.cyan.bold('<a>') +
' to toggle all, ' +
chalk.cyan.bold('<i>') +
2022-04-07 07:06:43 +00:00
' to invert selection, and ' +
chalk.cyan.bold('<enter>') +
' to proceed)';
2022-01-21 08:28:41 +00:00
}
// Render choices or answer depending on the state
if (this.status === 'answered') {
message += chalk.cyan(this.selection.join(', '));
} else {
2022-04-07 07:06:43 +00:00
const choicesStr = renderChoices(this.opt.choices, this.pointer);
const indexPosition = this.opt.choices.indexOf(
2022-01-21 08:28:41 +00:00
this.opt.choices.getChoice(this.pointer)
);
2022-04-07 07:06:43 +00:00
const realIndexPosition =
this.opt.choices.reduce((acc, value, i) => {
// Dont count lines past the choice we are looking at
if (i > indexPosition) {
return acc;
}
// Add line if it's a separator
if (value.type === 'separator') {
return acc + 1;
}
let l = value.name;
// Non-strings take up one line
if (typeof l !== 'string') {
return acc + 1;
}
// Calculate lines taken up by string
l = l.split('\n');
return acc + l.length;
}, 0) - 1;
2022-01-21 08:28:41 +00:00
message +=
2022-04-07 07:06:43 +00:00
'\n' + this.paginator.paginate(choicesStr, realIndexPosition, this.opt.pageSize);
2022-01-21 08:28:41 +00:00
}
if (error) {
bottomContent = chalk.red('>> ') + error;
}
this.screen.render(message, bottomContent);
}
/**
* When user press `enter` key
*/
onEnd(state) {
this.status = 'answered';
2022-04-07 07:06:43 +00:00
this.dontShowHints = true;
2022-01-21 08:28:41 +00:00
// Rerender prompt (and clean subline error)
this.render();
this.screen.done();
cliCursor.show();
this.done(state.value);
}
onError(state) {
this.render(state.isValid);
}
getCurrentValue() {
2022-04-07 07:06:43 +00:00
const choices = this.opt.choices.filter(
(choice) => Boolean(choice.checked) && !choice.disabled
);
2022-01-21 08:28:41 +00:00
2022-04-07 07:06:43 +00:00
this.selection = choices.map((choice) => choice.short);
return choices.map((choice) => choice.value);
2022-01-21 08:28:41 +00:00
}
onUpKey() {
2022-04-07 07:06:43 +00:00
this.pointer = incrementListIndex(this.pointer, 'up', this.opt);
2022-01-21 08:28:41 +00:00
this.render();
}
onDownKey() {
2022-04-07 07:06:43 +00:00
this.pointer = incrementListIndex(this.pointer, 'down', this.opt);
2022-01-21 08:28:41 +00:00
this.render();
}
onNumberKey(input) {
if (input <= this.opt.choices.realLength) {
this.pointer = input - 1;
this.toggleChoice(this.pointer);
}
this.render();
}
onSpaceKey() {
this.toggleChoice(this.pointer);
this.render();
}
onAllKey() {
2022-04-07 07:06:43 +00:00
const shouldBeChecked = Boolean(
this.opt.choices.find((choice) => choice.type !== 'separator' && !choice.checked)
2022-01-21 08:28:41 +00:00
);
2022-04-07 07:06:43 +00:00
this.opt.choices.forEach((choice) => {
2022-01-21 08:28:41 +00:00
if (choice.type !== 'separator') {
choice.checked = shouldBeChecked;
}
});
this.render();
}
onInverseKey() {
2022-04-07 07:06:43 +00:00
this.opt.choices.forEach((choice) => {
2022-01-21 08:28:41 +00:00
if (choice.type !== 'separator') {
choice.checked = !choice.checked;
}
});
this.render();
}
toggleChoice(index) {
2022-04-07 07:06:43 +00:00
const item = this.opt.choices.getChoice(index);
2022-01-21 08:28:41 +00:00
if (item !== undefined) {
this.opt.choices.getChoice(index).checked = !item.checked;
}
}
}
/**
* Function for rendering checkbox choices
* @param {Number} pointer Position of the pointer
* @return {String} Rendered content
*/
function renderChoices(choices, pointer) {
2022-04-07 07:06:43 +00:00
let output = '';
let separatorOffset = 0;
2022-01-21 08:28:41 +00:00
2022-04-07 07:06:43 +00:00
choices.forEach((choice, i) => {
2022-01-21 08:28:41 +00:00
if (choice.type === 'separator') {
separatorOffset++;
output += ' ' + choice + '\n';
return;
}
if (choice.disabled) {
separatorOffset++;
output += ' - ' + choice.name;
2022-04-07 07:06:43 +00:00
output += ` (${
typeof choice.disabled === 'string' ? choice.disabled : 'Disabled'
})`;
2022-01-21 08:28:41 +00:00
} else {
2022-04-07 07:06:43 +00:00
const line = getCheckbox(choice.checked) + ' ' + choice.name;
2022-01-21 08:28:41 +00:00
if (i - separatorOffset === pointer) {
output += chalk.cyan(figures.pointer + line);
} else {
output += ' ' + line;
}
}
output += '\n';
});
return output.replace(/\n$/, '');
}
/**
* Get the checkbox
* @param {Boolean} checked - add a X or not to the checkbox
* @return {String} Composited checkbox string
*/
function getCheckbox(checked) {
return checked ? chalk.green(figures.radioOn) : figures.radioOff;
}
module.exports = CheckboxPrompt;