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.
- checksums.yaml +4 -4
- data/README.md +203 -2
- data/Rakefile +9 -0
- data/app/assets/javascripts/mumuki_laboratory/application.js +0 -1
- data/app/assets/javascripts/mumuki_laboratory/application/alias-modes.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/assets-loader.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +82 -47
- data/app/assets/javascripts/mumuki_laboratory/application/button.js +90 -1
- data/app/assets/javascripts/mumuki_laboratory/application/codemirror-builder.js +28 -25
- data/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +8 -9
- data/app/assets/javascripts/mumuki_laboratory/application/confirmation.js +2 -2
- data/app/assets/javascripts/mumuki_laboratory/application/console.js +41 -43
- data/app/assets/javascripts/mumuki_laboratory/application/csrf-token.js +9 -12
- data/app/assets/javascripts/mumuki_laboratory/application/custom-editor.js +46 -8
- data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +15 -16
- data/app/assets/javascripts/mumuki_laboratory/application/editors.js +104 -0
- data/app/assets/javascripts/mumuki_laboratory/application/elipsis.js +5 -4
- data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +32 -0
- data/app/assets/javascripts/mumuki_laboratory/application/inputs.js +4 -2
- data/app/assets/javascripts/mumuki_laboratory/application/interval.js +2 -4
- data/app/assets/javascripts/mumuki_laboratory/application/kids.js +74 -37
- data/app/assets/javascripts/mumuki_laboratory/application/load-analytics.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/load-error-svg.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/messages.js +2 -2
- data/app/assets/javascripts/mumuki_laboratory/application/multiple-choice.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/multiple-scenarios.js +3 -6
- data/app/assets/javascripts/mumuki_laboratory/application/pin.js +3 -5
- data/app/assets/javascripts/mumuki_laboratory/application/progress.js +27 -6
- data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +60 -0
- data/app/assets/javascripts/mumuki_laboratory/application/speech-bubble-renderer.js +12 -5
- data/app/assets/javascripts/mumuki_laboratory/application/submission.js +122 -55
- data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +93 -0
- data/app/assets/javascripts/mumuki_laboratory/application/sync-mode.js +75 -0
- data/app/assets/javascripts/mumuki_laboratory/application/timer.js +5 -6
- data/app/assets/javascripts/mumuki_laboratory/application/tooltip.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/upload.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/user.js +1 -1
- data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +1 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +43 -5
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_gs-board.scss +3 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids.scss +3 -4
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +55 -0
- data/app/controllers/application_controller.rb +1 -0
- data/app/controllers/assets_controller.rb +2 -0
- data/app/controllers/concerns/with_authorization.rb +4 -0
- data/app/controllers/concerns/with_user_discussion_validation.rb +14 -0
- data/app/controllers/discussions_controller.rb +6 -14
- data/app/controllers/discussions_messages_controller.rb +10 -1
- data/app/controllers/exercise_solutions_controller.rb +4 -2
- data/app/helpers/application_helper.rb +9 -5
- data/app/helpers/discussions_helper.rb +37 -23
- data/app/helpers/exercise_input_helper.rb +1 -1
- data/app/helpers/{locale_helper.rb → globals_helper.rb} +6 -2
- data/app/helpers/icons_helper.rb +3 -3
- data/app/mailers/user_mailer.rb +24 -11
- data/app/views/book/show.html.erb +1 -1
- data/app/views/book_discussions/index.html.erb +3 -3
- data/app/views/discussions/_message.html.erb +20 -8
- data/app/views/discussions/index.html.erb +0 -1
- data/app/views/discussions/new.html.erb +33 -0
- data/app/views/discussions/show.html.erb +18 -46
- data/app/views/exercise_solutions/_contextualization_results_container.html.erb +1 -1
- data/app/views/exercise_solutions/_results_title.html.erb +2 -2
- data/app/views/exercises/_read_only.html.erb +33 -6
- data/app/views/exercises/show.html.erb +2 -0
- data/app/views/layouts/_copyright.html.erb +1 -1
- data/app/views/layouts/_discussions.html.erb +21 -3
- data/app/views/layouts/_main.html.erb +1 -2
- data/app/views/layouts/_progress.html.erb +1 -1
- data/app/views/layouts/_progress_bar.html.erb +7 -1
- data/app/views/layouts/_test_results.html.erb +1 -1
- data/app/views/layouts/application.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/editors/_custom.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/layouts/_input_bottom.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/layouts/_input_kindergarten.html.erb +40 -0
- data/app/views/layouts/exercise_inputs/layouts/{_input_kids.html.erb → _input_primary.html.erb} +1 -1
- data/app/views/layouts/exercise_inputs/layouts/_input_right.html.erb +1 -1
- data/app/views/layouts/modals/_kids_context.html.erb +1 -8
- data/app/views/user_mailer/1st_reminder.html.erb +1 -1
- data/app/views/user_mailer/1st_reminder.text.erb +1 -1
- data/app/views/user_mailer/2nd_reminder.html.erb +1 -1
- data/app/views/user_mailer/2nd_reminder.text.erb +1 -1
- data/app/views/user_mailer/3rd_reminder.html.erb +1 -1
- data/app/views/user_mailer/3rd_reminder.text.erb +1 -1
- data/app/views/user_mailer/no_submissions_reminder.html.erb +1 -1
- data/app/views/user_mailer/no_submissions_reminder.text.erb +1 -1
- data/config/routes.rb +2 -1
- data/lib/mumuki/laboratory/controllers.rb +1 -0
- data/lib/mumuki/laboratory/controllers/incognito_mode.rb +28 -0
- data/lib/mumuki/laboratory/controllers/results_rendering.rb +1 -2
- data/lib/mumuki/laboratory/locales/en.yml +14 -6
- data/lib/mumuki/laboratory/locales/es-CL.yml +292 -0
- data/lib/mumuki/laboratory/locales/es.yml +13 -5
- data/lib/mumuki/laboratory/locales/pt.yml +12 -6
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/controllers/confirmations_controller_spec.rb +1 -1
- data/spec/controllers/discussions_messages_controller_spec.rb +73 -0
- data/spec/controllers/exercise_solutions_controller_spec.rb +41 -6
- data/spec/dummy/db/schema.rb +13 -1
- data/spec/features/chapter_spec.rb +17 -0
- data/spec/features/discussion_flow_spec.rb +190 -0
- data/spec/features/exercise_flow_spec.rb +48 -3
- data/spec/features/home_public_flow_spec.rb +16 -0
- data/spec/features/menu_bar_spec.rb +88 -7
- data/spec/helpers/breadcrumbs_helper_spec.rb +1 -1
- data/spec/javascripts/bridge-spec.js +5 -0
- data/spec/javascripts/csrf-token-spec.js +7 -0
- data/spec/javascripts/editors-spec.js +54 -0
- data/spec/javascripts/elipsis-spec.js +25 -0
- data/spec/javascripts/exercise-spec.js +22 -0
- data/spec/javascripts/global-spec.js +6 -0
- data/spec/javascripts/results-renderers-spec.js +17 -0
- data/spec/javascripts/spec-helper.js +34 -0
- data/spec/javascripts/speech-bubble-renderer-spec.js +11 -0
- data/spec/javascripts/submissions-store-spec.js +44 -0
- data/spec/javascripts/sync-mode-spec.js +15 -0
- data/spec/javascripts/timeout-spec.js +5 -0
- data/spec/javascripts/timer-spec.js +5 -0
- data/spec/mailers/user_mailer_spec.rb +18 -3
- data/spec/teaspoon_env.rb +193 -0
- metadata +50 -11
- data/app/helpers/version_helper.rb +0 -5
- data/app/views/layouts/modals/_new_discussion.html.erb +0 -27
- data/vendor/assets/javascripts/hotjar.js +0 -8
@@ -1,4 +1,4 @@
|
|
1
|
-
mumuki.load(
|
1
|
+
mumuki.load(() => {
|
2
2
|
var Chat = {
|
3
3
|
$body: function () {
|
4
4
|
return $('body')
|
@@ -61,7 +61,7 @@ mumuki.load(function () {
|
|
61
61
|
Chat.readMessages(readUrl);
|
62
62
|
}
|
63
63
|
|
64
|
-
function error(
|
64
|
+
function error(_xhr) {
|
65
65
|
Chat.tokenRequest({
|
66
66
|
url: errorUrl,
|
67
67
|
success: renderHTML,
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
(function (mumuki) {
|
1
|
+
mumuki.MultipleScenarios = (() => {
|
4
2
|
|
5
3
|
const setControlVisibility = function ($control, visible) {
|
6
4
|
visible ? $control.show() : $control.hide();
|
@@ -147,6 +145,5 @@ var mumuki = mumuki || {};
|
|
147
145
|
}
|
148
146
|
}
|
149
147
|
|
150
|
-
|
151
|
-
|
152
|
-
}(mumuki));
|
148
|
+
return MultipleScenarios;
|
149
|
+
})();
|
@@ -1,12 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
(function (mumuki) {
|
1
|
+
mumuki.pin = (() => {
|
4
2
|
function smoothScrollToElement(domElement) {
|
5
3
|
var SPEED = 1000;
|
6
4
|
$('html, body').animate({scrollTop: domElement.offset().top}, SPEED);
|
7
5
|
}
|
8
6
|
|
9
|
-
|
7
|
+
return {
|
10
8
|
scroll: function () {
|
11
9
|
var scrollPin = $('.scroll-pin');
|
12
10
|
if (scrollPin.length) {
|
@@ -14,4 +12,4 @@ var mumuki = mumuki || {};
|
|
14
12
|
}
|
15
13
|
}
|
16
14
|
}
|
17
|
-
})(
|
15
|
+
})();
|
@@ -1,10 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
mumuki.progress = (() => {
|
2
|
+
/**
|
3
|
+
* Updates the current exercise progress indicator
|
4
|
+
*
|
5
|
+
* @param {SubmissionResult} data
|
6
|
+
* */
|
7
|
+
function updateProgressBarAndShowModal(data) {
|
6
8
|
$('.progress-list-item.active').attr('class', data.class_for_progress_list_item);
|
7
9
|
if(data.guide_finished_by_solution) $('#guide-done').modal();
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Update all links in the progress bar with the given function
|
14
|
+
*
|
15
|
+
* @param {(anchor: JQuery) => string} f
|
16
|
+
*/
|
17
|
+
function updateWholeProgressBar(f) {
|
18
|
+
$('.progress-list-item').each((_, it) => {
|
19
|
+
const $anchor = $(it);
|
20
|
+
$anchor.attr('class', f($anchor))
|
21
|
+
});
|
22
|
+
}
|
23
|
+
|
24
|
+
return {
|
25
|
+
updateProgressBarAndShowModal,
|
26
|
+
updateWholeProgressBar
|
8
27
|
};
|
28
|
+
})();
|
9
29
|
|
10
|
-
}
|
30
|
+
/** @deprecated use {@code mumuki.progress.updateProgressBarAndShowModal} instead */
|
31
|
+
mumuki.updateProgressBarAndShowModal = mumuki.progress.updateProgressBarAndShowModal;
|
@@ -0,0 +1,60 @@
|
|
1
|
+
mumuki.renderers = mumuki.renderers || {};
|
2
|
+
mumuki.renderers.results = (() => {
|
3
|
+
|
4
|
+
|
5
|
+
// ==========================
|
6
|
+
// View function for building
|
7
|
+
// the results UI
|
8
|
+
// ==========================
|
9
|
+
|
10
|
+
/**
|
11
|
+
* @param {SubmissionStatus} status
|
12
|
+
* @returns {string}
|
13
|
+
*/
|
14
|
+
function iconForStatus(status) {
|
15
|
+
switch (status) {
|
16
|
+
case "errored": return "fa-minus-circle";
|
17
|
+
case "failed": return "fa-times-circle";
|
18
|
+
case "passed_with_warnings": return "fa-exclamation-circle";
|
19
|
+
case "passed": return "fa-check-circle";
|
20
|
+
case "pending": return "fa-circle";
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* @param {SubmissionStatus} status
|
26
|
+
* @returns {string}
|
27
|
+
*/
|
28
|
+
function classForStatus(status) {
|
29
|
+
switch (status) {
|
30
|
+
case "errored": return "broken";
|
31
|
+
case "failed": return "danger";
|
32
|
+
case "passed_with_warnings": return "warning";
|
33
|
+
case "passed": return "success";
|
34
|
+
case "pending": return "muted";
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
/**
|
40
|
+
* @param {SubmissionStatus} status
|
41
|
+
* @param {boolean} [active]
|
42
|
+
* @returns {string}
|
43
|
+
*/
|
44
|
+
function progressListItemClassForStatus(status, active = false) {
|
45
|
+
return `progress-list-item text-center ${classForStatus(status)} ${active ? 'active' : ''}`;
|
46
|
+
}
|
47
|
+
|
48
|
+
return {
|
49
|
+
classForStatus,
|
50
|
+
iconForStatus,
|
51
|
+
progressListItemClassForStatus
|
52
|
+
}
|
53
|
+
})();
|
54
|
+
|
55
|
+
/** @deprecated use {@code mumuki.renderers.results.classForStatus} instead */
|
56
|
+
mumuki.renderers.classForStatus = mumuki.renderers.results.classForStatus;
|
57
|
+
/** @deprecated use {@code mumuki.renderers.results.iconForStatus} instead */
|
58
|
+
mumuki.renderers.iconForStatus = mumuki.renderers.results.iconForStatus;
|
59
|
+
/** @deprecated use {@code mumuki.renderers.results.progressListItemClassForStatus} instead */
|
60
|
+
mumuki.renderers.progressListItemClassForStatus = mumuki.renderers.results.progressListItemClassForStatus;
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
mumuki.renderers = mumuki.renderers || {};
|
2
|
+
mumuki.renderers.speechBubble = (()=> {
|
2
3
|
|
3
4
|
function renderSpeechBubbleResultItem(item) {
|
4
5
|
return `
|
@@ -93,7 +94,13 @@
|
|
93
94
|
}
|
94
95
|
}
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
}
|
97
|
+
return {
|
98
|
+
SpeechBubbleRenderer,
|
99
|
+
renderSpeechBubbleResultItem
|
100
|
+
}
|
101
|
+
})();
|
102
|
+
|
103
|
+
/** @deprecated use {@code mumuki.renderers.speechBubble.SpeechBubbleRenderer} instead */
|
104
|
+
mumuki.renderers.SpeechBubbleRenderer = mumuki.renderers.speechBubble.SpeechBubbleRenderer;
|
105
|
+
/** @deprecated use {@code mumuki.renderers.speechBubble.renderSpeechBubbleResultItem} instead */
|
106
|
+
mumuki.renderers.renderSpeechBubbleResultItem = mumuki.renderers.speechBubble.renderSpeechBubbleResultItem;
|
@@ -1,32 +1,42 @@
|
|
1
|
-
|
1
|
+
mumuki.submission = (() => {
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
// =============
|
4
|
+
// UI Components
|
5
|
+
// =============
|
6
|
+
|
7
|
+
function animateTimeoutError(submitButton) {
|
8
|
+
let scene = new muvment.Scene($('.submission-result-error-animation'));
|
9
|
+
scene.addState(mumuki.errorState('timeout_1').onStart(submitButton.setOriginalContent.bind(submitButton)).onEndSwitch(scene, 'timeout_2'))
|
10
|
+
.addState(mumuki.errorState('timeout_2').onEndSwitch(scene, 'timeout_3'))
|
11
|
+
.addState(mumuki.errorState('timeout_3').onStart(submitButton.enable.bind(submitButton)))
|
12
|
+
.play();
|
8
13
|
}
|
9
14
|
|
10
|
-
ResultsBox
|
11
|
-
|
15
|
+
class ResultsBox {
|
16
|
+
constructor(submissionsResults) {
|
17
|
+
this.submissionsResultsArea = submissionsResults;
|
18
|
+
this.processingTemplate = $('#processing-template');
|
19
|
+
this.submissionsErrorTemplate = $(".submission-result-error");
|
20
|
+
}
|
21
|
+
waiting() {
|
12
22
|
this.submissionsResultsArea.html(this.processingTemplate.html());
|
13
23
|
this.submissionsErrorTemplate.hide();
|
14
|
-
}
|
15
|
-
success
|
24
|
+
}
|
25
|
+
success(data, submitButton) {
|
16
26
|
this.submissionsResultsArea.html(data.html);
|
17
27
|
data.status === 'aborted' ? this.error(submitButton) : submitButton.enable();
|
18
28
|
mumuki.updateProgressBarAndShowModal(data);
|
19
|
-
}
|
20
|
-
error
|
29
|
+
}
|
30
|
+
error(submitButton) {
|
21
31
|
this.submissionsResultsArea.html('');
|
22
32
|
this.submissionsErrorTemplate.show();
|
23
33
|
animateTimeoutError(submitButton);
|
24
|
-
}
|
25
|
-
done
|
34
|
+
}
|
35
|
+
done(data, submitButton) {
|
26
36
|
submitButton.updateAttemptsLeft(data);
|
27
37
|
mumuki.pin.scroll();
|
28
38
|
}
|
29
|
-
}
|
39
|
+
}
|
30
40
|
|
31
41
|
class SubmitButton extends mumuki.Button {
|
32
42
|
|
@@ -44,31 +54,61 @@ var mumuki = mumuki || {};
|
|
44
54
|
this.preventClick();
|
45
55
|
}
|
46
56
|
}
|
47
|
-
|
48
57
|
}
|
49
58
|
|
50
|
-
mumuki.load(function () {
|
51
|
-
var submissionsResults = $('.submission-results');
|
52
|
-
if (!submissionsResults) return;
|
53
59
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
// ==========
|
61
|
+
// Processing
|
62
|
+
// ==========
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Process solution, which consist of making buttons wait, sending to server, rendering results,
|
66
|
+
* restoring buttons state.
|
67
|
+
*
|
68
|
+
* The actual implementation of this method depends on contextual {@link _solutionProcessor}, which can
|
69
|
+
* be configured using {@link _registerSolutionProcessor}. Currently there are only two available processors -
|
70
|
+
* {@link _kidsSolutionProcessor} and {@link _classicSolutionProcessor} - which are automatically choosen depending
|
71
|
+
* on the exercise DOM.
|
72
|
+
*
|
73
|
+
* @param {Submission} solution
|
74
|
+
*/
|
75
|
+
function processSolution(solution) {
|
76
|
+
mumuki.submission._solutionProcessor(solution);
|
77
|
+
}
|
58
78
|
|
59
|
-
|
79
|
+
/**
|
80
|
+
* Configures a callback for processing a solution.
|
81
|
+
*
|
82
|
+
* This method is called internally by {@link _selectSolutionProcessor}
|
83
|
+
* and should normally not be called by runners editor, but is exposed
|
84
|
+
* for further non-standard customizations.
|
85
|
+
*
|
86
|
+
* @param {({solution: object}) => void} processor
|
87
|
+
*/
|
88
|
+
function _registerSolutionProcessor(processor) {
|
89
|
+
mumuki.submission._solutionProcessor = processor;
|
90
|
+
}
|
60
91
|
|
61
|
-
|
92
|
+
/** Processor for kids layouts */
|
93
|
+
function _kidsSolutionProcessor(bridge, submitButton) {
|
94
|
+
return (solution) => {
|
95
|
+
submitButton.wait();
|
96
|
+
bridge._submitSolution(solution).always(function (data) {
|
97
|
+
submitButton.ready(() => {
|
98
|
+
mumuki.kids.restart();
|
99
|
+
submitButton.continue();
|
100
|
+
});
|
101
|
+
mumuki.kids.showResult(data);
|
102
|
+
});
|
103
|
+
}
|
104
|
+
}
|
62
105
|
|
63
|
-
|
64
|
-
|
106
|
+
/** Processor for non-kids layouts */
|
107
|
+
function _classicSolutionProcessor(bridge, submitButton, resultsBox) {
|
108
|
+
return (solution) => {
|
65
109
|
submitButton.disable();
|
66
110
|
submitButton.setWaitingText();
|
67
111
|
resultsBox.waiting();
|
68
|
-
|
69
|
-
mumuki.editor.syncContent();
|
70
|
-
var solution = getContent();
|
71
|
-
|
72
112
|
bridge._submitSolution(solution).done(function (data) {
|
73
113
|
resultsBox.success(data, submitButton);
|
74
114
|
}).fail(function () {
|
@@ -77,36 +117,63 @@ var mumuki = mumuki || {};
|
|
77
117
|
$(document).renderMuComponents();
|
78
118
|
resultsBox.done(data, submitButton);
|
79
119
|
});
|
80
|
-
}
|
81
|
-
|
82
|
-
submitButton.checkAttemptsLeft();
|
83
|
-
});
|
120
|
+
}
|
121
|
+
}
|
84
122
|
|
85
|
-
|
86
|
-
|
123
|
+
/** Selects the most appropriate solution processor */
|
124
|
+
function _selectSolutionProcessor(submitButton, $submissionsResults) {
|
125
|
+
const bridge = new mumuki.bridge.Laboratory();
|
126
|
+
let processor;
|
127
|
+
if ($('.mu-kids-exercise').length) {
|
128
|
+
processor = _kidsSolutionProcessor(bridge, submitButton);
|
129
|
+
} else {
|
130
|
+
processor = _classicSolutionProcessor(bridge, submitButton, new ResultsBox($submissionsResults));
|
131
|
+
}
|
132
|
+
mumuki.submission._registerSolutionProcessor(processor);
|
87
133
|
}
|
88
134
|
|
89
|
-
function getContent(){
|
90
|
-
var content = {};
|
91
135
|
|
92
|
-
|
93
|
-
|
136
|
+
// ===========
|
137
|
+
// Entry Point
|
138
|
+
// ===========
|
139
|
+
|
140
|
+
mumuki.load(() => {
|
141
|
+
var $submissionsResults = $('.submission-results');
|
142
|
+
if (!$submissionsResults) return;
|
143
|
+
|
144
|
+
const $btnSubmit = $('.btn-submit');
|
145
|
+
const submitButton = new SubmitButton($btnSubmit, $('.submission_control'));
|
146
|
+
|
147
|
+
mumuki.submission._selectSolutionProcessor(submitButton, $submissionsResults);
|
148
|
+
|
149
|
+
submitButton.start(() => {
|
150
|
+
mumuki.submission.processSolution(mumuki.editors.getSubmission());
|
94
151
|
});
|
95
152
|
|
96
|
-
|
97
|
-
}
|
153
|
+
submitButton.checkAttemptsLeft();
|
154
|
+
});
|
98
155
|
|
99
|
-
function animateTimeoutError(submitButton) {
|
100
|
-
let scene = new muvment.Scene($('.submission-result-error-animation'));
|
101
|
-
scene.addState(mumuki.errorState('timeout_1').onStart(submitButton.setOriginalContent.bind(submitButton)).onEndSwitch(scene, 'timeout_2'))
|
102
|
-
.addState(mumuki.errorState('timeout_2').onEndSwitch(scene, 'timeout_3'))
|
103
|
-
.addState(mumuki.errorState('timeout_3').onStart(submitButton.enable.bind(submitButton)))
|
104
|
-
.play();
|
105
|
-
}
|
106
156
|
|
107
|
-
|
108
|
-
|
109
|
-
|
157
|
+
/**
|
158
|
+
* This module contains methods for submitting solution in at high level, dealing with network communication,
|
159
|
+
* and layout-sensitive UI updates. It is intended to be both used internally by standard editors and by runners
|
160
|
+
* custom editors.
|
161
|
+
*
|
162
|
+
* Runners can choose to bypass this module under kids layouts, and handling all that low-level details. In order
|
163
|
+
* to do that {@code .mu-kids-submit-button} selector must be overiden. Customizing submission in classic layout
|
164
|
+
* or in a layout-agnostic way can be accomplish by overriding {@code .mu-submit-button}.
|
165
|
+
*
|
166
|
+
* @see mumuki.kids.showResult
|
167
|
+
* @see mumuki.bridge.Laboratory.runTests
|
168
|
+
*
|
169
|
+
* @module mumuki.submission
|
170
|
+
*/
|
171
|
+
return {
|
172
|
+
processSolution,
|
173
|
+
_registerSolutionProcessor,
|
174
|
+
_selectSolutionProcessor,
|
175
|
+
|
176
|
+
animateTimeoutError,
|
177
|
+
SubmitButton,
|
110
178
|
};
|
111
|
-
|
112
|
-
})(mumuki);
|
179
|
+
})();
|
@@ -0,0 +1,93 @@
|
|
1
|
+
mumuki.SubmissionsStore = (() => {
|
2
|
+
const SubmissionsStore = new class {
|
3
|
+
/**
|
4
|
+
* Returns the submission's result status for the last submission to
|
5
|
+
* the given exercise, or pending, if not present
|
6
|
+
*
|
7
|
+
* @param {number} exerciseId
|
8
|
+
* @returns {SubmissionStatus}
|
9
|
+
*/
|
10
|
+
getLastSubmissionStatus(exerciseId) {
|
11
|
+
const submission = this.getLastSubmissionAndResult(exerciseId);
|
12
|
+
return submission ? submission.result.status : 'pending';
|
13
|
+
}
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Returns the submission and result for the last submission to
|
17
|
+
* the given exercise
|
18
|
+
*
|
19
|
+
* @param {number} exerciseId
|
20
|
+
* @returns {SubmissionAndResult}
|
21
|
+
*/
|
22
|
+
getLastSubmissionAndResult(exerciseId) {
|
23
|
+
const submissionAndResult = window.localStorage.getItem(this._keyFor(exerciseId));
|
24
|
+
if (!submissionAndResult) return null;
|
25
|
+
return JSON.parse(submissionAndResult);
|
26
|
+
}
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Saves the result for the given exercise
|
30
|
+
*
|
31
|
+
* @param {number} exerciseId
|
32
|
+
* @param {SubmissionAndResult} submissionAndResult
|
33
|
+
*/
|
34
|
+
setSubmissionResultFor(exerciseId, submissionAndResult) {
|
35
|
+
window.localStorage.setItem(this._keyFor(exerciseId), this._asString(submissionAndResult));
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Retrieves the last cached, non-aborted result for the given submission of the given exercise
|
40
|
+
*
|
41
|
+
* @param {number} exerciseId
|
42
|
+
* @param {Submission} submission
|
43
|
+
* @returns {SubmissionResult} the cached result for this submission
|
44
|
+
*/
|
45
|
+
getSubmissionResultFor(exerciseId, submission) {
|
46
|
+
const last = this.getLastSubmissionAndResult(exerciseId);
|
47
|
+
if (!last
|
48
|
+
|| last.result.status === 'aborted'
|
49
|
+
|| !this.submissionSolutionEquals(last.submission, submission)) {
|
50
|
+
return null;
|
51
|
+
}
|
52
|
+
return last.result;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Extract the submission's solution content
|
57
|
+
*
|
58
|
+
* @param {Submission} submission
|
59
|
+
* @returns {string}
|
60
|
+
*/
|
61
|
+
submissionSolutionContent(submission) {
|
62
|
+
if (submission.solution) {
|
63
|
+
return submission.solution.content;
|
64
|
+
} else {
|
65
|
+
return submission['solution[content]'];
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Compares two solutions to determine if they are equivalent
|
71
|
+
* from the point of view of the code evaluation
|
72
|
+
*
|
73
|
+
* @param {Submission} one
|
74
|
+
* @param {Submission} other
|
75
|
+
* @returns {boolean}
|
76
|
+
*/
|
77
|
+
submissionSolutionEquals(one, other) {
|
78
|
+
return this.submissionSolutionContent(one) === this.submissionSolutionContent(other);
|
79
|
+
}
|
80
|
+
|
81
|
+
// private API
|
82
|
+
|
83
|
+
_asString(object) {
|
84
|
+
return JSON.stringify(object);
|
85
|
+
}
|
86
|
+
|
87
|
+
_keyFor(exerciseId) {
|
88
|
+
return `/exercise/${exerciseId}/submission`;
|
89
|
+
}
|
90
|
+
};
|
91
|
+
|
92
|
+
return SubmissionsStore;
|
93
|
+
})();
|