Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
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.

array.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. 'use strict';
  2. const colors = require('ansi-colors');
  3. const Prompt = require('../prompt');
  4. const roles = require('../roles');
  5. const utils = require('../utils');
  6. const { reorder, scrollUp, scrollDown, isObject, swap } = utils;
  7. class ArrayPrompt extends Prompt {
  8. constructor(options) {
  9. super(options);
  10. this.cursorHide();
  11. this.maxSelected = options.maxSelected || Infinity;
  12. this.multiple = options.multiple || false;
  13. this.initial = options.initial || 0;
  14. this.delay = options.delay || 0;
  15. this.longest = 0;
  16. this.num = '';
  17. }
  18. async initialize() {
  19. if (typeof this.options.initial === 'function') {
  20. this.initial = await this.options.initial.call(this);
  21. }
  22. await this.reset(true);
  23. await super.initialize();
  24. }
  25. async reset() {
  26. let { choices, initial, autofocus, suggest } = this.options;
  27. this.state._choices = [];
  28. this.state.choices = [];
  29. this.choices = await Promise.all(await this.toChoices(choices));
  30. this.choices.forEach(ch => (ch.enabled = false));
  31. if (typeof suggest !== 'function' && this.selectable.length === 0) {
  32. throw new Error('At least one choice must be selectable');
  33. }
  34. if (isObject(initial)) initial = Object.keys(initial);
  35. if (Array.isArray(initial)) {
  36. if (autofocus != null) this.index = this.findIndex(autofocus);
  37. initial.forEach(v => this.enable(this.find(v)));
  38. await this.render();
  39. } else {
  40. if (autofocus != null) initial = autofocus;
  41. if (typeof initial === 'string') initial = this.findIndex(initial);
  42. if (typeof initial === 'number' && initial > -1) {
  43. this.index = Math.max(0, Math.min(initial, this.choices.length));
  44. this.enable(this.find(this.index));
  45. }
  46. }
  47. if (this.isDisabled(this.focused)) {
  48. await this.down();
  49. }
  50. }
  51. async toChoices(value, parent) {
  52. this.state.loadingChoices = true;
  53. let choices = [];
  54. let index = 0;
  55. let toChoices = async(items, parent) => {
  56. if (typeof items === 'function') items = await items.call(this);
  57. if (items instanceof Promise) items = await items;
  58. for (let i = 0; i < items.length; i++) {
  59. let choice = items[i] = await this.toChoice(items[i], index++, parent);
  60. choices.push(choice);
  61. if (choice.choices) {
  62. await toChoices(choice.choices, choice);
  63. }
  64. }
  65. return choices;
  66. };
  67. return toChoices(value, parent)
  68. .then(choices => {
  69. this.state.loadingChoices = false;
  70. return choices;
  71. });
  72. }
  73. async toChoice(ele, i, parent) {
  74. if (typeof ele === 'function') ele = await ele.call(this, this);
  75. if (ele instanceof Promise) ele = await ele;
  76. if (typeof ele === 'string') ele = { name: ele };
  77. if (ele.normalized) return ele;
  78. ele.normalized = true;
  79. let origVal = ele.value;
  80. let role = roles(ele.role, this.options);
  81. ele = role(this, ele);
  82. if (typeof ele.disabled === 'string' && !ele.hint) {
  83. ele.hint = ele.disabled;
  84. ele.disabled = true;
  85. }
  86. if (ele.disabled === true && ele.hint == null) {
  87. ele.hint = '(disabled)';
  88. }
  89. // if the choice was already normalized, return it
  90. if (ele.index != null) return ele;
  91. ele.name = ele.name || ele.key || ele.title || ele.value || ele.message;
  92. ele.message = ele.message || ele.name || '';
  93. ele.value = [ele.value, ele.name].find(this.isValue.bind(this));
  94. ele.input = '';
  95. ele.index = i;
  96. ele.cursor = 0;
  97. utils.define(ele, 'parent', parent);
  98. ele.level = parent ? parent.level + 1 : 1;
  99. if (ele.indent == null) {
  100. ele.indent = parent ? parent.indent + ' ' : (ele.indent || '');
  101. }
  102. ele.path = parent ? parent.path + '.' + ele.name : ele.name;
  103. ele.enabled = !!(this.multiple && !this.isDisabled(ele) && (ele.enabled || this.isSelected(ele)));
  104. if (!this.isDisabled(ele)) {
  105. this.longest = Math.max(this.longest, colors.unstyle(ele.message).length);
  106. }
  107. // shallow clone the choice first
  108. let choice = { ...ele };
  109. // then allow the choice to be reset using the "original" values
  110. ele.reset = (input = choice.input, value = choice.value) => {
  111. for (let key of Object.keys(choice)) ele[key] = choice[key];
  112. ele.input = input;
  113. ele.value = value;
  114. };
  115. if (origVal == null && typeof ele.initial === 'function') {
  116. ele.input = await ele.initial.call(this, this.state, ele, i);
  117. }
  118. return ele;
  119. }
  120. async onChoice(choice, i) {
  121. this.emit('choice', choice, i, this);
  122. if (typeof choice.onChoice === 'function') {
  123. await choice.onChoice.call(this, this.state, choice, i);
  124. }
  125. }
  126. async addChoice(ele, i, parent) {
  127. let choice = await this.toChoice(ele, i, parent);
  128. this.choices.push(choice);
  129. this.index = this.choices.length - 1;
  130. this.limit = this.choices.length;
  131. return choice;
  132. }
  133. async newItem(item, i, parent) {
  134. let ele = { name: 'New choice name?', editable: true, newChoice: true, ...item };
  135. let choice = await this.addChoice(ele, i, parent);
  136. choice.updateChoice = () => {
  137. delete choice.newChoice;
  138. choice.name = choice.message = choice.input;
  139. choice.input = '';
  140. choice.cursor = 0;
  141. };
  142. return this.render();
  143. }
  144. indent(choice) {
  145. if (choice.indent == null) {
  146. return choice.level > 1 ? ' '.repeat(choice.level - 1) : '';
  147. }
  148. return choice.indent;
  149. }
  150. dispatch(s, key) {
  151. if (this.multiple && this[key.name]) return this[key.name]();
  152. this.alert();
  153. }
  154. focus(choice, enabled) {
  155. if (typeof enabled !== 'boolean') enabled = choice.enabled;
  156. if (enabled && !choice.enabled && this.selected.length >= this.maxSelected) {
  157. return this.alert();
  158. }
  159. this.index = choice.index;
  160. choice.enabled = enabled && !this.isDisabled(choice);
  161. return choice;
  162. }
  163. space() {
  164. if (!this.multiple) return this.alert();
  165. this.toggle(this.focused);
  166. return this.render();
  167. }
  168. a() {
  169. if (this.maxSelected < this.choices.length) return this.alert();
  170. let enabled = this.selectable.every(ch => ch.enabled);
  171. this.choices.forEach(ch => (ch.enabled = !enabled));
  172. return this.render();
  173. }
  174. i() {
  175. // don't allow choices to be inverted if it will result in
  176. // more than the maximum number of allowed selected items.
  177. if (this.choices.length - this.selected.length > this.maxSelected) {
  178. return this.alert();
  179. }
  180. this.choices.forEach(ch => (ch.enabled = !ch.enabled));
  181. return this.render();
  182. }
  183. g(choice = this.focused) {
  184. if (!this.choices.some(ch => !!ch.parent)) return this.a();
  185. this.toggle((choice.parent && !choice.choices) ? choice.parent : choice);
  186. return this.render();
  187. }
  188. toggle(choice, enabled) {
  189. if (!choice.enabled && this.selected.length >= this.maxSelected) {
  190. return this.alert();
  191. }
  192. if (typeof enabled !== 'boolean') enabled = !choice.enabled;
  193. choice.enabled = enabled;
  194. if (choice.choices) {
  195. choice.choices.forEach(ch => this.toggle(ch, enabled));
  196. }
  197. let parent = choice.parent;
  198. while (parent) {
  199. let choices = parent.choices.filter(ch => this.isDisabled(ch));
  200. parent.enabled = choices.every(ch => ch.enabled === true);
  201. parent = parent.parent;
  202. }
  203. reset(this, this.choices);
  204. this.emit('toggle', choice, this);
  205. return choice;
  206. }
  207. enable(choice) {
  208. if (this.selected.length >= this.maxSelected) return this.alert();
  209. choice.enabled = !this.isDisabled(choice);
  210. choice.choices && choice.choices.forEach(this.enable.bind(this));
  211. return choice;
  212. }
  213. disable(choice) {
  214. choice.enabled = false;
  215. choice.choices && choice.choices.forEach(this.disable.bind(this));
  216. return choice;
  217. }
  218. number(n) {
  219. this.num += n;
  220. let number = num => {
  221. let i = Number(num);
  222. if (i > this.choices.length - 1) return this.alert();
  223. let focused = this.focused;
  224. let choice = this.choices.find(ch => i === ch.index);
  225. if (!choice.enabled && this.selected.length >= this.maxSelected) {
  226. return this.alert();
  227. }
  228. if (this.visible.indexOf(choice) === -1) {
  229. let choices = reorder(this.choices);
  230. let actualIdx = choices.indexOf(choice);
  231. if (focused.index > actualIdx) {
  232. let start = choices.slice(actualIdx, actualIdx + this.limit);
  233. let end = choices.filter(ch => !start.includes(ch));
  234. this.choices = start.concat(end);
  235. } else {
  236. let pos = actualIdx - this.limit + 1;
  237. this.choices = choices.slice(pos).concat(choices.slice(0, pos));
  238. }
  239. }
  240. this.index = this.choices.indexOf(choice);
  241. this.toggle(this.focused);
  242. return this.render();
  243. };
  244. clearTimeout(this.numberTimeout);
  245. return new Promise(resolve => {
  246. let len = this.choices.length;
  247. let num = this.num;
  248. let handle = (val = false, res) => {
  249. clearTimeout(this.numberTimeout);
  250. if (val) res = number(num);
  251. this.num = '';
  252. resolve(res);
  253. };
  254. if (num === '0' || (num.length === 1 && Number(num + '0') > len)) {
  255. return handle(true);
  256. }
  257. if (Number(num) > len) {
  258. return handle(false, this.alert());
  259. }
  260. this.numberTimeout = setTimeout(() => handle(true), this.delay);
  261. });
  262. }
  263. home() {
  264. this.choices = reorder(this.choices);
  265. this.index = 0;
  266. return this.render();
  267. }
  268. end() {
  269. let pos = this.choices.length - this.limit;
  270. let choices = reorder(this.choices);
  271. this.choices = choices.slice(pos).concat(choices.slice(0, pos));
  272. this.index = this.limit - 1;
  273. return this.render();
  274. }
  275. first() {
  276. this.index = 0;
  277. return this.render();
  278. }
  279. last() {
  280. this.index = this.visible.length - 1;
  281. return this.render();
  282. }
  283. prev() {
  284. if (this.visible.length <= 1) return this.alert();
  285. return this.up();
  286. }
  287. next() {
  288. if (this.visible.length <= 1) return this.alert();
  289. return this.down();
  290. }
  291. right() {
  292. if (this.cursor >= this.input.length) return this.alert();
  293. this.cursor++;
  294. return this.render();
  295. }
  296. left() {
  297. if (this.cursor <= 0) return this.alert();
  298. this.cursor--;
  299. return this.render();
  300. }
  301. up() {
  302. let len = this.choices.length;
  303. let vis = this.visible.length;
  304. let idx = this.index;
  305. if (this.options.scroll === false && idx === 0) {
  306. return this.alert();
  307. }
  308. if (len > vis && idx === 0) {
  309. return this.scrollUp();
  310. }
  311. this.index = ((idx - 1 % len) + len) % len;
  312. if (this.isDisabled()) {
  313. return this.up();
  314. }
  315. return this.render();
  316. }
  317. down() {
  318. let len = this.choices.length;
  319. let vis = this.visible.length;
  320. let idx = this.index;
  321. if (this.options.scroll === false && idx === vis - 1) {
  322. return this.alert();
  323. }
  324. if (len > vis && idx === vis - 1) {
  325. return this.scrollDown();
  326. }
  327. this.index = (idx + 1) % len;
  328. if (this.isDisabled()) {
  329. return this.down();
  330. }
  331. return this.render();
  332. }
  333. scrollUp(i = 0) {
  334. this.choices = scrollUp(this.choices);
  335. this.index = i;
  336. if (this.isDisabled()) {
  337. return this.up();
  338. }
  339. return this.render();
  340. }
  341. scrollDown(i = this.visible.length - 1) {
  342. this.choices = scrollDown(this.choices);
  343. this.index = i;
  344. if (this.isDisabled()) {
  345. return this.down();
  346. }
  347. return this.render();
  348. }
  349. async shiftUp() {
  350. if (this.options.sort === true) {
  351. this.sorting = true;
  352. this.swap(this.index - 1);
  353. await this.up();
  354. this.sorting = false;
  355. return;
  356. }
  357. return this.scrollUp(this.index);
  358. }
  359. async shiftDown() {
  360. if (this.options.sort === true) {
  361. this.sorting = true;
  362. this.swap(this.index + 1);
  363. await this.down();
  364. this.sorting = false;
  365. return;
  366. }
  367. return this.scrollDown(this.index);
  368. }
  369. pageUp() {
  370. if (this.visible.length <= 1) return this.alert();
  371. this.limit = Math.max(this.limit - 1, 0);
  372. this.index = Math.min(this.limit - 1, this.index);
  373. this._limit = this.limit;
  374. if (this.isDisabled()) {
  375. return this.up();
  376. }
  377. return this.render();
  378. }
  379. pageDown() {
  380. if (this.visible.length >= this.choices.length) return this.alert();
  381. this.index = Math.max(0, this.index);
  382. this.limit = Math.min(this.limit + 1, this.choices.length);
  383. this._limit = this.limit;
  384. if (this.isDisabled()) {
  385. return this.down();
  386. }
  387. return this.render();
  388. }
  389. swap(pos) {
  390. swap(this.choices, this.index, pos);
  391. }
  392. isDisabled(choice = this.focused) {
  393. let keys = ['disabled', 'collapsed', 'hidden', 'completing', 'readonly'];
  394. if (choice && keys.some(key => choice[key] === true)) {
  395. return true;
  396. }
  397. return choice && choice.role === 'heading';
  398. }
  399. isEnabled(choice = this.focused) {
  400. if (Array.isArray(choice)) return choice.every(ch => this.isEnabled(ch));
  401. if (choice.choices) {
  402. let choices = choice.choices.filter(ch => !this.isDisabled(ch));
  403. return choice.enabled && choices.every(ch => this.isEnabled(ch));
  404. }
  405. return choice.enabled && !this.isDisabled(choice);
  406. }
  407. isChoice(choice, value) {
  408. return choice.name === value || choice.index === Number(value);
  409. }
  410. isSelected(choice) {
  411. if (Array.isArray(this.initial)) {
  412. return this.initial.some(value => this.isChoice(choice, value));
  413. }
  414. return this.isChoice(choice, this.initial);
  415. }
  416. map(names = [], prop = 'value') {
  417. return [].concat(names || []).reduce((acc, name) => {
  418. acc[name] = this.find(name, prop);
  419. return acc;
  420. }, {});
  421. }
  422. filter(value, prop) {
  423. let isChoice = (ele, i) => [ele.name, i].includes(value);
  424. let fn = typeof value === 'function' ? value : isChoice;
  425. let choices = this.options.multiple ? this.state._choices : this.choices;
  426. let result = choices.filter(fn);
  427. if (prop) {
  428. return result.map(ch => ch[prop]);
  429. }
  430. return result;
  431. }
  432. find(value, prop) {
  433. if (isObject(value)) return prop ? value[prop] : value;
  434. let isChoice = (ele, i) => [ele.name, i].includes(value);
  435. let fn = typeof value === 'function' ? value : isChoice;
  436. let choice = this.choices.find(fn);
  437. if (choice) {
  438. return prop ? choice[prop] : choice;
  439. }
  440. }
  441. findIndex(value) {
  442. return this.choices.indexOf(this.find(value));
  443. }
  444. async submit() {
  445. let choice = this.focused;
  446. if (!choice) return this.alert();
  447. if (choice.newChoice) {
  448. if (!choice.input) return this.alert();
  449. choice.updateChoice();
  450. return this.render();
  451. }
  452. if (this.choices.some(ch => ch.newChoice)) {
  453. return this.alert();
  454. }
  455. let { reorder, sort } = this.options;
  456. let multi = this.multiple === true;
  457. let value = this.selected;
  458. if (value === void 0) {
  459. return this.alert();
  460. }
  461. // re-sort choices to original order
  462. if (Array.isArray(value) && reorder !== false && sort !== true) {
  463. value = utils.reorder(value);
  464. }
  465. this.value = multi ? value.map(ch => ch.name) : value.name;
  466. return super.submit();
  467. }
  468. set choices(choices = []) {
  469. this.state._choices = this.state._choices || [];
  470. this.state.choices = choices;
  471. for (let choice of choices) {
  472. if (!this.state._choices.some(ch => ch.name === choice.name)) {
  473. this.state._choices.push(choice);
  474. }
  475. }
  476. if (!this._initial && this.options.initial) {
  477. this._initial = true;
  478. let init = this.initial;
  479. if (typeof init === 'string' || typeof init === 'number') {
  480. let choice = this.find(init);
  481. if (choice) {
  482. this.initial = choice.index;
  483. this.focus(choice, true);
  484. }
  485. }
  486. }
  487. }
  488. get choices() {
  489. return reset(this, this.state.choices || []);
  490. }
  491. set visible(visible) {
  492. this.state.visible = visible;
  493. }
  494. get visible() {
  495. return (this.state.visible || this.choices).slice(0, this.limit);
  496. }
  497. set limit(num) {
  498. this.state.limit = num;
  499. }
  500. get limit() {
  501. let { state, options, choices } = this;
  502. let limit = state.limit || this._limit || options.limit || choices.length;
  503. return Math.min(limit, this.height);
  504. }
  505. set value(value) {
  506. super.value = value;
  507. }
  508. get value() {
  509. if (typeof super.value !== 'string' && super.value === this.initial) {
  510. return this.input;
  511. }
  512. return super.value;
  513. }
  514. set index(i) {
  515. this.state.index = i;
  516. }
  517. get index() {
  518. return Math.max(0, this.state ? this.state.index : 0);
  519. }
  520. get enabled() {
  521. return this.filter(this.isEnabled.bind(this));
  522. }
  523. get focused() {
  524. let choice = this.choices[this.index];
  525. if (choice && this.state.submitted && this.multiple !== true) {
  526. choice.enabled = true;
  527. }
  528. return choice;
  529. }
  530. get selectable() {
  531. return this.choices.filter(choice => !this.isDisabled(choice));
  532. }
  533. get selected() {
  534. return this.multiple ? this.enabled : this.focused;
  535. }
  536. }
  537. function reset(prompt, choices) {
  538. if (choices instanceof Promise) return choices;
  539. if (typeof choices === 'function') {
  540. if (utils.isAsyncFn(choices)) return choices;
  541. choices = choices.call(prompt, prompt);
  542. }
  543. for (let choice of choices) {
  544. if (Array.isArray(choice.choices)) {
  545. let items = choice.choices.filter(ch => !prompt.isDisabled(ch));
  546. choice.enabled = items.every(ch => ch.enabled === true);
  547. }
  548. if (prompt.isDisabled(choice) === true) {
  549. delete choice.enabled;
  550. }
  551. }
  552. return choices;
  553. }
  554. module.exports = ArrayPrompt;