mumuki-laboratory 7.12.1 → 7.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/gamification.js +89 -11
  3. data/app/assets/javascripts/mumuki_laboratory/application/kids.js +16 -4
  4. data/app/assets/javascripts/mumuki_laboratory/application/kindergarten.js +9 -50
  5. data/app/assets/javascripts/mumuki_laboratory/application/mu-modal-carrousel.js +63 -0
  6. data/app/assets/javascripts/mumuki_laboratory/application/number-counter.js +18 -0
  7. data/app/assets/javascripts/mumuki_laboratory/application/primary.js +2 -1
  8. data/app/assets/javascripts/mumuki_laboratory/application/submission.js +1 -1
  9. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids_results.scss +117 -0
  10. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +45 -131
  11. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_profile.scss +31 -3
  12. data/app/helpers/gamification_helper.rb +5 -0
  13. data/app/views/exercise_solutions/_contextualization_results_container.html.erb +9 -0
  14. data/app/views/exercise_solutions/_kids_level_up.html.erb +11 -0
  15. data/app/views/exercise_solutions/_results_title.html.erb +5 -0
  16. data/app/views/exercises/show.html.erb +4 -1
  17. data/app/views/layouts/_kindergarten.html.erb +1 -1
  18. data/app/views/layouts/application.html.erb +8 -2
  19. data/app/views/layouts/modals/_guide_corollary.html.erb +1 -1
  20. data/app/views/layouts/modals/_kids_context.html.erb +1 -1
  21. data/app/views/layouts/modals/_kids_results.html.erb +16 -6
  22. data/app/views/layouts/modals/_kindergarten_context.html.erb +15 -15
  23. data/app/views/layouts/modals/_kindergarten_results.html.erb +20 -7
  24. data/app/views/layouts/modals/_kindergarten_results_aborted.html.erb +4 -4
  25. data/app/views/layouts/modals/_level_up.html.erb +27 -0
  26. data/app/views/users/_user_form.html.erb +12 -2
  27. data/lib/mumuki/laboratory/controllers/results_rendering.rb +1 -0
  28. data/lib/mumuki/laboratory/locales/en.yml +5 -0
  29. data/lib/mumuki/laboratory/locales/es-CL.yml +5 -0
  30. data/lib/mumuki/laboratory/locales/es.yml +5 -0
  31. data/lib/mumuki/laboratory/locales/pt.yml +5 -0
  32. data/lib/mumuki/laboratory/version.rb +1 -1
  33. data/spec/controllers/exercise_solutions_controller_spec.rb +1 -1
  34. metadata +102 -97
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e025a36f6aa947e6a39b8372e2a0f257d473143a9c5bef32575885fcf375312b
4
- data.tar.gz: 81428a5522e48781ed29cde5b8f2c219f3388a8d36e1ae466f91ee412e69b246
3
+ metadata.gz: 26c8eb799e96cc5d8e68fc6d5b9ae0e268fcc77fa99156db5051b373b6aee412
4
+ data.tar.gz: 4dde09623edc6d26430bbd6bc86bcd6e85347e73bcd444df00d8f7b47274859c
5
5
  SHA512:
6
- metadata.gz: 374aec02893b3e31880b6bb3bbaf1031e0c2b567ee1b25b8d03bb7a30bda810aa72f5ff9974d70cab6637cede23a8e734e95ec5e9a1357d14772300e64a0fc6f
7
- data.tar.gz: 70637e35aa57f98661f78f787a15e181ed4737d4b1d3080adcf06e90f4564e5cec9b642055106cccf6ae29fbaa90b1695367c7ac424c89b225166f80ab007557
6
+ metadata.gz: efef98352ef78664e8aac7493a70546b824b54e97ed556d18ae1068c532564dcf509f2878e2c8105d9e5a0e318222b3aa0b1894d43fe2d1d48da397137ea6329
7
+ data.tar.gz: fb241340ebe7994e84ab86c08275304c74cd5669bbc2ca3b7c8bcf616d1186b088ecd3da59fe8abb80b65a1f9dfd8538eccbf31cba4f55dbd83f7a2838601911
@@ -5,9 +5,22 @@ mumuki.gamification = (() => {
5
5
  static get CONSTANT_TERM() { return -125; }
6
6
  }
7
7
 
8
+ class DummyLevelProgression {
9
+ setExpMessage() {}
10
+
11
+ registerLevelUpAction() {}
12
+
13
+ registerGainedExperienceAction() {}
14
+
15
+ updateLevel() {}
16
+ }
17
+
8
18
  class LevelProgression {
9
19
  constructor(currentExp) {
10
20
  this.currentExp = currentExp;
21
+ this.lastEarnedExp = 0;
22
+ this._levelUpAction = this.defaultLevelUpAction;
23
+ this._gainedExperienceAction = this.defaultGainedExperienceAction;
11
24
  }
12
25
 
13
26
  expToLevelUp() {
@@ -38,8 +51,8 @@ mumuki.gamification = (() => {
38
51
  return Math.floor((-b + Math.sqrt(Math.pow(b, 2) - 4 * a * (c - exp))) / (2 * a));
39
52
  }
40
53
 
41
- triggersLevelChange(exp) {
42
- return this.levelFor(exp) !== this.currentLevel();
54
+ triggersLevelChange(newExp) {
55
+ return this.levelFor(this.currentExp + newExp) !== this.currentLevel();
43
56
  }
44
57
 
45
58
  currentLevelProgress() {
@@ -50,23 +63,87 @@ mumuki.gamification = (() => {
50
63
  return this.expFor(this.currentLevel());
51
64
  }
52
65
 
53
- setExpMessage(exp) {
54
- let expGained = exp - this.currentExp;
66
+ exercisesToNextLevel() {
67
+ return Math.ceil(this.expToLevelUp() / 100);
68
+ }
69
+
70
+ setExpMessage(data) {
71
+ const exp = data.current_exp;
72
+ this.lastEarnedExp = exp - this.currentExp;
73
+
74
+ if (this.lastEarnedExp > 0) {
75
+ this.levelUpActionIfLevelUp(data.level_up_html);
76
+ this._gainedExperienceAction();
55
77
 
56
- if (expGained > 0) {
57
78
  this.currentExp = exp;
58
- $('#mu-exp-points').html(expGained);
59
- $('#mu-level-number').html(this.currentLevel());
79
+ this.updateLevel();
80
+ }
81
+ }
82
+
83
+ defaultGainedExperienceAction() {
84
+ $('#mu-exp-points').html(this.lastEarnedExp);
85
+ $('#mu-exp-earned-message').removeClass('hidden');
86
+ }
87
+
88
+ defaultLevelUpAction(_levelUpHtml) {
89
+ $('#mu-level-up').modal();
90
+ }
91
+
92
+ registerLevelUpAction(action) {
93
+ this._levelUpAction = action;
94
+ }
95
+
96
+ registerGainedExperienceAction(action) {
97
+ this._gainedExperienceAction = action;
98
+ }
99
+
100
+ levelUpActionIfLevelUp(levelUpHtml) {
101
+ if (this.triggersLevelChange(this.lastEarnedExp)) {
102
+ this._levelUpAction(levelUpHtml);
60
103
  }
61
104
  }
105
+
106
+ animateExperienceCounter(selector) {
107
+ mumuki.animateNumberCounter(selector, this.lastEarnedExp);
108
+ }
109
+
110
+ updateLevel() {
111
+ const $muLevelProgress = $('#mu-level-progress');
112
+
113
+ $('#mu-solve-more-exercises span').text(this.exercisesToNextLevel());
114
+ $('.mu-level-number').html(this.currentLevel());
115
+ $('.mu-level-tooltip').attr("title", (_, value) => `${value} ${this.currentLevel()}`);
116
+
117
+ if (this.currentLevelProgress() === 0) {
118
+ $muLevelProgress.attr("display", "none");
119
+ }
120
+
121
+ $muLevelProgress.animate(
122
+ {'progress': this.currentLevelProgress() * 250},
123
+ {
124
+ step: function(progress) {
125
+ let pattern = progress + ", 999";
126
+ $(this).attr("stroke-dasharray", pattern);
127
+ },
128
+ duration: 1000
129
+ });
130
+ }
62
131
  }
63
132
 
64
133
  function _setUpCurrentLevelProgression() {
65
- mumuki.gamification._currentLevelProgression = new LevelProgression(currentExp());
134
+ if (_gamificationEnabled()) {
135
+ mumuki.gamification.currentLevelProgression = new LevelProgression(currentExp());
136
+ } else {
137
+ mumuki.gamification.currentLevelProgression = new DummyLevelProgression();
138
+ }
139
+ }
140
+
141
+ function _gamificationEnabled() {
142
+ return $('#mu-current-exp').length;
66
143
  }
67
144
 
68
145
  function currentExp() {
69
- return $('#mu-current-exp').val();
146
+ return parseInt($('#mu-current-exp').val());
70
147
  }
71
148
 
72
149
  return {
@@ -75,11 +152,12 @@ mumuki.gamification = (() => {
75
152
 
76
153
  _setUpCurrentLevelProgression,
77
154
 
78
- /** @type {LevelProgression} */
79
- _currentLevelProgression: null
155
+ /** @type {LevelProgression|DummyLevelProgression} */
156
+ currentLevelProgression: null
80
157
  };
81
158
  })();
82
159
 
83
160
  mumuki.load(() => {
84
161
  mumuki.gamification._setUpCurrentLevelProgression();
162
+ mumuki.gamification.currentLevelProgression.updateLevel();
85
163
  });
@@ -21,9 +21,20 @@ mumuki.Kids = class {
21
21
  this.$stateImage = $('.mu-kids-state-image');
22
22
  this.$contextModal = $('#mu-kids-context');
23
23
  this.$resultsModal = $('#kids-results');
24
+ this.resultsCarrousel = new mumuki.ModalCarrousel('.mu-kids-results-carrousel');
24
25
  this.$resultsAbortedModal = $('#kids-results-aborted');
25
26
  this.$bubbleCharacterAnimation = $('.mu-kids-character-animation');
26
27
  this.$submissionResult = $('.submission-results');
28
+ mumuki.gamification.currentLevelProgression.registerLevelUpAction(this.levelUpAction);
29
+ mumuki.gamification.currentLevelProgression.registerGainedExperienceAction(this.gainedExperienceAction);
30
+ }
31
+
32
+ gainedExperienceAction() {
33
+ mumuki.gamification.currentLevelProgression.animateExperienceCounter('.mu-kids-results .mu-experience');
34
+ }
35
+
36
+ levelUpAction(levelUpHtml) {
37
+ $('.mu-kids-results-carrousel').append(levelUpHtml);
27
38
  }
28
39
 
29
40
  showContext() {
@@ -74,7 +85,7 @@ mumuki.Kids = class {
74
85
  }
75
86
 
76
87
  onSubmissionResultModalOpen(_data) {
77
- // SubClasses may override this method
88
+ this.resultsCarrousel.show();
78
89
  }
79
90
 
80
91
  // =================
@@ -82,9 +93,10 @@ mumuki.Kids = class {
82
93
  // =================
83
94
 
84
95
  _openSubmissionResultModal(data) {
85
- this.$resultsModal.modal({ backdrop: 'static', keyboard: false })
86
- this.$resultsModal.find('.modal-header').first().html(data.title_html)
87
- this.$resultsModal.find('.modal-footer').first().html(data.button_html)
96
+ this.$resultsModal.modal({ backdrop: 'static', keyboard: false });
97
+ this.$resultsModal.find('.modal-header').first().html(data.title_html);
98
+ mumuki.gamification.currentLevelProgression.setExpMessage(data);
99
+ this.$resultsModal.find('.modal-footer').first().html(data.button_html);
88
100
  $('.mu-close-modal').click(() => this.$resultsModal.modal('hide'));
89
101
  this.onSubmissionResultModalOpen(data);
90
102
  }
@@ -10,7 +10,7 @@ mumuki.load(() => {
10
10
 
11
11
  initialize() {
12
12
  super.initialize();
13
- this.$contextModalButton = new mumuki.Button($('#mu-kids-context .mu-kindergarten-modal-button.mu-close'));
13
+ this.$contextModalButton = new mumuki.Button($('#mu-kids-context .mu-kids-modal-button.mu-close'));
14
14
 
15
15
  this.resultActions.passed = this._showSuccessPopup.bind(this);
16
16
  this.resultActions.passed_with_warnings = this._showSuccessPopup.bind(this);
@@ -29,10 +29,9 @@ mumuki.load(() => {
29
29
  // ================
30
30
 
31
31
  showNonAbortedPopup(data, animation_name) {
32
- data.guide_finished_by_solution = false;
33
32
  this.$submissionResult.html(data.title_html);
34
- this.$resultsModal.find('.modal-content').removeClass().addClass('modal-content').addClass(data.status);
35
- super.showNonAbortedPopup(data, animation_name, 1000);
33
+ this.$resultsModal.find('.modal-content').removeClass().addClass('modal-content kindergarten').addClass(data.status);
34
+ super.showNonAbortedPopup(data, animation_name);
36
35
  }
37
36
 
38
37
  showAbortedPopup(data) {
@@ -72,6 +71,11 @@ mumuki.load(() => {
72
71
  // == Called by the runner ==
73
72
  // ==========================
74
73
 
74
+ showResult(data) {
75
+ data.guide_finished_by_solution = false;
76
+ super.showResult(data);
77
+ }
78
+
75
79
  restart() {
76
80
  mumuki.presenterCharacter.playAnimation('talk', this.$bubbleCharacterAnimation);
77
81
  }
@@ -143,52 +147,7 @@ mumuki.load(() => {
143
147
  }
144
148
 
145
149
  get context() {
146
- return {
147
- showContext() {
148
- mumuki.kids.showContext();
149
- this._showFirstSlideImage();
150
- },
151
- nextSlide() {
152
- this._clickButton('next');
153
- },
154
- prevSlide() {
155
- this._clickButton('prev');
156
- },
157
- _imageSlides() {
158
- return $('.mu-kindergarten-context-image-slides');
159
- },
160
- _activeSlideImage() {
161
- return this._imageSlides().find('.active');
162
- },
163
- _clickButton(prevOrNext) {
164
- this._activeSlideImage().removeClass('active')[prevOrNext]().addClass('active');
165
- this.showNextOrCloseButton();
166
- this._hidePreviousButtonIfFirstImage();
167
- },
168
- showNextOrCloseButton() {
169
- const $next = $('.mu-kindergarten-modal-button.mu-next');
170
- const $close = $('.mu-kindergarten-modal-button.mu-close');
171
- const isLastChild = this._activeSlideImage().is(':last-child');
172
- this._addClassIf($next, 'hidden', () => isLastChild);
173
- this._addClassIf($close, 'hidden', () => !isLastChild);
174
- },
175
- _hidePreviousButtonIfFirstImage() {
176
- const $prev = $('.mu-kindergarten-modal-button.mu-previous');
177
- this._addClassIf($prev, 'hidden', () => this._activeSlideImage().is(':first-child'))
178
- },
179
- _showFirstSlideImage() {
180
- this._imageSlides().find('img').each((i, el) => this._addClassIf($(el), 'active', () => i === 0))
181
- this.showNextOrCloseButton();
182
- this._hidePreviousButtonIfFirstImage();
183
- },
184
- _addClassIf(element, clazz, criteria) {
185
- if (criteria()) {
186
- element.addClass(clazz)
187
- } else {
188
- element.removeClass(clazz);
189
- }
190
- }
191
- }
150
+ return new mumuki.ModalCarrousel('.mu-kindergarten-context-image-slides', () => mumuki.kids.showContext());
192
151
  }
193
152
 
194
153
  }
@@ -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
+ })();
@@ -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
+ };
@@ -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
  // ==========================
@@ -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('');
@@ -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
+ }