mumuki-laboratory 7.12.1 → 8.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +1 -1
  3. data/app/assets/javascripts/mumuki_laboratory/application/button.js +2 -2
  4. data/app/assets/javascripts/mumuki_laboratory/application/codemirror-builder.js +2 -2
  5. data/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +5 -5
  6. data/app/assets/javascripts/mumuki_laboratory/application/console.js +2 -2
  7. data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +7 -7
  8. data/app/assets/javascripts/mumuki_laboratory/application/editors.js +1 -1
  9. data/app/assets/javascripts/mumuki_laboratory/application/elipsis.js +1 -1
  10. data/app/assets/javascripts/mumuki_laboratory/application/events.js +2 -2
  11. data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +2 -2
  12. data/app/assets/javascripts/mumuki_laboratory/application/gamification.js +99 -11
  13. data/app/assets/javascripts/mumuki_laboratory/application/inputs.js +1 -1
  14. data/app/assets/javascripts/mumuki_laboratory/application/kids.js +20 -8
  15. data/app/assets/javascripts/mumuki_laboratory/application/kindergarten.js +16 -57
  16. data/app/assets/javascripts/mumuki_laboratory/application/messages.js +6 -6
  17. data/app/assets/javascripts/mumuki_laboratory/application/mu-modal-carrousel.js +63 -0
  18. data/app/assets/javascripts/mumuki_laboratory/application/multiple-choice.js +1 -1
  19. data/app/assets/javascripts/mumuki_laboratory/application/multiple-files.js +3 -3
  20. data/app/assets/javascripts/mumuki_laboratory/application/multiple-scenarios.js +1 -1
  21. data/app/assets/javascripts/mumuki_laboratory/application/number-counter.js +18 -0
  22. data/app/assets/javascripts/mumuki_laboratory/application/pin.js +1 -1
  23. data/app/assets/javascripts/mumuki_laboratory/application/primary.js +3 -2
  24. data/app/assets/javascripts/mumuki_laboratory/application/progress.js +1 -1
  25. data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +1 -1
  26. data/app/assets/javascripts/mumuki_laboratory/application/speech-bubble-renderer.js +4 -4
  27. data/app/assets/javascripts/mumuki_laboratory/application/submission.js +3 -3
  28. data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +3 -3
  29. data/app/assets/javascripts/mumuki_laboratory/application/sync-mode.js +2 -2
  30. data/app/assets/javascripts/mumuki_laboratory/application/timer.js +1 -1
  31. data/app/assets/stylesheets/mumuki_laboratory/application/_layout.scss +7 -7
  32. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids_results.scss +117 -0
  33. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +45 -131
  34. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_terms.scss +9 -12
  35. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_profile.scss +31 -3
  36. data/app/controllers/api/courses_controller.rb +1 -2
  37. data/app/controllers/api/organizations_controller.rb +2 -3
  38. data/app/controllers/api/users_controller.rb +2 -4
  39. data/app/controllers/application_controller.rb +41 -8
  40. data/app/controllers/assets_controller.rb +1 -0
  41. data/app/controllers/book_discussions_controller.rb +1 -1
  42. data/app/controllers/chapters_controller.rb +1 -0
  43. data/app/controllers/concerns/with_user_discussion_validation.rb +6 -0
  44. data/app/controllers/discussions_controller.rb +4 -6
  45. data/app/controllers/exercises_controller.rb +1 -0
  46. data/app/controllers/guides_controller.rb +2 -0
  47. data/app/controllers/invitations_controller.rb +2 -2
  48. data/app/controllers/lessons_controller.rb +1 -0
  49. data/app/controllers/login_controller.rb +1 -0
  50. data/app/controllers/users_controller.rb +9 -1
  51. data/app/helpers/assistance_box_helper.rb +7 -5
  52. data/app/helpers/gamification_helper.rb +5 -0
  53. data/app/helpers/links_helper.rb +2 -2
  54. data/app/views/exercise_solutions/_assistant_rules_box.html.erb +13 -0
  55. data/app/views/exercise_solutions/_contextualization_results_container.html.erb +9 -0
  56. data/app/views/exercise_solutions/_kids_level_up.html.erb +11 -0
  57. data/app/views/exercise_solutions/_results.html.erb +19 -19
  58. data/app/views/exercise_solutions/_results_title.html.erb +5 -0
  59. data/app/views/exercises/show.html.erb +4 -1
  60. data/app/views/layouts/_copyright.html.erb +1 -1
  61. data/app/views/layouts/_kindergarten.html.erb +1 -1
  62. data/app/views/layouts/_organizations_listing.html.erb +8 -12
  63. data/app/views/layouts/application.html.erb +23 -15
  64. data/app/views/layouts/embedded.html.erb +14 -11
  65. data/app/views/layouts/modals/_guide_corollary.html.erb +1 -1
  66. data/app/views/layouts/modals/_kids_context.html.erb +1 -1
  67. data/app/views/layouts/modals/_kids_results.html.erb +16 -6
  68. data/app/views/layouts/modals/_kindergarten_context.html.erb +15 -15
  69. data/app/views/layouts/modals/_kindergarten_results.html.erb +20 -7
  70. data/app/views/layouts/modals/_kindergarten_results_aborted.html.erb +4 -4
  71. data/app/views/layouts/modals/_level_up.html.erb +27 -0
  72. data/app/views/users/_edit_user_form.html.erb +1 -1
  73. data/app/views/users/_user_form.html.erb +12 -2
  74. data/app/views/users/terms.html.erb +12 -0
  75. data/config/routes.rb +8 -8
  76. data/lib/mumuki/laboratory.rb +18 -5
  77. data/lib/mumuki/laboratory/controllers.rb +2 -0
  78. data/lib/mumuki/laboratory/controllers/action_redirector.rb +21 -0
  79. data/lib/mumuki/laboratory/controllers/immersive_navigation.rb +7 -0
  80. data/lib/mumuki/laboratory/controllers/results_rendering.rb +1 -0
  81. data/lib/mumuki/laboratory/events/events.rb +0 -33
  82. data/lib/mumuki/laboratory/locales/en.yml +5 -0
  83. data/lib/mumuki/laboratory/locales/es-CL.yml +6 -1
  84. data/lib/mumuki/laboratory/locales/es.yml +7 -2
  85. data/lib/mumuki/laboratory/locales/pt.yml +6 -1
  86. data/lib/mumuki/laboratory/version.rb +1 -1
  87. data/spec/capybara_helper.rb +5 -1
  88. data/spec/controllers/discussions_controller_spec.rb +19 -0
  89. data/spec/controllers/exercise_solutions_controller_spec.rb +1 -1
  90. data/spec/dummy/db/schema.rb +2 -1
  91. data/spec/features/immersive_redirection_spec.rb +181 -0
  92. data/spec/features/profile_flow_spec.rb +35 -3
  93. data/spec/features/terms_flow_spec.rb +155 -0
  94. metadata +110 -101
  95. data/app/helpers/organization_list_helper.rb +0 -5
  96. data/spec/features/choose_organization_spec.rb +0 -74
@@ -1,7 +1,7 @@
1
1
  mumuki.load(() => {
2
2
  var Chat = {
3
3
  $body: function () {
4
- return $('body')
4
+ return $('body');
5
5
  },
6
6
  $newMessageModal: function () {
7
7
  return $('.new-message-modal');
@@ -18,7 +18,7 @@ mumuki.load(() => {
18
18
  $('.notifications-box').toggleClass('notifications-box-empty', !data.has_messages);
19
19
  $('.pending-messages-filter').removeClass('pending-messages-filter');
20
20
  $('button.btn-submit').removeClass('disabled');
21
- $('.pending-messages-text').remove()
21
+ $('.pending-messages-text').remove();
22
22
  },
23
23
  readMessages: function (url) {
24
24
  Chat.tokenRequest({
@@ -26,10 +26,10 @@ mumuki.load(() => {
26
26
  method: 'POST',
27
27
  success: Chat.setMessages,
28
28
  xhrFields: {withCredentials: true}
29
- })
29
+ });
30
30
  },
31
31
  tokenRequest: function (data) {
32
- $.ajax(Chat.token.newRequest(data))
32
+ $.ajax(Chat.token.newRequest(data));
33
33
  },
34
34
  getMessages: function () {
35
35
  if ($('.badge-messages').length == 0) {
@@ -74,7 +74,7 @@ mumuki.load(() => {
74
74
  success: success,
75
75
  error: error,
76
76
  xhrFields: {withCredentials: true}
77
- })
77
+ });
78
78
  },
79
79
  openNewMessageModal: function () {
80
80
  Chat.$newMessageModal().modal({backdrop: false, keyboard: false});
@@ -89,5 +89,5 @@ mumuki.load(() => {
89
89
  Chat.setMessagesInterval();
90
90
  mumuki.Chat = Chat;
91
91
 
92
- })
92
+ });
93
93
 
@@ -0,0 +1,63 @@
1
+ mumuki.ModalCarrousel = (() => {
2
+
3
+ class MuModalCarrousel {
4
+ constructor(containerSelector, onShow = () => {}) {
5
+ this.$container = $(containerSelector);
6
+ this.onShow = onShow;
7
+ }
8
+
9
+ show() {
10
+ this.onShow();
11
+ this._showFirstSlide();
12
+ }
13
+
14
+ nextSlide() {
15
+ this._clickButton('next');
16
+ }
17
+
18
+ prevSlide() {
19
+ this._clickButton('prev');
20
+ }
21
+
22
+ _activeSlide() {
23
+ return this.$container.find('.active');
24
+ }
25
+
26
+ _clickButton(prevOrNext) {
27
+ this._activeSlide().removeClass('active')[prevOrNext]().addClass('active');
28
+ this.showNextOrCloseButton();
29
+ this._hidePreviousButtonIfFirstSlide();
30
+ }
31
+
32
+ showNextOrCloseButton() {
33
+ const $next = $('.mu-kids-modal-button.mu-next');
34
+ const $close = $('.mu-kids-modal-button.mu-close');
35
+ const $footer = $('.modal-footer');
36
+ const isLastChild = this._activeSlide().is(':last-child');
37
+ this._addClassIf($next, 'hidden', () => isLastChild);
38
+ this._addClassIf($close, 'hidden', () => !isLastChild);
39
+ this._addClassIf($footer, 'hidden', () => !isLastChild);
40
+ }
41
+
42
+ _hidePreviousButtonIfFirstSlide() {
43
+ const $prev = $('.mu-kids-modal-button.mu-previous');
44
+ this._addClassIf($prev, 'hidden', () => this._activeSlide().is(':first-child'));
45
+ }
46
+
47
+ _showFirstSlide() {
48
+ this.$container.children().each((i, el) => this._addClassIf($(el), 'active', () => i === 0));
49
+ this.showNextOrCloseButton();
50
+ this._hidePreviousButtonIfFirstSlide();
51
+ }
52
+
53
+ _addClassIf(element, clazz, criteria) {
54
+ if (criteria()) {
55
+ element.addClass(clazz);
56
+ } else {
57
+ element.removeClass(clazz);
58
+ }
59
+ }
60
+ }
61
+
62
+ return MuModalCarrousel;
63
+ })();
@@ -1,7 +1,7 @@
1
1
  mumuki.load(() => {
2
2
  function dumpChoices(_evt) {
3
3
  var indexes = $('.solution-choice:checked').map(function () {
4
- return $(this).data('index')
4
+ return $(this).data('index');
5
5
  }).get().join(':');
6
6
  $('#solution_content').attr('value', indexes);
7
7
  }
@@ -103,11 +103,11 @@ mumuki.load(() => {
103
103
  }
104
104
 
105
105
  get highlightModes() {
106
- return this._getDataFromHiddenInput('#highlight-modes')
106
+ return this._getDataFromHiddenInput('#highlight-modes');
107
107
  }
108
108
 
109
109
  get locales() {
110
- return this._getDataFromHiddenInput('#multifile-locales')
110
+ return this._getDataFromHiddenInput('#multifile-locales');
111
111
  }
112
112
 
113
113
  setUpAddFile() {
@@ -131,7 +131,7 @@ mumuki.load(() => {
131
131
 
132
132
  this._setVisibility(this._addFileButton, filesCount < this.MAX_TABS);
133
133
  this.files.toArray().forEach(file => {
134
- this._setVisibility(file.deleteButton, file.name !== this.mainFile && filesCount > 1)
134
+ this._setVisibility(file.deleteButton, file.name !== this.mainFile && filesCount > 1);
135
135
  });
136
136
  }
137
137
 
@@ -73,7 +73,7 @@ mumuki.MultipleScenarios = (() => {
73
73
  if (this.validScenarioIndex(newScenarioIndex)) {
74
74
  this.setActiveIndex(newScenarioIndex);
75
75
  }
76
- })
76
+ });
77
77
  }
78
78
 
79
79
  createControls () {
@@ -0,0 +1,18 @@
1
+ mumuki.animateNumberCounter = (selector, valueTo, seconds = 1) => {
2
+ const $numberCounter = $(selector);
3
+
4
+ if ($numberCounter.text()) return;
5
+
6
+ const millis = seconds * 1000;
7
+ const incrementStep = valueTo / (millis / 10);
8
+
9
+ _increment();
10
+
11
+ function _increment(initValue = 0, delay = 10) {
12
+ if (initValue >= valueTo) return;
13
+ const nextValue = initValue + incrementStep;
14
+ // TODO: this one should be xp agnostic
15
+ $numberCounter.text(`+${Math.min(Math.round(nextValue), valueTo)}exp`);
16
+ setTimeout(() => _increment(nextValue), delay);
17
+ }
18
+ };
@@ -11,5 +11,5 @@ mumuki.pin = (() => {
11
11
  smoothScrollToElement(scrollPin);
12
12
  }
13
13
  }
14
- }
14
+ };
15
15
  })();
@@ -83,7 +83,7 @@ mumuki.load(() => {
83
83
  this._$texts.hide();
84
84
  this.$characterSpeechBubbleNormal.children('.' + $tab.data('target')).show();
85
85
  this._updateSpeechParagraphs();
86
- })
86
+ });
87
87
  }
88
88
  });
89
89
 
@@ -123,8 +123,9 @@ mumuki.load(() => {
123
123
  this._showMessageOnCharacterBubble(data);
124
124
  }
125
125
 
126
- onSubmissionResultModalOpen(_data) {
126
+ onSubmissionResultModalOpen(data) {
127
127
  this._showCorollaryCharacter();
128
+ super.onSubmissionResultModalOpen(data);
128
129
  }
129
130
 
130
131
  // ==========================
@@ -17,7 +17,7 @@ mumuki.progress = (() => {
17
17
  function updateWholeProgressBar(f) {
18
18
  $('.progress-list-item').each((_, it) => {
19
19
  const $anchor = $(it);
20
- $anchor.attr('class', f($anchor))
20
+ $anchor.attr('class', f($anchor));
21
21
  });
22
22
  }
23
23
 
@@ -49,7 +49,7 @@ mumuki.renderers.results = (() => {
49
49
  classForStatus,
50
50
  iconForStatus,
51
51
  progressListItemClassForStatus
52
- }
52
+ };
53
53
  })();
54
54
 
55
55
  /** @deprecated use {@code mumuki.renderers.results.classForStatus} instead */
@@ -20,7 +20,7 @@ mumuki.renderers.speechBubble = (()=> {
20
20
 
21
21
  _chooseResultItem() {
22
22
  if (this._responseStatus() !== 'passed' && this._hasTips()) {
23
- this._appendFirstTip()
23
+ this._appendFirstTip();
24
24
  } else if (this._responseStatus() === 'failed') {
25
25
  this._appendFirstFailedTestResultSummary();
26
26
  } else if (this._responseStatus() === 'passed_with_warnings') {
@@ -29,7 +29,7 @@ mumuki.renderers.speechBubble = (()=> {
29
29
  }
30
30
 
31
31
  _appendFirstFailedTestResultSummary() {
32
- const failedTestResult = this._failedTestResults()[0]
32
+ const failedTestResult = this._failedTestResults()[0];
33
33
  if (failedTestResult && failedTestResult.summary) {
34
34
  this._appendResultItem(mumuki.renderers.renderSpeechBubbleResultItem(failedTestResult.summary));
35
35
  }
@@ -67,7 +67,7 @@ mumuki.renderers.speechBubble = (()=> {
67
67
  }
68
68
 
69
69
  _hasTips() {
70
- return this.responseData.tips && this.responseData.tips.length
70
+ return this.responseData.tips && this.responseData.tips.length;
71
71
  }
72
72
 
73
73
  _failedTestResults() {
@@ -97,7 +97,7 @@ mumuki.renderers.speechBubble = (()=> {
97
97
  return {
98
98
  SpeechBubbleRenderer,
99
99
  renderSpeechBubbleResultItem
100
- }
100
+ };
101
101
  })();
102
102
 
103
103
  /** @deprecated use {@code mumuki.renderers.speechBubble.SpeechBubbleRenderer} instead */
@@ -26,7 +26,7 @@ mumuki.submission = (() => {
26
26
  this.submissionsResultsArea.html(data.html);
27
27
  data.status === 'aborted' ? this.error(submitButton) : submitButton.enable();
28
28
  mumuki.updateProgressBarAndShowModal(data);
29
- mumuki.gamification._currentLevelProgression.setExpMessage(data.current_exp);
29
+ mumuki.gamification.currentLevelProgression.setExpMessage(data);
30
30
  }
31
31
  error(submitButton) {
32
32
  this.submissionsResultsArea.html('');
@@ -101,7 +101,7 @@ mumuki.submission = (() => {
101
101
  });
102
102
  mumuki.kids.showResult(data);
103
103
  });
104
- }
104
+ };
105
105
  }
106
106
 
107
107
  /** Processor for non-kids layouts */
@@ -118,7 +118,7 @@ mumuki.submission = (() => {
118
118
  $(document).renderMuComponents();
119
119
  resultsBox.done(data, submitButton);
120
120
  });
121
- }
121
+ };
122
122
  }
123
123
 
124
124
  /** Selects the most appropriate solution processor */
@@ -96,7 +96,7 @@ mumuki.SubmissionsStore = (() => {
96
96
  _keyFor(exerciseId) {
97
97
  return `/exercise/${exerciseId}/submission`;
98
98
  }
99
- };
99
+ }();
100
100
 
101
101
  return SubmissionsStore;
102
102
  })();
@@ -106,5 +106,5 @@ mumuki.load(() => {
106
106
  if (e.detail[0]) {
107
107
  mumuki.SubmissionsStore.clear();
108
108
  }
109
- })
110
- })
109
+ });
110
+ });
@@ -65,11 +65,11 @@ mumuki.syncMode = (() => {
65
65
 
66
66
  /** @type {ClientSyncMode|ServerSyncMode}*/
67
67
  _current: null
68
- }
68
+ };
69
69
  })();
70
70
 
71
71
  mumuki.load(() => {
72
72
  mumuki.syncMode._selectSyncMode();
73
73
  mumuki.syncMode._current.syncProgress();
74
74
  mumuki.syncMode._current.syncEditorContent();
75
- })
75
+ });
@@ -16,5 +16,5 @@ mumuki.startTimer = (() => {
16
16
  }
17
17
  }, intervalDuration);
18
18
  }
19
- return startTimer
19
+ return startTimer;
20
20
  })();
@@ -25,13 +25,13 @@ body {
25
25
  margin-bottom: 110px;
26
26
  }
27
27
 
28
- .footer {
29
- position: absolute;
30
- bottom: 0;
31
- width: 100%;
32
- height: 110px;
33
- .container {
34
- margin-top: 33px;
28
+ .mu-footer {
29
+ &.container {
30
+ margin-top: 45px;
31
+ }
32
+ .mu-footer-terms {
33
+ font-size: 15px;
34
+ text-align: center;
35
35
  }
36
36
  }
37
37
 
@@ -1,3 +1,14 @@
1
+ @mixin full-size-character {
2
+ width: 100%;
3
+ height: 100%;
4
+ img {
5
+ width: 100%;
6
+ height: 100%;
7
+ object-position: center;
8
+ object-fit: contain;
9
+ }
10
+ }
11
+
1
12
  @each $class in success, warning, danger, broken {
2
13
  .mu-kids-callout-#{$class} {
3
14
  h4 {
@@ -47,3 +58,109 @@ $capital-animation-width: 135px;
47
58
  }
48
59
  }
49
60
 
61
+ .mu-kids-modal-border {
62
+
63
+ .modal-dialog {
64
+
65
+ height: 100vh;
66
+ width: 100vw;
67
+ margin: 0;
68
+
69
+ .modal-content {
70
+
71
+ position: absolute;
72
+
73
+ $width-lg: 800px;
74
+
75
+ width: $width-lg;
76
+
77
+ top: 60px;
78
+ left: calc(50% - #{$width-lg} / 2);
79
+
80
+ @media (max-width: $width-lg + 30px) {
81
+ width: calc(100vw - 30px);
82
+ }
83
+
84
+ $border-width: 16px;
85
+ border-width: $border-width;
86
+ border-style: solid;
87
+ border-color: $mu-color-link;
88
+ border-radius: $border-width;
89
+ box-shadow: none;
90
+
91
+ .modal-body {
92
+ div, p {
93
+ height: 100%;
94
+ .mu-kids-results-carrousel,
95
+ .mu-kindergarten-context-image-slides {
96
+ > :not(.active) {
97
+ display: none;
98
+ }
99
+ }
100
+ }
101
+ .mu-level p {
102
+ height: unset;
103
+ }
104
+ }
105
+
106
+ .mu-kids-modal-button {
107
+ $diameter: 64px;
108
+ position: absolute;
109
+ border-radius: 50%;
110
+ height: $diameter;
111
+ width: $diameter;
112
+ color: white;
113
+ font-weight: bold;
114
+ border: none;
115
+ padding: 0;
116
+ background: $mu-color-link;
117
+ &.mu-next,
118
+ &.mu-close {
119
+ top: - $diameter / 2 - $border-width / 2;
120
+ right: - $diameter / 2 - $border-width / 2;
121
+ }
122
+ &.mu-previous {
123
+ top: - $diameter / 2 - $border-width / 2;
124
+ left: - $diameter / 2 - $border-width / 2;
125
+ }
126
+ }
127
+
128
+ $mu-statuses-colors: (
129
+ 'broken': $mu-color-broken,
130
+ 'danger': $mu-color-danger,
131
+ 'success': $mu-color-success,
132
+ 'warning': $mu-color-warning,
133
+ 'passed': $mu-color-success,
134
+ 'passed-with-warnings': $mu-color-warning,
135
+ 'failed': $mu-color-danger,
136
+ 'errored': $mu-color-broken,
137
+ 'aborted': $mu-color-broken,
138
+ 'pending': $mu-color-info
139
+ );
140
+
141
+ @each $class, $color in $mu-statuses-colors {
142
+ &.#{$class} {
143
+ border-color: $color;
144
+ .mu-kids-modal-button {
145
+ background: $color;
146
+ }
147
+ }
148
+ .mu-kids-character.kindergarten {
149
+ @include full-size-character;
150
+ }
151
+
152
+ .submission-results.kindergarten {
153
+ width: 100%;
154
+ height: 100%;
155
+ .mu-kids-callout-#{$class},
156
+ p {
157
+ display: none;
158
+ }
159
+ .mu-kids-default-#{$class} {
160
+ @include full-size-character;
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }