UNPKG

5.17 kBJavaScriptView Raw
1'use strict';
2/**
3 * `rawlist` type prompt
4 */
5
6const _ = {
7 extend: require('lodash/extend'),
8 isNumber: require('lodash/isNumber'),
9 findIndex: require('lodash/findIndex'),
10};
11const chalk = require('chalk');
12const { map, takeUntil } = require('rxjs/operators');
13const Base = require('./base');
14const Separator = require('../objects/separator');
15const observe = require('../utils/events');
16const Paginator = require('../utils/paginator');
17const incrementListIndex = require('../utils/incrementListIndex');
18
19class RawListPrompt extends Base {
20 constructor(questions, rl, answers) {
21 super(questions, rl, answers);
22
23 if (!this.opt.choices) {
24 this.throwParamError('choices');
25 }
26
27 this.opt.validChoices = this.opt.choices.filter(Separator.exclude);
28
29 this.selected = 0;
30 this.rawDefault = 0;
31
32 _.extend(this.opt, {
33 validate(val) {
34 return val != null;
35 },
36 });
37
38 const def = this.opt.default;
39 if (_.isNumber(def) && def >= 0 && def < this.opt.choices.realLength) {
40 this.selected = def;
41 this.rawDefault = def;
42 } else if (!_.isNumber(def) && def != null) {
43 const index = _.findIndex(
44 this.opt.choices.realChoices,
45 ({ value }) => value === def
46 );
47 const safeIndex = Math.max(index, 0);
48 this.selected = safeIndex;
49 this.rawDefault = safeIndex;
50 }
51
52 // Make sure no default is set (so it won't be printed)
53 this.opt.default = null;
54
55 const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop;
56 this.paginator = new Paginator(undefined, { isInfinite: shouldLoop });
57 }
58
59 /**
60 * Start the Inquiry session
61 * @param {Function} cb Callback when prompt is done
62 * @return {this}
63 */
64
65 _run(cb) {
66 this.done = cb;
67
68 // Once user confirm (enter key)
69 const events = observe(this.rl);
70 const submit = events.line.pipe(map(this.getCurrentValue.bind(this)));
71
72 const validation = this.handleSubmitEvents(submit);
73 validation.success.forEach(this.onEnd.bind(this));
74 validation.error.forEach(this.onError.bind(this));
75
76 events.normalizedUpKey
77 .pipe(takeUntil(validation.success))
78 .forEach(this.onUpKey.bind(this));
79 events.normalizedDownKey
80 .pipe(takeUntil(validation.success))
81 .forEach(this.onDownKey.bind(this));
82 events.keypress
83 .pipe(takeUntil(validation.success))
84 .forEach(this.onKeypress.bind(this));
85 // Init the prompt
86 this.render();
87
88 return this;
89 }
90
91 /**
92 * Render the prompt to screen
93 * @return {RawListPrompt} self
94 */
95
96 render(error) {
97 // Render question
98 let message = this.getQuestion();
99 let bottomContent = '';
100
101 if (this.status === 'answered') {
102 message += chalk.cyan(this.opt.choices.getChoice(this.selected).short);
103 } else {
104 const choicesStr = renderChoices(this.opt.choices, this.selected);
105 message +=
106 '\n' + this.paginator.paginate(choicesStr, this.selected, this.opt.pageSize);
107 message += '\n Answer: ';
108 }
109 message += this.rl.line;
110
111 if (error) {
112 bottomContent = '\n' + chalk.red('>> ') + error;
113 }
114
115 this.screen.render(message, bottomContent);
116 }
117
118 /**
119 * When user press `enter` key
120 */
121
122 getCurrentValue(index) {
123 if (index == null) {
124 index = this.rawDefault;
125 } else if (index === '') {
126 this.selected = this.selected === undefined ? -1 : this.selected;
127 index = this.selected;
128 } else {
129 index -= 1;
130 }
131
132 const choice = this.opt.choices.getChoice(index);
133 return choice ? choice.value : null;
134 }
135
136 onEnd(state) {
137 this.status = 'answered';
138 this.answer = state.value;
139
140 // Re-render prompt
141 this.render();
142
143 this.screen.done();
144 this.done(state.value);
145 }
146
147 onError() {
148 this.render('Please enter a valid index');
149 }
150
151 /**
152 * When user press a key
153 */
154
155 onKeypress() {
156 const index = this.rl.line.length ? Number(this.rl.line) - 1 : 0;
157
158 if (this.opt.choices.getChoice(index)) {
159 this.selected = index;
160 } else {
161 this.selected = undefined;
162 }
163 this.render();
164 }
165
166 /**
167 * When user press up key
168 */
169
170 onUpKey() {
171 this.onArrowKey('up');
172 }
173
174 /**
175 * When user press down key
176 */
177
178 onDownKey() {
179 this.onArrowKey('down');
180 }
181
182 /**
183 * When user press up or down key
184 * @param {String} type Arrow type: up or down
185 */
186
187 onArrowKey(type) {
188 this.selected = incrementListIndex(this.selected, type, this.opt);
189 this.rl.line = String(this.selected + 1);
190 }
191}
192
193/**
194 * Function for rendering list choices
195 * @param {Number} pointer Position of the pointer
196 * @return {String} Rendered content
197 */
198
199function renderChoices(choices, pointer) {
200 let output = '';
201 let separatorOffset = 0;
202
203 choices.forEach((choice, i) => {
204 output += '\n ';
205
206 if (choice.type === 'separator') {
207 separatorOffset++;
208 output += ' ' + choice;
209 return;
210 }
211
212 const index = i - separatorOffset;
213 let display = index + 1 + ') ' + choice.name;
214 if (index === pointer) {
215 display = chalk.cyan(display);
216 }
217
218 output += display;
219 });
220
221 return output;
222}
223
224module.exports = RawListPrompt;