survey.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. 'use strict';
  2. const ArrayPrompt = require('../types/array');
  3. class Survey extends ArrayPrompt {
  4. constructor(options = {}) {
  5. super(options);
  6. this.emptyError = options.emptyError || 'No items were selected';
  7. this.term = process.env.TERM_PROGRAM;
  8. if (!this.options.header) {
  9. let header = ['', '4 - Strongly Agree', '3 - Agree', '2 - Neutral', '1 - Disagree', '0 - Strongly Disagree', ''];
  10. header = header.map(ele => this.styles.muted(ele));
  11. this.state.header = header.join('\n ');
  12. }
  13. }
  14. async toChoices(...args) {
  15. if (this.createdScales) return false;
  16. this.createdScales = true;
  17. let choices = await super.toChoices(...args);
  18. for (let choice of choices) {
  19. choice.scale = createScale(5, this.options);
  20. choice.scaleIdx = 2;
  21. }
  22. return choices;
  23. }
  24. dispatch() {
  25. this.alert();
  26. }
  27. space() {
  28. let choice = this.focused;
  29. let ele = choice.scale[choice.scaleIdx];
  30. let selected = ele.selected;
  31. choice.scale.forEach(e => (e.selected = false));
  32. ele.selected = !selected;
  33. return this.render();
  34. }
  35. indicator() {
  36. return '';
  37. }
  38. pointer() {
  39. return '';
  40. }
  41. separator() {
  42. return this.styles.muted(this.symbols.ellipsis);
  43. }
  44. right() {
  45. let choice = this.focused;
  46. if (choice.scaleIdx >= choice.scale.length - 1) return this.alert();
  47. choice.scaleIdx++;
  48. return this.render();
  49. }
  50. left() {
  51. let choice = this.focused;
  52. if (choice.scaleIdx <= 0) return this.alert();
  53. choice.scaleIdx--;
  54. return this.render();
  55. }
  56. indent() {
  57. return ' ';
  58. }
  59. async renderChoice(item, i) {
  60. await this.onChoice(item, i);
  61. let focused = this.index === i;
  62. let isHyper = this.term === 'Hyper';
  63. let n = !isHyper ? 8 : 9;
  64. let s = !isHyper ? ' ' : '';
  65. let ln = this.symbols.line.repeat(n);
  66. let sp = ' '.repeat(n + (isHyper ? 0 : 1));
  67. let dot = enabled => (enabled ? this.styles.success('◉') : '◯') + s;
  68. let num = i + 1 + '.';
  69. let color = focused ? this.styles.heading : this.styles.noop;
  70. let msg = await this.resolve(item.message, this.state, item, i);
  71. let indent = this.indent(item);
  72. let scale = indent + item.scale.map((e, i) => dot(i === item.scaleIdx)).join(ln);
  73. let val = i => i === item.scaleIdx ? color(i) : i;
  74. let next = indent + item.scale.map((e, i) => val(i)).join(sp);
  75. let line = () => [num, msg].filter(Boolean).join(' ');
  76. let lines = () => [line(), scale, next, ' '].filter(Boolean).join('\n');
  77. if (focused) {
  78. scale = this.styles.cyan(scale);
  79. next = this.styles.cyan(next);
  80. }
  81. return lines();
  82. }
  83. async renderChoices() {
  84. if (this.state.submitted) return '';
  85. let choices = this.visible.map(async(ch, i) => await this.renderChoice(ch, i));
  86. let visible = await Promise.all(choices);
  87. if (!visible.length) visible.push(this.styles.danger('No matching choices'));
  88. return visible.join('\n');
  89. }
  90. format() {
  91. if (this.state.submitted) {
  92. let values = this.choices.map(ch => this.styles.info(ch.scaleIdx));
  93. return values.join(', ');
  94. }
  95. return '';
  96. }
  97. async render() {
  98. let { submitted, size } = this.state;
  99. let prefix = await this.prefix();
  100. let separator = await this.separator();
  101. let message = await this.message();
  102. let prompt = [prefix, message, separator].filter(Boolean).join(' ');
  103. this.state.prompt = prompt;
  104. let header = await this.header();
  105. let output = await this.format();
  106. let help = await this.error() || await this.hint();
  107. let body = await this.renderChoices();
  108. let footer = await this.footer();
  109. if (output || !help) prompt += ' ' + output;
  110. if (help && !prompt.includes(help)) prompt += ' ' + help;
  111. if (submitted && !output && !body && this.multiple && this.type !== 'form') {
  112. prompt += this.styles.danger(this.emptyError);
  113. }
  114. this.clear(size);
  115. this.write([prompt, header, body, footer].filter(Boolean).join('\n'));
  116. this.restore();
  117. }
  118. submit() {
  119. this.value = {};
  120. for (let choice of this.choices) {
  121. this.value[choice.name] = choice.scaleIdx;
  122. }
  123. return this.base.submit.call(this);
  124. }
  125. }
  126. function createScale(n, options = {}) {
  127. if (Array.isArray(options.scale)) {
  128. return options.scale.map(ele => ({ ...ele }));
  129. }
  130. let scale = [];
  131. for (let i = 1; i < n + 1; i++) scale.push({ i, selected: false });
  132. return scale;
  133. }
  134. module.exports = Survey;