mumuki-laboratory 7.6.2 → 7.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +203 -2
  3. data/Rakefile +9 -0
  4. data/app/assets/javascripts/mumuki_laboratory/application.js +0 -1
  5. data/app/assets/javascripts/mumuki_laboratory/application/alias-modes.js +1 -1
  6. data/app/assets/javascripts/mumuki_laboratory/application/assets-loader.js +1 -1
  7. data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +82 -47
  8. data/app/assets/javascripts/mumuki_laboratory/application/button.js +90 -1
  9. data/app/assets/javascripts/mumuki_laboratory/application/codemirror-builder.js +28 -25
  10. data/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +8 -9
  11. data/app/assets/javascripts/mumuki_laboratory/application/confirmation.js +2 -2
  12. data/app/assets/javascripts/mumuki_laboratory/application/console.js +41 -43
  13. data/app/assets/javascripts/mumuki_laboratory/application/csrf-token.js +9 -12
  14. data/app/assets/javascripts/mumuki_laboratory/application/custom-editor.js +46 -8
  15. data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +15 -16
  16. data/app/assets/javascripts/mumuki_laboratory/application/editors.js +104 -0
  17. data/app/assets/javascripts/mumuki_laboratory/application/elipsis.js +5 -4
  18. data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +32 -0
  19. data/app/assets/javascripts/mumuki_laboratory/application/inputs.js +4 -2
  20. data/app/assets/javascripts/mumuki_laboratory/application/interval.js +2 -4
  21. data/app/assets/javascripts/mumuki_laboratory/application/kids.js +74 -37
  22. data/app/assets/javascripts/mumuki_laboratory/application/load-analytics.js +1 -1
  23. data/app/assets/javascripts/mumuki_laboratory/application/load-error-svg.js +1 -1
  24. data/app/assets/javascripts/mumuki_laboratory/application/messages.js +2 -2
  25. data/app/assets/javascripts/mumuki_laboratory/application/multiple-choice.js +1 -1
  26. data/app/assets/javascripts/mumuki_laboratory/application/multiple-scenarios.js +3 -6
  27. data/app/assets/javascripts/mumuki_laboratory/application/pin.js +3 -5
  28. data/app/assets/javascripts/mumuki_laboratory/application/progress.js +27 -6
  29. data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +60 -0
  30. data/app/assets/javascripts/mumuki_laboratory/application/speech-bubble-renderer.js +12 -5
  31. data/app/assets/javascripts/mumuki_laboratory/application/submission.js +122 -55
  32. data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +93 -0
  33. data/app/assets/javascripts/mumuki_laboratory/application/sync-mode.js +75 -0
  34. data/app/assets/javascripts/mumuki_laboratory/application/timer.js +5 -6
  35. data/app/assets/javascripts/mumuki_laboratory/application/tooltip.js +1 -1
  36. data/app/assets/javascripts/mumuki_laboratory/application/upload.js +1 -1
  37. data/app/assets/javascripts/mumuki_laboratory/application/user.js +1 -1
  38. data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +1 -0
  39. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +43 -5
  40. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_gs-board.scss +3 -0
  41. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids.scss +3 -4
  42. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +55 -0
  43. data/app/controllers/application_controller.rb +1 -0
  44. data/app/controllers/assets_controller.rb +2 -0
  45. data/app/controllers/concerns/with_authorization.rb +4 -0
  46. data/app/controllers/concerns/with_user_discussion_validation.rb +14 -0
  47. data/app/controllers/discussions_controller.rb +6 -14
  48. data/app/controllers/discussions_messages_controller.rb +10 -1
  49. data/app/controllers/exercise_solutions_controller.rb +4 -2
  50. data/app/helpers/application_helper.rb +9 -5
  51. data/app/helpers/discussions_helper.rb +37 -23
  52. data/app/helpers/exercise_input_helper.rb +1 -1
  53. data/app/helpers/{locale_helper.rb → globals_helper.rb} +6 -2
  54. data/app/helpers/icons_helper.rb +3 -3
  55. data/app/mailers/user_mailer.rb +24 -11
  56. data/app/views/book/show.html.erb +1 -1
  57. data/app/views/book_discussions/index.html.erb +3 -3
  58. data/app/views/discussions/_message.html.erb +20 -8
  59. data/app/views/discussions/index.html.erb +0 -1
  60. data/app/views/discussions/new.html.erb +33 -0
  61. data/app/views/discussions/show.html.erb +18 -46
  62. data/app/views/exercise_solutions/_contextualization_results_container.html.erb +1 -1
  63. data/app/views/exercise_solutions/_results_title.html.erb +2 -2
  64. data/app/views/exercises/_read_only.html.erb +33 -6
  65. data/app/views/exercises/show.html.erb +2 -0
  66. data/app/views/layouts/_copyright.html.erb +1 -1
  67. data/app/views/layouts/_discussions.html.erb +21 -3
  68. data/app/views/layouts/_main.html.erb +1 -2
  69. data/app/views/layouts/_progress.html.erb +1 -1
  70. data/app/views/layouts/_progress_bar.html.erb +7 -1
  71. data/app/views/layouts/_test_results.html.erb +1 -1
  72. data/app/views/layouts/application.html.erb +1 -1
  73. data/app/views/layouts/exercise_inputs/editors/_custom.html.erb +1 -1
  74. data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
  75. data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +1 -1
  76. data/app/views/layouts/exercise_inputs/layouts/_input_bottom.html.erb +1 -1
  77. data/app/views/layouts/exercise_inputs/layouts/_input_kindergarten.html.erb +40 -0
  78. data/app/views/layouts/exercise_inputs/layouts/{_input_kids.html.erb → _input_primary.html.erb} +1 -1
  79. data/app/views/layouts/exercise_inputs/layouts/_input_right.html.erb +1 -1
  80. data/app/views/layouts/modals/_kids_context.html.erb +1 -8
  81. data/app/views/user_mailer/1st_reminder.html.erb +1 -1
  82. data/app/views/user_mailer/1st_reminder.text.erb +1 -1
  83. data/app/views/user_mailer/2nd_reminder.html.erb +1 -1
  84. data/app/views/user_mailer/2nd_reminder.text.erb +1 -1
  85. data/app/views/user_mailer/3rd_reminder.html.erb +1 -1
  86. data/app/views/user_mailer/3rd_reminder.text.erb +1 -1
  87. data/app/views/user_mailer/no_submissions_reminder.html.erb +1 -1
  88. data/app/views/user_mailer/no_submissions_reminder.text.erb +1 -1
  89. data/config/routes.rb +2 -1
  90. data/lib/mumuki/laboratory/controllers.rb +1 -0
  91. data/lib/mumuki/laboratory/controllers/incognito_mode.rb +28 -0
  92. data/lib/mumuki/laboratory/controllers/results_rendering.rb +1 -2
  93. data/lib/mumuki/laboratory/locales/en.yml +14 -6
  94. data/lib/mumuki/laboratory/locales/es-CL.yml +292 -0
  95. data/lib/mumuki/laboratory/locales/es.yml +13 -5
  96. data/lib/mumuki/laboratory/locales/pt.yml +12 -6
  97. data/lib/mumuki/laboratory/version.rb +1 -1
  98. data/spec/controllers/confirmations_controller_spec.rb +1 -1
  99. data/spec/controllers/discussions_messages_controller_spec.rb +73 -0
  100. data/spec/controllers/exercise_solutions_controller_spec.rb +41 -6
  101. data/spec/dummy/db/schema.rb +13 -1
  102. data/spec/features/chapter_spec.rb +17 -0
  103. data/spec/features/discussion_flow_spec.rb +190 -0
  104. data/spec/features/exercise_flow_spec.rb +48 -3
  105. data/spec/features/home_public_flow_spec.rb +16 -0
  106. data/spec/features/menu_bar_spec.rb +88 -7
  107. data/spec/helpers/breadcrumbs_helper_spec.rb +1 -1
  108. data/spec/javascripts/bridge-spec.js +5 -0
  109. data/spec/javascripts/csrf-token-spec.js +7 -0
  110. data/spec/javascripts/editors-spec.js +54 -0
  111. data/spec/javascripts/elipsis-spec.js +25 -0
  112. data/spec/javascripts/exercise-spec.js +22 -0
  113. data/spec/javascripts/global-spec.js +6 -0
  114. data/spec/javascripts/results-renderers-spec.js +17 -0
  115. data/spec/javascripts/spec-helper.js +34 -0
  116. data/spec/javascripts/speech-bubble-renderer-spec.js +11 -0
  117. data/spec/javascripts/submissions-store-spec.js +44 -0
  118. data/spec/javascripts/sync-mode-spec.js +15 -0
  119. data/spec/javascripts/timeout-spec.js +5 -0
  120. data/spec/javascripts/timer-spec.js +5 -0
  121. data/spec/mailers/user_mailer_spec.rb +18 -3
  122. data/spec/teaspoon_env.rb +193 -0
  123. metadata +50 -11
  124. data/app/helpers/version_helper.rb +0 -5
  125. data/app/views/layouts/modals/_new_discussion.html.erb +0 -27
  126. data/vendor/assets/javascripts/hotjar.js +0 -8
@@ -1,21 +1,59 @@
1
- var mumuki = mumuki || {};
1
+ /**
2
+ * @typedef {{name: string, value: string}} EditorProperty
3
+ */
2
4
 
3
- (function (mumuki) {
5
+ /**
6
+ * @typedef {{getContent: () => EditorProperty}} CustomEditorSource
7
+ */
4
8
 
5
- var CustomEditor = {
9
+ /**
10
+ * This module allows custom editors to register
11
+ * content sources that can not me mapped to standard selectors {@code mu-custom-editor-value},
12
+ * {@code mu-custom-editor-extra} and {@code mu-custom-editor-test}
13
+ *
14
+ * CustomEditor sources are cleared after page reload even when using turbolinks
15
+ */
16
+ mumuki.CustomEditor = (() => {
17
+
18
+ const CustomEditor = {
19
+ /**
20
+ * @type {CustomEditorSource[]}
21
+ */
6
22
  sources: [],
7
23
 
8
- addSource: function (source) {
24
+ /**
25
+ * @param {CustomEditorSource} source
26
+ */
27
+ addSource(source) {
9
28
  CustomEditor.sources.push(source);
10
29
  },
11
30
 
12
- // Each external source must implement getContent method
31
+ /**
32
+ * @deprecated use getContents instead
33
+ */
34
+ getContent() {
35
+ return this.getContents();
36
+ },
13
37
 
14
- getContent: function () {
38
+ /**
39
+ * @returns {EditorProperty[]}
40
+ */
41
+ getContents() {
15
42
  return CustomEditor.sources.map( e => e.getContent() );
43
+ },
44
+
45
+ clearSources() {
46
+ this.sources = [];
47
+ },
48
+
49
+ get hasSources() {
50
+ return this.sources.length > 0;
16
51
  }
17
52
  };
18
53
 
19
- mumuki.CustomEditor = CustomEditor;
54
+ mumuki.load(() => {
55
+ mumuki.CustomEditor.clearSources();
56
+ });
20
57
 
21
- }(mumuki));
58
+ return CustomEditor;
59
+ })();
@@ -1,23 +1,11 @@
1
- var mumuki = mumuki || {};
2
-
3
- mumuki.load(function () {
4
- var $newDiscussionModal = $('.new-discussion-modal');
5
- var $newDiscussion = $('.discussion-create');
6
-
7
- $newDiscussion.click(function () {
8
- $newDiscussionModal.modal({
9
- backdrop: 'static',
10
- keyboard: false
11
- });
12
- });
13
-
1
+ mumuki.load(() => {
14
2
  var $subscriptionSpans = $('.discussion-subscription > span');
15
3
  var $upvoteSpans = $('.discussion-upvote > span');
16
4
 
17
5
  function createNewMessageEditor() {
18
6
  var $textarea = $("#new-discussion-message");
19
7
  var textarea = $textarea[0];
20
- if(!textarea) return;
8
+ if (!textarea) return;
21
9
 
22
10
  return new mumuki.editor.CodeMirrorBuilder(textarea)
23
11
  .setupSimpleEditor()
@@ -39,7 +27,7 @@ mumuki.load(function () {
39
27
 
40
28
  createReadOnlyEditors();
41
29
  createNewMessageEditor();
42
-
30
+
43
31
  var Forum = {
44
32
  toggleButton: function (spans) {
45
33
  spans.toggleClass('hidden');
@@ -64,10 +52,21 @@ mumuki.load(function () {
64
52
  discussionPostAndToggle: function (url, elem) {
65
53
  Forum.discussionPost(url).done(Forum.toggleButton(elem))
66
54
  },
67
- discussionMessageToggleApprove : function (url, elem) {
55
+ discussionMessageToggleApprove: function (url, elem) {
68
56
  Forum.discussionPost(url).done(function () {
69
57
  elem.toggleClass("selected");
70
58
  })
59
+ },
60
+ discussionMessageToggleNotActuallyAQuestion: function (url, elem) {
61
+ Forum.discussionPost(url).done(function () {
62
+ elem.toggleClass("selected");
63
+ })
64
+ },
65
+ discussionsToggleCheckbox: function (elem) {
66
+ const key = elem.attr('name');
67
+ const params = new URLSearchParams(location.search);
68
+ elem.is(':checked') ? params.set(key, elem.val()) : params.delete(key);
69
+ location.search = params.toString();
71
70
  }
72
71
  };
73
72
 
@@ -0,0 +1,104 @@
1
+ /**
2
+ * This module allows to read and write the current editor's contents
3
+ * regardless if it is an standard editor or a custom editor
4
+ */
5
+ mumuki.editors = {
6
+ /**
7
+ * @type {() => void}
8
+ */
9
+ _contentSyncer: null,
10
+
11
+ /**
12
+ * Updates the current editor's content
13
+ *
14
+ * @param {string} content
15
+ */
16
+ setContent(content) {
17
+ const $customEditor = $('#mu-custom-editor-value');
18
+ if ($customEditor.length) {
19
+ $customEditor.val(content);
20
+ } else {
21
+ mumuki.editor.setContent(content);
22
+ }
23
+ },
24
+
25
+
26
+ /**
27
+ * @returns {EditorProperty[]}
28
+ */
29
+ getContents() {
30
+ return mumuki.CustomEditor.hasSources ?
31
+ mumuki.CustomEditor.getContents() :
32
+ this.getStandardEditorContents();
33
+ },
34
+
35
+ /**
36
+ * Syncs and returns the content objects of the standard editor form
37
+ *
38
+ * This content object may include keys like {@code content},
39
+ * {@code content_extra} and {@code content_test}
40
+ *
41
+ * @returns {EditorProperty[]}
42
+ */
43
+ getStandardEditorContents() {
44
+ this._syncContent();
45
+ return $('.new_solution').serializeArray();
46
+ },
47
+
48
+ /**
49
+ * Answers a submission object with a key for each of the current
50
+ * editor sources.
51
+ *
52
+ * This method will use CustomEditor's sources if availble, or
53
+ * standard editor's content sources otherwise
54
+ *
55
+ * @returns {Submission}
56
+ */
57
+ getSubmission() {
58
+ let content = {};
59
+ let contents = this.getContents();
60
+ contents.forEach((it) => {
61
+ content[it.name] = it.value;
62
+ });
63
+ return content;
64
+ },
65
+
66
+ /**
67
+ * Copies current solution from it native rendering components
68
+ * to the appropriate submission form elements.
69
+ *
70
+ * Both editors and runners with a custom editor that don't register a source should
71
+ * register its own syncer function in order to {@link syncContent} work properly.
72
+ *
73
+ * @see registerContentSyncer
74
+ * @see CustomEditor#addSource
75
+ */
76
+ _syncContent() {
77
+ if (this._contentSyncer) {
78
+ this._contentSyncer();
79
+ }
80
+ },
81
+
82
+ /**
83
+ * Sets a content syncer, that will be used by {@link _syncContent}
84
+ * in ordet to dump solution into the submission form fields.
85
+ *
86
+ * Each editor should have its own syncer registered - otherwise previous or none may be used
87
+ * causing unpredicatble behaviours - or cleared by passing {@code null}.
88
+ *
89
+ * As a particular case, runners with custom editors that don't add sources using {@link CustomEditor#addSource}
90
+ * should set the {@code #mu-custom-editor-value} value within its syncer.
91
+ *
92
+ * @param {() => void} [syncer] the syncer, or null, if no sync'ing is needed
93
+ */
94
+ registerContentSyncer(syncer = null) {
95
+ this._contentSyncer = syncer;
96
+ },
97
+
98
+ /**
99
+ * @param {CustomEditorSource} source
100
+ */
101
+ addCustomSource(source) {
102
+ mumuki.CustomEditor.addSource(source)
103
+ }
104
+ };
@@ -7,7 +7,7 @@
7
7
  //
8
8
  //
9
9
  // This module assumes the strings are already markdown-like escaped code strings, not plain code
10
- (function (mumuki) {
10
+ mumuki.elipsis = (() => {
11
11
 
12
12
  function elipsis(code) {
13
13
  return code
@@ -16,8 +16,7 @@
16
16
  .replace(/<description-for-student\[([^\]]*)\]@[\s\S]*?@description-for-student>/g, ' ... $1 ... ');
17
17
  }
18
18
 
19
- mumuki.elipsis = elipsis;
20
- mumuki.elipsis.replaceHtml = () => {
19
+ elipsis.replaceHtml = () => {
21
20
  let $elipsis = $('.mu-elipsis');
22
21
  $elipsis.each((it, e) => {
23
22
  let $e = $(e);
@@ -28,4 +27,6 @@
28
27
  mumuki.load(() => {
29
28
  mumuki.elipsis.replaceHtml();
30
29
  });
31
- })(mumuki);
30
+
31
+ return elipsis;
32
+ })();
@@ -0,0 +1,32 @@
1
+ mumuki.exercise = {
2
+ /**
3
+ * The current exercise's id
4
+ *
5
+ * @type {number}
6
+ * */
7
+ id: null,
8
+
9
+ /**
10
+ * The current exercise's layout
11
+ *
12
+ * @type {"input_right" | "input_bottom" | "input_primary" | "input_kindergarten"}
13
+ * */
14
+ layout: null,
15
+
16
+ /**
17
+ * Set global current exercise information
18
+ */
19
+ load() {
20
+ const $muExerciseId = $('#mu-exercise-id');
21
+ if ($muExerciseId.length) {
22
+ this.id = Number($muExerciseId.val());
23
+ // @ts-ignore
24
+ this.layout = $('#mu-exercise-layout').val();
25
+ } else {
26
+ this.id = null;
27
+ this.layout = null;
28
+ }
29
+ }
30
+ }
31
+
32
+ mumuki.load(() => mumuki.exercise.load())
@@ -1,12 +1,14 @@
1
- (() => {
1
+ mumuki.onInputsReady = (() => {
2
2
  // Declares a `document.ready` handler that will be
3
3
  // activated only when there is at least one element that match
4
4
  // the given selector
5
- mumuki.onInputsReady = (inputsSelector, callback) => {
5
+ function onInputsReady(inputsSelector, callback) {
6
6
  $(document).ready((event) => {
7
7
  if ($(inputsSelector).length === 0) return;
8
8
 
9
9
  callback(event);
10
10
  })
11
11
  }
12
+
13
+ return onInputsReady;
12
14
  })();
@@ -1,6 +1,4 @@
1
- var mumuki = mumuki || {};
2
-
3
- (function (mumuki) {
1
+ (function () {
4
2
  // When using Turbolinks, intervals loaded inside <body> aren't destroyed on page changes
5
3
  // Use this function instead if you want the behaviour of a regular setInterval
6
4
  mumuki.setInterval = function (intervalFunction, milliseconds) {
@@ -14,4 +12,4 @@ var mumuki = mumuki || {};
14
12
  return interval;
15
13
  }.bind(this);
16
14
 
17
- }(mumuki));
15
+ }());
@@ -1,20 +1,36 @@
1
- mumuki.load(function () {
2
- var $bubble = $('.mu-kids-character-speech-bubble').children('.mu-kids-character-speech-bubble-normal');
3
-
4
- var availableTabs = ['.description', '.hint'];
5
- var $speechParagraphs, paragraphHeight, scrollHeight, nextSpeechBlinking;
6
- var currentParagraphIndex = 0;
7
- var paragraphCount = 1;
8
- var paragraphsLines = 2;
9
- var $prevSpeech = $('.mu-kids-character-speech-bubble-normal > .mu-kids-prev-speech').hide();
10
- var $nextSpeech = $('.mu-kids-character-speech-bubble-normal > .mu-kids-next-speech');
11
- var $speechTabs = $('.mu-kids-character-speech-bubble-tabs > li:not(.separator)');
12
- var $defaultSpeechTabName = 'description';
13
- var $texts = $bubble.children(availableTabs.join(", "));
14
- var $hint = $('.mu-kids-hint');
15
- var $description = $('.mu-kids-description');
16
- var discussionsLinkHtml = $('#mu-kids-discussion-link-html').html();
17
- var contextModalButton = new mumuki.Button($('.mu-kids-context .modal-footer button'));
1
+ mumuki.load(() => {
2
+ let $bubble = $('.mu-kids-character-speech-bubble').children('.mu-kids-character-speech-bubble-normal');
3
+
4
+ let availableTabs = ['.description', '.hint'];
5
+ let $speechParagraphs, paragraphHeight, scrollHeight, nextSpeechBlinking;
6
+ let currentParagraphIndex = 0;
7
+ let paragraphCount = 1;
8
+ let paragraphsLines = 2;
9
+ let $prevSpeech = $('.mu-kids-character-speech-bubble-normal > .mu-kids-prev-speech').hide();
10
+ let $nextSpeech = $('.mu-kids-character-speech-bubble-normal > .mu-kids-next-speech');
11
+ let $speechTabs = $('.mu-kids-character-speech-bubble-tabs > li:not(.separator)');
12
+ let $defaultSpeechTabName = 'description';
13
+ let $texts = $bubble.children(availableTabs.join(", "));
14
+ let $hint = $('.mu-kids-hint');
15
+ let $description = $('.mu-kids-description');
16
+ let discussionsLinkHtml = $('#mu-kids-discussion-link-html').html();
17
+ let $kidsContext = $('#mu-kids-context');
18
+ let contextModalButton = new mumuki.Button($('.mu-kids-context .modal-footer button'));
19
+
20
+ // It is important that context is shown as early as possible
21
+ // in order to display the loading animation
22
+ function showContext() {
23
+ $kidsContext.modal({
24
+ backdrop: 'static',
25
+ keyboard: false
26
+ });
27
+ }
28
+
29
+ $kidsContext.on('hidden.bs.modal', function () {
30
+ animateSpeech();
31
+ });
32
+
33
+ showContext();
18
34
 
19
35
  function floatFromPx(value) {
20
36
  return parseFloat(value.substring(0, value.length - 2));
@@ -57,6 +73,25 @@ mumuki.load(function () {
57
73
  isVisible ? element.show() : element.hide();
58
74
  }
59
75
 
76
+ /**
77
+ * Assigns propert widths to the states and blocks areas
78
+ * depending on the presence and type of available states
79
+ *
80
+ * @param {*} $muKidsStateImage
81
+ * @param {*} $muKidsStatesContainer
82
+ * @param {*} $muKidsBlocks
83
+ * @param {number} fullMargin
84
+ */
85
+ function distributeAreas($muKidsStateImage, $muKidsStatesContainer, $muKidsBlocks, fullMargin) {
86
+ if ($muKidsStateImage.children().length) {
87
+ var ratio = $muKidsStatesContainer.hasClass('mu-kids-single-state') ? 1 : 2;
88
+ $muKidsStatesContainer.width($muKidsStatesContainer.height() / ratio * 1.25 - fullMargin);
89
+ } else {
90
+ $muKidsStatesContainer.width(0);
91
+ $muKidsBlocks.width('100%');
92
+ }
93
+ }
94
+
60
95
  mumuki.kids = {
61
96
 
62
97
  // ==========
@@ -134,6 +169,8 @@ mumuki.load(function () {
134
169
  contextModalButton.enable();
135
170
  },
136
171
 
172
+ showContext,
173
+
137
174
  // ===========
138
175
  // Private API
139
176
  // ===========
@@ -274,9 +311,6 @@ mumuki.load(function () {
274
311
  mumuki.kids.resultAction.errored = mumuki.kids._showOnCharacterBubble;
275
312
  mumuki.kids.resultAction.pending = mumuki.kids._showOnCharacterBubble;
276
313
 
277
- $('.mu-kids-context').on('hidden.bs.modal', function () {
278
- animateSpeech();
279
- });
280
314
 
281
315
  $(document).ready(() => {
282
316
  // Speech initialization
@@ -292,13 +326,15 @@ mumuki.load(function () {
292
326
 
293
327
  $speechTabs.each(function (i) {
294
328
  var $tab = $($speechTabs[i]);
295
- $tab.click(function () {
296
- $speechTabs.removeClass('active');
297
- $tab.addClass('active');
298
- $texts.hide();
299
- $bubble.children('.' + $tab.data('target')).show();
300
- updateSpeechParagraphs();
301
- })
329
+ if ($tab.data('target')) {
330
+ $tab.click(function () {
331
+ $speechTabs.removeClass('active');
332
+ $tab.addClass('active');
333
+ $texts.hide();
334
+ $bubble.children('.' + $tab.data('target')).show();
335
+ updateSpeechParagraphs();
336
+ })
337
+ }
302
338
  });
303
339
 
304
340
  if (paragraphCount > 1) {
@@ -315,24 +351,25 @@ mumuki.load(function () {
315
351
  });
316
352
 
317
353
  // States initial resizing
318
-
319
354
  mumuki.resize(function () {
320
355
  var margin = 15;
321
356
  var fullMargin = margin * 2;
322
357
 
323
- var $muKidsStatesContainer = $('.mu-kids-states');
324
- var $muKidsStates = $('.mu-kids-state');
325
-
326
- var dimension = $muKidsStatesContainer.height() / 2 * 1.25 - fullMargin;
327
- $muKidsStatesContainer.width(dimension);
358
+ let $muKidsStatesContainer = $('.mu-kids-states');
359
+ let $muKidsStates = $('.mu-kids-state');
360
+ let $muKidsBlocks = $('.mu-kids-blocks');
361
+ let $muKidsExercise = $('.mu-kids-exercise');
362
+ let $muKidsExerciseDescription = $('.mu-kids-exercise-description');
363
+ let $muKidsStateImage = $('.mu-kids-state-image');
328
364
 
329
- var $muKidsExercise = $('.mu-kids-exercise');
330
- var $muKidsExerciseDescription = $('.mu-kids-exercise-description');
365
+ distributeAreas($muKidsStateImage, $muKidsStatesContainer, $muKidsBlocks, fullMargin);
331
366
 
332
- $muKidsExerciseDescription.width($muKidsExercise.width() - $muKidsStatesContainer.width() - margin);
367
+ if (!$muKidsExerciseDescription.hasClass('mu-kids-exercise-description-fixed')) {
368
+ $muKidsExerciseDescription.width($muKidsExercise.width() - $muKidsStatesContainer.width() - margin);
369
+ }
333
370
 
334
371
  $muKidsStates.each((index, state) => mumuki.kids.scaleState($(state), fullMargin));
335
- mumuki.kids.scaleBlocksArea($('.mu-kids-blocks'));
372
+ mumuki.kids.scaleBlocksArea($muKidsBlocks);
336
373
 
337
374
  if (paragraphCount <= 1) clearInterval(nextSpeechBlinking);
338
375