mumuki-laboratory 6.4.2 → 6.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +61 -1
  3. data/app/assets/javascripts/application.js +1 -0
  4. data/app/assets/javascripts/application/characters.js +26 -0
  5. data/app/assets/javascripts/application/kids.js +179 -123
  6. data/app/assets/javascripts/application/load-error-svg.js +3 -20
  7. data/app/assets/javascripts/application/multiple-scenarios.js +152 -0
  8. data/app/assets/javascripts/application/popover.js +18 -0
  9. data/app/assets/javascripts/application/submission.js +5 -10
  10. data/app/assets/stylesheets/application/_layout.scss +10 -2
  11. data/app/assets/stylesheets/application/_modules.scss +1 -0
  12. data/app/assets/stylesheets/application/modules/_kids.scss +11 -4
  13. data/app/assets/stylesheets/application/modules/popover.scss +9 -0
  14. data/app/controllers/application_controller.rb +2 -2
  15. data/app/helpers/application_helper.rb +14 -5
  16. data/app/helpers/assets_helper.rb +13 -1
  17. data/app/helpers/links_helper.rb +15 -1
  18. data/app/helpers/organization_list_helper.rb +1 -1
  19. data/app/helpers/reset_button_helper.rb +1 -1
  20. data/app/helpers/version_helper.rb +5 -0
  21. data/app/views/errors/forbidden.html.erb +1 -1
  22. data/app/views/errors/internal_server_error.html.erb +1 -1
  23. data/app/views/errors/not_found.html.erb +1 -1
  24. data/app/views/exercise_solutions/_kids_results.html.erb +2 -2
  25. data/app/views/exercises/show.html.erb +2 -1
  26. data/app/views/layouts/_guide.html.erb +21 -5
  27. data/app/views/layouts/_kids.html.erb +1 -1
  28. data/app/views/layouts/_main.html.erb +1 -0
  29. data/app/views/layouts/_organizations_listing.html.erb +1 -1
  30. data/app/views/layouts/_submission_result_error_body.html.erb +1 -1
  31. data/app/views/layouts/exercise_inputs/layouts/_input_kids.html.erb +21 -10
  32. data/app/views/layouts/modals/_kids_context.html.erb +2 -2
  33. data/app/views/layouts/modals/_kids_results.html.erb +1 -1
  34. data/app/views/layouts/modals/_kids_results_aborted.html.erb +1 -1
  35. data/lib/mumuki/laboratory/controllers/results_rendering.rb +2 -1
  36. data/lib/mumuki/laboratory/engine.rb +3 -0
  37. data/lib/mumuki/laboratory/locales/en.yml +2 -0
  38. data/lib/mumuki/laboratory/locales/es.yml +2 -0
  39. data/lib/mumuki/laboratory/locales/pt.yml +2 -0
  40. data/lib/mumuki/laboratory/version.rb +1 -1
  41. data/public/character/kibi/context.svg +1 -0
  42. data/public/character/kibi/failure.svg +1 -0
  43. data/public/character/kibi/jump.svg +1 -0
  44. data/public/character/kibi/success2_l.svg +1 -0
  45. data/public/character/kibi/success_l.svg +1 -0
  46. data/public/character/magnifying_glass/apparition.svg +1 -0
  47. data/public/character/magnifying_glass/loop.svg +1 -0
  48. data/public/error/403.svg +1 -0
  49. data/public/error/404.svg +1 -0
  50. data/public/error/500.svg +1 -0
  51. data/public/error/timeout_1.svg +1 -0
  52. data/public/error/timeout_2.svg +1 -0
  53. data/public/error/timeout_3.svg +1 -0
  54. data/spec/controllers/exercise_solutions_controller_spec.rb +2 -1
  55. data/spec/dummy/db/schema.rb +5 -2
  56. data/spec/dummy/public/character/animations.json +30 -0
  57. data/spec/features/exercise_flow_spec.rb +3 -3
  58. data/spec/features/guides_flow_spec.rb +3 -3
  59. metadata +52 -17
  60. data/public/character/kids/kibi_context.svg +0 -11
  61. data/public/error_403.svg +0 -2
  62. data/public/error_404.svg +0 -5
  63. data/public/error_500.svg +0 -2
  64. data/public/error_timeout_1.svg +0 -2
  65. data/public/error_timeout_2.svg +0 -1
  66. data/public/error_timeout_3.svg +0 -1
  67. data/public/kibi_animated.svg +0 -2
  68. data/public/kibi_failure.svg +0 -5
  69. data/public/kibi_success.svg +0 -11
  70. data/public/kibi_success_dancing.svg +0 -16
  71. data/public/magnifying_glass_apparition.svg +0 -2
  72. data/public/magnifying_glass_loop.svg +0 -3
@@ -2,27 +2,10 @@ mumuki.load(function () {
2
2
  var error_svgs = ['403', '404', '500', 'timeout_1', 'timeout_2', 'timeout_3'];
3
3
 
4
4
  mumuki.errors = mumuki.errors || {};
5
- mumuki.characters = mumuki.characters || {};
6
5
 
7
- error_svgs.forEach(function (svgErrorSuffix) {
8
- addImage(mumuki.errors, 'error_' + svgErrorSuffix, '/');
6
+ error_svgs.forEach(function (svgErrorName) {
7
+ muvment.animation.addImage(mumuki.errors, svgErrorName, '/error/');
9
8
  });
10
9
 
11
- addImage(mumuki.characters, 'kibi_context', '/character/kids/');
12
- addImage(mumuki.characters, 'magnifying_glass_apparition', '/');
13
- addImage(mumuki.characters, 'magnifying_glass_loop', '/');
14
-
15
- function addImage(object, imageName, urlPrefix) {
16
- var url = urlPrefix + imageName + '.svg';
17
- if (!object[imageName]) {
18
- $.get(url, function (data) {
19
- var duration = parseFloat($(data).find('animate').attr('dur') || 0, 10) * 1000;
20
- object[imageName] = {
21
- url: url,
22
- duration: duration
23
- };
24
- });
25
- }
26
- }
27
-
10
+ mumuki.errorState = (error_name) => mumuki.errors[error_name].asState(error_name);
28
11
  });
@@ -0,0 +1,152 @@
1
+ var mumuki = mumuki || {};
2
+
3
+ (function (mumuki) {
4
+
5
+ const setControlVisibility = function ($control, visible) {
6
+ visible ? $control.show() : $control.hide();
7
+ };
8
+
9
+ const addClickListener = function($element, listener) {
10
+ $element.on('click', listener);
11
+ };
12
+
13
+ const getControlElement = function(controlSelector) {
14
+ return $(`.mu-scenario-control.${controlSelector} i`);
15
+ };
16
+
17
+ const statusIconMap = new Map([
18
+ ['pending', 'fa-circle'],
19
+ ['passed', 'fa-check-circle'],
20
+ ['failed', 'fa-times-circle']
21
+ ]);
22
+
23
+ const highlightAnimationClass = 'jump';
24
+
25
+ const statusesClasses = Array.from(statusIconMap.keys());
26
+ const iconsClasses = Array.from(statusIconMap.values());
27
+ const indicatorsClasses = iconsClasses.concat(statusesClasses);
28
+
29
+ const getClassesFor = (status) => [status, statusIconMap.get(status)];
30
+
31
+ const wrapScenario = ($scenario) => $scenario.wrap("<div class='mu-scenario'></div>").parent();
32
+
33
+ const setIndicatorStatus = function (indicator, status) {
34
+ const indicatorClassList = indicator.classList;
35
+ const statusClasses = getClassesFor(status);
36
+ indicatorClassList.remove(...indicatorsClasses);
37
+ indicatorClassList.add(...statusClasses);
38
+ };
39
+
40
+ const unhighlightIndicator = function ($indicator) {
41
+ $indicator.classList.remove(highlightAnimationClass);
42
+ };
43
+
44
+ const highlightIndicator = function ($indicator) {
45
+ $indicator.classList.add(highlightAnimationClass);
46
+ };
47
+
48
+ const triggerResize = function () {
49
+ let event = document.createEvent('HTMLEvents');
50
+ event.initEvent('resize', true, false);
51
+ document.dispatchEvent(event);
52
+ };
53
+
54
+ class MultipleScenarios {
55
+ constructor(selectors) {
56
+ this.indicators = $('.mu-multiple-scenarios .indicators');
57
+ this.initialize(selectors);
58
+ }
59
+
60
+ initialize (selectors) {
61
+ this.createScenarios(selectors);
62
+ this.createControls();
63
+ this.createIndicators();
64
+ this.setActiveIndex(0);
65
+ $('.mu-scenario-control').removeClass('hidden');
66
+ }
67
+
68
+ validScenarioIndex (index) {
69
+ return index >= 0 && index < this.scenariosCount;
70
+ }
71
+
72
+ createControlListener ($control, changeStep) {
73
+ addClickListener($control, () => {
74
+ const newScenarioIndex = this.currentScenarioIndex + changeStep;
75
+ if (this.validScenarioIndex(newScenarioIndex)) {
76
+ this.setActiveIndex(newScenarioIndex);
77
+ }
78
+ })
79
+ }
80
+
81
+ createControls () {
82
+ this.previousControl = getControlElement('previous');
83
+ this.nextControl = getControlElement('next');
84
+ this.createControlListener(this.previousControl, -1);
85
+ this.createControlListener(this.nextControl, 1);
86
+ }
87
+
88
+ createScenarios (selectors) {
89
+ let $scenarios = selectors.map(selector => $(`.mu-multiple-scenarios ${selector}`));
90
+ this.scenarios = $scenarios.map($scenario => wrapScenario($scenario));
91
+ this.scenariosCount = this.scenarios[0].length;
92
+ }
93
+
94
+ createIndicators () {
95
+ for (let index = 0; index < this.scenariosCount; index++) {
96
+ let $indicator = $('<li class="fa"></li>');
97
+ this.indicators.append($indicator);
98
+ let indicatorIndex = index;
99
+ addClickListener($indicator, () => {
100
+ this.setActiveIndex(indicatorIndex);
101
+ });
102
+ }
103
+ this.indicatorItems = this.indicators.children('li');
104
+ this.resetIndicators();
105
+ }
106
+
107
+ setActiveIndex (index) {
108
+ this.currentScenarioIndex = index;
109
+ this.setActiveScenario(index);
110
+ unhighlightIndicator(this.indicatorItems[index]);
111
+ this.updateControls(index);
112
+ triggerResize();
113
+ }
114
+
115
+ updateControls (index) {
116
+ setControlVisibility(this.previousControl, index !== 0);
117
+ setControlVisibility(this.nextControl, index !== this.scenariosCount - 1);
118
+ }
119
+
120
+ setActiveScenario (index) {
121
+ [...this.scenarios, this.indicatorItems].forEach(($scenario) => {
122
+ $scenario.removeClass('active');
123
+ $scenario[index].classList.add('active');
124
+ });
125
+ }
126
+
127
+ updateIndicators (testResultsStatuses) {
128
+ testResultsStatuses.forEach((status, index) => {
129
+ const $indicator = this.indicatorItems[index];
130
+ setIndicatorStatus($indicator, status);
131
+ });
132
+ this.highlightFailedScenario(testResultsStatuses);
133
+ }
134
+
135
+ highlightFailedScenario (testResultsStatuses) {
136
+ const currentStatus = testResultsStatuses[this.currentScenarioIndex];
137
+ const isCurrentFailed = currentStatus === 'failed';
138
+ const $failedIndicator = this.indicatorItems.filter('.failed')[0];
139
+ if($failedIndicator && !isCurrentFailed) highlightIndicator($failedIndicator);
140
+ }
141
+
142
+ resetIndicators () {
143
+ this.indicatorItems.each((_, $indicator) => {
144
+ setIndicatorStatus($indicator, 'pending');
145
+ unhighlightIndicator($indicator);
146
+ });
147
+ }
148
+ }
149
+
150
+ mumuki.MultipleScenarios = MultipleScenarios;
151
+
152
+ }(mumuki));
@@ -0,0 +1,18 @@
1
+ mumuki.load(() => {
2
+ let $popover = $('.mu-popover');
3
+
4
+ $('html').click(function(e) {
5
+ if (!e.target.closest('.popover')) {
6
+ $popover.popover('hide');
7
+ }
8
+ });
9
+
10
+ $popover.popover({
11
+ html: true,
12
+ trigger: 'manual',
13
+ container: 'body'
14
+ }).click(function(e) {
15
+ $(this).popover('toggle');
16
+ e.stopPropagation();
17
+ });
18
+ });
@@ -118,16 +118,11 @@ var mumuki = mumuki || {};
118
118
  }
119
119
 
120
120
  function animateTimeoutError(submitButton) {
121
- var image = $('#submission-result-error-animation')[0];
122
- image.src = mumuki.errors.error_timeout_1.url;
123
- submitButton.setSendText();
124
- setTimeout(function () {
125
- image.src = mumuki.errors.error_timeout_2.url;
126
- setTimeout(function () {
127
- image.src = mumuki.errors.error_timeout_3.url;
128
- submitButton.enable();
129
- }, mumuki.errors.error_timeout_2.duration);
130
- }, mumuki.errors.error_timeout_1.duration);
121
+ let scene = new muvment.Scene($('.submission-result-error-animation'));
122
+ scene.addState(mumuki.errorState('timeout_1').onStart(submitButton.setSendText.bind(submitButton)).onEndSwitch(scene, 'timeout_2'))
123
+ .addState(mumuki.errorState('timeout_2').onEndSwitch(scene, 'timeout_3'))
124
+ .addState(mumuki.errorState('timeout_3').onStart(submitButton.enable.bind(submitButton)))
125
+ .play();
131
126
  }
132
127
 
133
128
  mumuki.submission = {
@@ -99,7 +99,7 @@ h1 {
99
99
  }
100
100
  }
101
101
 
102
- .corollary-box {
102
+ .mu-last-box {
103
103
  margin-bottom: 45px;
104
104
  }
105
105
 
@@ -140,7 +140,15 @@ hr {
140
140
  padding-left: 5px;
141
141
  }
142
142
 
143
- .mu-edit-link {
143
+ .mu-content-toolbar-item {
144
144
  font-size: 60%;
145
145
  vertical-align: middle;
146
+ .fa {
147
+ margin: 0;
148
+ padding: 0;
149
+ }
150
+ }
151
+
152
+ h3 {
153
+ margin-top: 20px;
146
154
  }
@@ -21,3 +21,4 @@
21
21
  @import "modules/kids";
22
22
  @import "modules/kids_results";
23
23
  @import "modules/discussion";
24
+ @import "modules/popover";
@@ -27,7 +27,8 @@ $kids-speech-tabs-width: 40px;
27
27
  }
28
28
 
29
29
  .mu-kids-states {
30
- margin-bottom: 15px;
30
+ padding-bottom: 15px;
31
+ height: $kids-height;
31
32
  }
32
33
  .mu-kids-character {
33
34
  padding: 15px;
@@ -39,7 +40,7 @@ $kids-speech-tabs-width: 40px;
39
40
  margin-top: 15px;
40
41
  }
41
42
  .mu-kids-state {
42
- height: calc(#{$kids-height} / 2);
43
+ height: 50%;
43
44
  }
44
45
  .mu-kids-character {
45
46
  height: $kids-characters-height;
@@ -115,6 +116,9 @@ $kids-speech-tabs-width: 40px;
115
116
  ul.results-list li {
116
117
  font-size: 13px
117
118
  }
119
+ .mu-kids-states strong {
120
+ font-size: 16px;
121
+ }
118
122
  }
119
123
 
120
124
  @media screen and (max-width: $screen-xs-max) {
@@ -128,6 +132,9 @@ $kids-speech-tabs-width: 40px;
128
132
  ul.results-list li {
129
133
  font-size: 12px
130
134
  }
135
+ .mu-kids-states strong {
136
+ font-size: 14px;
137
+ }
131
138
  }
132
139
 
133
140
  &.passed_with_warnings {
@@ -417,8 +424,8 @@ $kids-speech-tabs-width: 40px;
417
424
  min-height: 100vh;
418
425
  max-height: 100vh;
419
426
 
420
- .mu-kids-state {
421
- height: 50vh;
427
+ .mu-kids-states {
428
+ height: 100vh;
422
429
  }
423
430
  .mu-kids-blocks {
424
431
  height: calc(100vh - #{$kids-characters-height} - 30px);
@@ -0,0 +1,9 @@
1
+ .popover-title {
2
+ padding: 15px;
3
+ background-color: #FFF;
4
+ font-weight: bold;
5
+ }
6
+
7
+ .popover {
8
+ max-width: 30%;
9
+ }
@@ -39,7 +39,7 @@ class ApplicationController < ActionController::Base
39
39
 
40
40
  def should_choose_organization?
41
41
  current_user? &&
42
- current_user.has_accessible_organizations? &&
42
+ current_user.has_student_granted_organizations? &&
43
43
  Mumukit::Platform.implicit_organization?(request)
44
44
  end
45
45
 
@@ -61,7 +61,7 @@ class ApplicationController < ActionController::Base
61
61
  end
62
62
 
63
63
  def validate_user_profile!
64
- return if !current_user.has_accessible_organizations? || current_user.profile_completed?
64
+ return if !current_user.has_student_granted_organizations? || current_user.profile_completed?
65
65
  flash.notice = I18n.t :please_fill_profile_data
66
66
  redirect_to user_path
67
67
  end
@@ -1,6 +1,11 @@
1
1
  module ApplicationHelper
2
2
  include WithStudentPathNavigation
3
3
 
4
+ # html-escapes an string even if it is html-safe
5
+ def html_rescape(html)
6
+ html_escape html.to_str
7
+ end
8
+
4
9
  def profile_picture
5
10
  image_tag(current_user.image_url, height: 40, class: 'img-circle', onError: "this.onerror = null; this.src = '#{image_url('user_shape.png')}'")
6
11
  end
@@ -9,13 +14,17 @@ module ApplicationHelper
9
14
  "<div class=\"text-center\">#{super(object, {theme: 'twitter-bootstrap-3'}.merge(options))}</div>".html_safe
10
15
  end
11
16
 
12
- def corollary_box(with_corollary)
17
+ def last_box_class(trailing_boxes)
18
+ trailing_boxes ? '' : 'mu-last-box'
19
+ end
20
+
21
+ def corollary_box(with_corollary, trailing_boxes = false)
13
22
  if with_corollary.corollary.present?
14
23
  %Q{
15
- <div class="corollary-box">
16
- <p>#{with_corollary.corollary_html}</p>
17
- </div>
18
- }.html_safe
24
+ <div class="#{last_box_class trailing_boxes}">
25
+ <p>#{with_corollary.corollary_html}</p>
26
+ </div>
27
+ }.html_safe
19
28
  end
20
29
  end
21
30
 
@@ -2,11 +2,23 @@ module AssetsHelper
2
2
  def assets_include_tags
3
3
  %Q{
4
4
  <meta name="turbolinks-cache-control" content="no-cache">
5
+ #{laboratory_assets_include_tags}
6
+ #{organization_assets_include_tags}
7
+ }.html_safe
8
+ end
9
+
10
+ def laboratory_assets_include_tags
11
+ %Q{
5
12
  #{stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'}
6
13
  #{javascript_include_tag 'application', 'data-turbolinks-track': 'reload'}
14
+ }
15
+ end
16
+
17
+ def organization_assets_include_tags
18
+ %Q{
7
19
  <link rel="icon" type="image/x-icon" href="#{Organization.current.favicon_url}" data-turbolinks-track="reload">
8
20
  <link rel="stylesheet" type="text/css" href="#{theme_stylesheet_url}" data-turbolinks-track="reload">
9
21
  <script type="text/javascript" src="#{extension_javascript_url}" defer data-turbolinks-track="reload"></script>
10
- }.html_safe
22
+ }
11
23
  end
12
24
  end
@@ -22,6 +22,20 @@ module LinksHelper
22
22
  app.organic_url(Organization.current.name)
23
23
  end
24
24
 
25
+ def teacher_info_button(item)
26
+ if current_user&.teacher_here? && item.teacher_info.present?
27
+ %Q{
28
+ <a
29
+ class="mu-content-toolbar-item mu-popover"
30
+ data-toggle="popover"
31
+ data-html="true"
32
+ title="#{t :teacher_info}"
33
+ data-placement="bottom"
34
+ data-content="#{html_rescape item.teacher_info_html}">#{fixed_fa_icon('info-circle')}</a>
35
+ }.html_safe
36
+ end
37
+ end
38
+
25
39
  def link_to_bibliotheca_guide(guide)
26
40
  edit_link_to_bibliotheca { url_for_bibliotheca_guide(guide) }
27
41
  end
@@ -58,7 +72,7 @@ module LinksHelper
58
72
  return unless current_user&.writer?
59
73
 
60
74
  url = yield
61
- link_to fixed_fa_icon(:pencil), url, class: "mu-edit-link", target: "_blank", alt: t(:edit)
75
+ link_to fixed_fa_icon(:pencil), url, class: "mu-content-toolbar-item", target: "_blank", title: t(:edit)
62
76
  end
63
77
 
64
78
  def url_for_bibliotheca_guide(guide)
@@ -1,5 +1,5 @@
1
1
  module OrganizationListHelper
2
2
  def organizations_for(user)
3
- (user.accessible_organizations + [Organization.central]).uniq.compact
3
+ (user.student_granted_organizations + [Organization.central]).uniq.compact
4
4
  end
5
5
  end
@@ -4,7 +4,7 @@ module ResetButtonHelper
4
4
  end
5
5
 
6
6
  def restart_guide_link(guide)
7
- link_to restart_icon, guide_progress_path(guide), data: {confirm: t(:confirm_restart)}, method: :delete
7
+ link_to restart_icon, guide_progress_path(guide), class: 'mu-content-toolbar-item', data: {confirm: t(:confirm_restart)}, method: :delete
8
8
  end
9
9
 
10
10
  end