source: frontend/node_modules/prompts/lib/elements/multiselect.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 7.1 KB
Line 
1'use strict';
2
3const color = require('kleur');
4const { cursor } = require('sisteransi');
5const Prompt = require('./prompt');
6const { clear, figures, style, wrap, entriesToDisplay } = require('../util');
7
8/**
9 * MultiselectPrompt Base Element
10 * @param {Object} opts Options
11 * @param {String} opts.message Message
12 * @param {Array} opts.choices Array of choice objects
13 * @param {String} [opts.hint] Hint to display
14 * @param {String} [opts.warn] Hint shown for disabled choices
15 * @param {Number} [opts.max] Max choices
16 * @param {Number} [opts.cursor=0] Cursor start position
17 * @param {Number} [opts.optionsPerPage=10] Max options to display at once
18 * @param {Stream} [opts.stdin] The Readable stream to listen to
19 * @param {Stream} [opts.stdout] The Writable stream to write readline data to
20 */
21class MultiselectPrompt extends Prompt {
22 constructor(opts={}) {
23 super(opts);
24 this.msg = opts.message;
25 this.cursor = opts.cursor || 0;
26 this.scrollIndex = opts.cursor || 0;
27 this.hint = opts.hint || '';
28 this.warn = opts.warn || '- This option is disabled -';
29 this.minSelected = opts.min;
30 this.showMinError = false;
31 this.maxChoices = opts.max;
32 this.instructions = opts.instructions;
33 this.optionsPerPage = opts.optionsPerPage || 10;
34 this.value = opts.choices.map((ch, idx) => {
35 if (typeof ch === 'string')
36 ch = {title: ch, value: idx};
37 return {
38 title: ch && (ch.title || ch.value || ch),
39 description: ch && ch.description,
40 value: ch && (ch.value === undefined ? idx : ch.value),
41 selected: ch && ch.selected,
42 disabled: ch && ch.disabled
43 };
44 });
45 this.clear = clear('', this.out.columns);
46 if (!opts.overrideRender) {
47 this.render();
48 }
49 }
50
51 reset() {
52 this.value.map(v => !v.selected);
53 this.cursor = 0;
54 this.fire();
55 this.render();
56 }
57
58 selected() {
59 return this.value.filter(v => v.selected);
60 }
61
62 exit() {
63 this.abort();
64 }
65
66 abort() {
67 this.done = this.aborted = true;
68 this.fire();
69 this.render();
70 this.out.write('\n');
71 this.close();
72 }
73
74 submit() {
75 const selected = this.value
76 .filter(e => e.selected);
77 if (this.minSelected && selected.length < this.minSelected) {
78 this.showMinError = true;
79 this.render();
80 } else {
81 this.done = true;
82 this.aborted = false;
83 this.fire();
84 this.render();
85 this.out.write('\n');
86 this.close();
87 }
88 }
89
90 first() {
91 this.cursor = 0;
92 this.render();
93 }
94
95 last() {
96 this.cursor = this.value.length - 1;
97 this.render();
98 }
99 next() {
100 this.cursor = (this.cursor + 1) % this.value.length;
101 this.render();
102 }
103
104 up() {
105 if (this.cursor === 0) {
106 this.cursor = this.value.length - 1;
107 } else {
108 this.cursor--;
109 }
110 this.render();
111 }
112
113 down() {
114 if (this.cursor === this.value.length - 1) {
115 this.cursor = 0;
116 } else {
117 this.cursor++;
118 }
119 this.render();
120 }
121
122 left() {
123 this.value[this.cursor].selected = false;
124 this.render();
125 }
126
127 right() {
128 if (this.value.filter(e => e.selected).length >= this.maxChoices) return this.bell();
129 this.value[this.cursor].selected = true;
130 this.render();
131 }
132
133 handleSpaceToggle() {
134 const v = this.value[this.cursor];
135
136 if (v.selected) {
137 v.selected = false;
138 this.render();
139 } else if (v.disabled || this.value.filter(e => e.selected).length >= this.maxChoices) {
140 return this.bell();
141 } else {
142 v.selected = true;
143 this.render();
144 }
145 }
146
147 toggleAll() {
148 if (this.maxChoices !== undefined || this.value[this.cursor].disabled) {
149 return this.bell();
150 }
151
152 const newSelected = !this.value[this.cursor].selected;
153 this.value.filter(v => !v.disabled).forEach(v => v.selected = newSelected);
154 this.render();
155 }
156
157 _(c, key) {
158 if (c === ' ') {
159 this.handleSpaceToggle();
160 } else if (c === 'a') {
161 this.toggleAll();
162 } else {
163 return this.bell();
164 }
165 }
166
167 renderInstructions() {
168 if (this.instructions === undefined || this.instructions) {
169 if (typeof this.instructions === 'string') {
170 return this.instructions;
171 }
172 return '\nInstructions:\n'
173 + ` ${figures.arrowUp}/${figures.arrowDown}: Highlight option\n`
174 + ` ${figures.arrowLeft}/${figures.arrowRight}/[space]: Toggle selection\n`
175 + (this.maxChoices === undefined ? ` a: Toggle all\n` : '')
176 + ` enter/return: Complete answer`;
177 }
178 return '';
179 }
180
181 renderOption(cursor, v, i, arrowIndicator) {
182 const prefix = (v.selected ? color.green(figures.radioOn) : figures.radioOff) + ' ' + arrowIndicator + ' ';
183 let title, desc;
184
185 if (v.disabled) {
186 title = cursor === i ? color.gray().underline(v.title) : color.strikethrough().gray(v.title);
187 } else {
188 title = cursor === i ? color.cyan().underline(v.title) : v.title;
189 if (cursor === i && v.description) {
190 desc = ` - ${v.description}`;
191 if (prefix.length + title.length + desc.length >= this.out.columns
192 || v.description.split(/\r?\n/).length > 1) {
193 desc = '\n' + wrap(v.description, { margin: prefix.length, width: this.out.columns });
194 }
195 }
196 }
197
198 return prefix + title + color.gray(desc || '');
199 }
200
201 // shared with autocompleteMultiselect
202 paginateOptions(options) {
203 if (options.length === 0) {
204 return color.red('No matches for this query.');
205 }
206
207 let { startIndex, endIndex } = entriesToDisplay(this.cursor, options.length, this.optionsPerPage);
208 let prefix, styledOptions = [];
209
210 for (let i = startIndex; i < endIndex; i++) {
211 if (i === startIndex && startIndex > 0) {
212 prefix = figures.arrowUp;
213 } else if (i === endIndex - 1 && endIndex < options.length) {
214 prefix = figures.arrowDown;
215 } else {
216 prefix = ' ';
217 }
218 styledOptions.push(this.renderOption(this.cursor, options[i], i, prefix));
219 }
220
221 return '\n' + styledOptions.join('\n');
222 }
223
224 // shared with autocomleteMultiselect
225 renderOptions(options) {
226 if (!this.done) {
227 return this.paginateOptions(options);
228 }
229 return '';
230 }
231
232 renderDoneOrInstructions() {
233 if (this.done) {
234 return this.value
235 .filter(e => e.selected)
236 .map(v => v.title)
237 .join(', ');
238 }
239
240 const output = [color.gray(this.hint), this.renderInstructions()];
241
242 if (this.value[this.cursor].disabled) {
243 output.push(color.yellow(this.warn));
244 }
245 return output.join(' ');
246 }
247
248 render() {
249 if (this.closed) return;
250 if (this.firstRender) this.out.write(cursor.hide);
251 super.render();
252
253 // print prompt
254 let prompt = [
255 style.symbol(this.done, this.aborted),
256 color.bold(this.msg),
257 style.delimiter(false),
258 this.renderDoneOrInstructions()
259 ].join(' ');
260 if (this.showMinError) {
261 prompt += color.red(`You must select a minimum of ${this.minSelected} choices.`);
262 this.showMinError = false;
263 }
264 prompt += this.renderOptions(this.value);
265
266 this.out.write(this.clear + prompt);
267 this.clear = clear(prompt, this.out.columns);
268 }
269}
270
271module.exports = MultiselectPrompt;
Note: See TracBrowser for help on using the repository browser.