diff --git a/Gemfile b/Gemfile index 42ca2e315..5ac7ef4aa 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,8 @@ ruby '~> 2.3' gem 'puma' -gem 'mumuki-domain', github: 'mumuki/mumuki-domain' +gem 'mumuki-domain', github: 'mumuki/mumuki-domain', branch: 'feature-cumparsita' +gem 'mulangjs', path: '../mulang/ghcjslib/gem' gem 'execjs' gem 'therubyracer', platforms: :ruby diff --git a/README.md b/README.md index ca9d4146e..c0dce6130 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,8 @@ which are granted to be safe and stable. * `setUpDeleteFiles` * `setUpDeleteFile` * `updateButtonsVisibility` +* `mumuki.registerLocalTestRunner` +* `mumuki.registerLocalExpectationsRunner` * `mumuki.version` ### Bridge Response Format @@ -204,9 +206,7 @@ which are granted to be safe and stable. { "status": "failed", "guide_finished_by_solution": false, - "class_for_progress_list_item":"progress-list-item text-center danger active", "html":"...", - "title_html":"...", "button_html":"...", "expectations_html":"...", "remaining_attempts_html":null, diff --git a/app/assets/javascripts/mumuki_laboratory/application.js b/app/assets/javascripts/mumuki_laboratory/application.js index 98007c637..3f825b32b 100644 --- a/app/assets/javascripts/mumuki_laboratory/application.js +++ b/app/assets/javascripts/mumuki_laboratory/application.js @@ -14,24 +14,15 @@ //= require moment/es.js //= require moment/pt.js //= require webcomponents-lite +//= require mulang //= require rails-ujs -//= require turbolinks //= require mumuki-styles -//= require nprogress -//= require nprogress-turbolinks -//= require nprogress-ajax //= require jquery-console //= require codemirror.min //= require codemirror-simple-mode.js //= require codemirror-autorefresh //= require codemirror-modes -//= require analytics -//= require hotjar //= require muvment //= require_tree ./application - -NProgress.configure({ - showSpinner: false -}); diff --git a/app/assets/javascripts/mumuki_laboratory/application/bridge.js b/app/assets/javascripts/mumuki_laboratory/application/bridge.js index 87fb7bdd5..a7e93ae20 100644 --- a/app/assets/javascripts/mumuki_laboratory/application/bridge.js +++ b/app/assets/javascripts/mumuki_laboratory/application/bridge.js @@ -1,68 +1,69 @@ -var mumuki = mumuki || {}; - -(function (mumuki) { - var lastSubmission = {}; - - function Laboratory(exerciseId){ - this.exerciseId = exerciseId; - } - - function asString(json){ - return JSON.stringify(json); - } - - function sameAsLastSolution(newSolution){ - return asString(lastSubmission.content) === asString(newSolution); - } - - function lastSubmissionFinishedSuccessfully(){ - return lastSubmission.result && lastSubmission.result.status !== 'aborted'; - } - - function sendNewSolution(solution){ - var token = new mumuki.CsrfToken(); - var request = token.newRequest({ - type: 'POST', - url: window.location.origin + window.location.pathname + '/solutions' + window.location.search, - data: solution - }); - - return $.ajax(request).done(function (result) { - lastSubmission = { content: solution, result: result }; - }); - } - - mumuki.load(function () { - lastSubmission = {}; - }); - - Laboratory.prototype = { - +(() => { + class Laboratory { // ========== // Public API // ========== // Runs tests for the current exercise using the given submission // content. - runTests: function(content) { - return this._submitSolution({ solution: content }); - }, + runTests(content) { + return this.runCurrentExerciseSolution({ solution: content }); + } + + // Runs the current exercise solution, trying to get the response from cache first + runCurrentExerciseSolution(solution) { + const cachedSolution = mumuki.SubmissionsStore.getCachedResultFor(mumuki.currentExerciseId, solution); + if(cachedSolution) { + return $.Deferred().resolve(cachedSolution); + } + return this._runNewSolution(mumuki.currentExerciseId, solution); + } + + // Actually sends the solution to server + submitCurrentExerciseSolution(_exerciseId, solution) { // TODO use exerciseId instead of window.location + const token = new mumuki.CsrfToken(); + const request = token.newRequest({ + type: 'POST', + url: window.location.origin + window.location.pathname + '/solutions' + window.location.search, + data: solution + }); + return $.ajax(request); + } // =========== // Private API // =========== - _submitSolution: function (solution) { - if(lastSubmissionFinishedSuccessfully() && sameAsLastSolution(solution)){ - return $.Deferred().resolve(lastSubmission.result); - } else { - return sendNewSolution(solution); + _runNewSolution(exerciseId, solution){ + const responsePromise = mumuki.Connection.runNewSolution(exerciseId, solution, this); + return responsePromise.then((result) => { + this._preRenderResult(exerciseId, result); + const lastSubmission = { content: solution, result: result }; + mumuki.SubmissionsStore.setLastSubmission(exerciseId, lastSubmission); + return result; + }); + } + + // pre-renders some html parts of submission that are normally + // generated by server, but will be empty in offline mode + _preRenderResult(exerciseId, result) { + // TODO defer rendering calculation. + // Caching them may introduce bugs with the next-button + try { + const status = result.status; + const exercise = mumuki.ExercisesStore.get(exerciseId); + + result.button_html = result.button_html || mumuki.renderButtonHtml(status); + result.html = result.html || mumuki.renderCorollaryHtml(status, exercise); + } catch (e) { + console.log(`[Mumuki::Laboratory::Bridge] pre-rendering failed ${e}`); + throw e; } - }, - }; + } + + } mumuki.bridge = { Laboratory: Laboratory }; - -}(mumuki)); +})(); diff --git a/app/assets/javascripts/mumuki_laboratory/application/codemirror.js b/app/assets/javascripts/mumuki_laboratory/application/codemirror.js index 5429369c0..78e89614a 100644 --- a/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +++ b/app/assets/javascripts/mumuki_laboratory/application/codemirror.js @@ -60,10 +60,17 @@ var mumuki = mumuki || {}; }); } + function setContent(content) { + mumuki.page.editors.each(function (_, editor) { + editor.getDoc().setValue(content); + }); + } + mumuki.editor = mumuki.editor || {}; mumuki.editor.toggleFullscreen = toggleFullscreen; mumuki.editor.indentWithSpaces = indentWithSpaces; mumuki.editor.syncContent = syncContent; + mumuki.editor.setContent = setContent; mumuki.page = mumuki.page || {}; mumuki.page.dynamicEditors = []; diff --git a/app/assets/javascripts/mumuki_laboratory/application/confirmation.js b/app/assets/javascripts/mumuki_laboratory/application/confirmation.js index 42eec4525..c6dc31571 100644 --- a/app/assets/javascripts/mumuki_laboratory/application/confirmation.js +++ b/app/assets/javascripts/mumuki_laboratory/application/confirmation.js @@ -7,7 +7,7 @@ mumuki.load(function () { url: $(this).data('confirmation-url'), xhrFields: {withCredentials: true}, success: function(data){ - mumuki.updateProgressBarAndShowModal(data); + mumuki.updateCurrentExerciseProgressBarAndShowModal(data); } })); diff --git a/app/assets/javascripts/mumuki_laboratory/application/connection.js b/app/assets/javascripts/mumuki_laboratory/application/connection.js new file mode 100644 index 000000000..b67dc123b --- /dev/null +++ b/app/assets/javascripts/mumuki_laboratory/application/connection.js @@ -0,0 +1,58 @@ +(() => { + + const OfflineMode = new class { + // Runs solution by evaluating it locally + runNewSolution(exerciseId, solution, _bridge) { + return mumuki.runSolutionLocally(exerciseId, solution); + } + + // Renders progress from SubmissionsStore + renderExercisesProgressBar() { + $('.progress-list-item').each((_, it) => this._updateProgressListItemClass($(it))); + } + + configureExerciseEditorValue() { + const lastSubmission = mumuki.SubmissionsStore.getLastSubmission(mumuki.currentExerciseId); + if (lastSubmission) { + // TODO extract + if (lastSubmission.content.solution) { + $('#mu-custom-editor-value').val(lastSubmission.content.solution.content); + } else { + mumuki.editor.setContent(lastSubmission.content['solution[content]']); + } + } + } + + _updateProgressListItemClass(a) { + const exerciseId = a.data('mu-exercise-id'); + const status = mumuki.SubmissionsStore.getLastSubmissionStatus(exerciseId); + a.attr('class', mumuki.progressListItemClassForStatus(status, exerciseId == mumuki.currentExerciseId)); + } + } + + const OnlineMode = new class { + // Runs solution by sending it to server + runNewSolution(exerciseId, solution, bridge) { + return bridge.submitCurrentExerciseSolution(exerciseId, solution); + } + + // Does nothing. Progress is rendered by server + renderExercisesProgressBar() { + } + + // Does nothing. Editor value is configured by server + configureExerciseEditorValue() { + } + + } + + mumuki.goOnline = function () { + mumuki.Connection = OnlineMode; + }; + + mumuki.goOffline = function () { + mumuki.Connection = OfflineMode; + }; + + mumuki.goOnline(); +})(); diff --git a/app/assets/javascripts/mumuki_laboratory/application/console.js b/app/assets/javascripts/mumuki_laboratory/application/console.js index c8fa97758..31b8d9b2a 100644 --- a/app/assets/javascripts/mumuki_laboratory/application/console.js +++ b/app/assets/javascripts/mumuki_laboratory/application/console.js @@ -116,7 +116,7 @@ var mumuki = mumuki || {}; $('.submission-results').show(); $('.submission-results').html(response.html); mumuki.pin.scroll(); - mumuki.updateProgressBarAndShowModal(response); + mumuki.updateCurrentExerciseProgressBarAndShowModal(response); } else { $('.submission-results').hide(); $('.progress-list-item.active').attr('class', "progress-list-item text-center danger active"); diff --git a/app/assets/javascripts/mumuki_laboratory/application/current-exercise.js b/app/assets/javascripts/mumuki_laboratory/application/current-exercise.js new file mode 100644 index 000000000..f33686f6f --- /dev/null +++ b/app/assets/javascripts/mumuki_laboratory/application/current-exercise.js @@ -0,0 +1,13 @@ +(() => { + mumuki.load(() => { + // Set global currentExerciseId + const $muExerciseId = $('#mu-exercise-id')[0]; + const $muExerciseResource = $('#mu-exercise-resource')[0]; + if ($muExerciseId) { + mumuki.currentExerciseId = Number($muExerciseId.value); + mumuki.ExercisesStore.saveJson(mumuki.currentExerciseId, $muExerciseResource.value); + } else { + mumuki.currentExerciseId = null; + } + }) +})(); diff --git a/app/assets/javascripts/mumuki_laboratory/application/exercisess-store.js b/app/assets/javascripts/mumuki_laboratory/application/exercisess-store.js new file mode 100644 index 000000000..b2f0334a9 --- /dev/null +++ b/app/assets/javascripts/mumuki_laboratory/application/exercisess-store.js @@ -0,0 +1,32 @@ +(() => { + const ExercisesStore = new class { + find(exerciseId) { + const exercise = window.localStorage.getItem(this._keyFor(exerciseId)); + if (!exercise) return null; + return JSON.parse(exercise); + } + + // Saves an exercise object + save(exerciseId, exercise) { + this.saveJson(exerciseId, JSON.stringify(exercise)); + } + + // Saves an exercise json string + saveJson(exerciseId, exerciseJson) { + window.localStorage.setItem(this._keyFor(exerciseId), exerciseJson); + } + + // Finds and existent exercise + // Fails if exercise is missing + get(exerciseId) { + const exercise = this.find(exerciseId); + if (!exercise) throw new Error(`Missing exercise ${exerciseId}`); + return exercise; + } + + _keyFor(exerciseId) { + return `/exercise/${exerciseId}`; + } + }; + mumuki.ExercisesStore = ExercisesStore; +})(); diff --git a/app/assets/javascripts/mumuki_laboratory/application/kids.js b/app/assets/javascripts/mumuki_laboratory/application/kids.js index 9c712bbd6..16bdbe532 100644 --- a/app/assets/javascripts/mumuki_laboratory/application/kids.js +++ b/app/assets/javascripts/mumuki_laboratory/application/kids.js @@ -110,7 +110,7 @@ mumuki.load(function () { // This method needs to be called by the runner's editor.html extension // in order to finish an exercise showResult: function (data) { - mumuki.updateProgressBarAndShowModal(data); + mumuki.updateCurrentExerciseProgressBarAndShowModal(data); if (data.guide_finished_by_solution) return; mumuki.kids.resultAction[data.status](data); }, @@ -172,11 +172,15 @@ mumuki.load(function () { mumuki.kids._getOverlay().hide(); }, + _titleHtml: function (data) { + return mumuki.renderTitleHtml(data.status); + }, + _showMessageOnCharacterBubble: function (data) { var $bubble = mumuki.kids._getCharacterBubble(); $bubble.find('.mu-kids-character-speech-bubble-tabs').hide(); $bubble.find('.mu-kids-character-speech-bubble-normal').hide(); - $bubble.find('.mu-kids-character-speech-bubble-failed').show().html(data.title_html); + $bubble.find('.mu-kids-character-speech-bubble-failed').show().html(mumuki.kids._titleHtml(data)); $bubble.addClass(data.status); if (data.status === 'passed_with_warnings') { $bubble.find('.mu-kids-character-speech-bubble-failed').append(data.expectations_html); @@ -203,7 +207,7 @@ mumuki.load(function () { backdrop: 'static', keyboard: false }); - $resultsKidsModal.find('.modal-header').first().html(data.title_html); + $resultsKidsModal.find('.modal-header').first().html(mumuki.kids._titleHtml(data)); $resultsKidsModal.find('.modal-footer').first().html(data.button_html); mumuki.kids._showCorollaryCharacter(); $('.mu-close-modal').click(() => mumuki.kids._getResultsModal().modal('hide')); @@ -228,7 +232,7 @@ mumuki.load(function () { _stateScaler: function ($state, fullMargin, preferredWidth, preferredHeight) { var $table = $state.find('gs-board > table'); - if (!$table.length) return setTimeout(() => this.scaleState($state, fullMargin)); + if (!$table.length) return setTimeout(() => mumuki.kids.scaleState($state, fullMargin)); console.warn("You are using the default states scaler, which is gobstones-specific. Please register your own scaler in the future"); diff --git a/app/assets/javascripts/mumuki_laboratory/application/load-analytics.js b/app/assets/javascripts/mumuki_laboratory/application/load-analytics.js deleted file mode 100644 index 51a9af449..000000000 --- a/app/assets/javascripts/mumuki_laboratory/application/load-analytics.js +++ /dev/null @@ -1,4 +0,0 @@ -mumuki.load(function(){ - ga('create', 'UA-58353823-1', 'auto'); - ga('send', 'pageview'); -}); diff --git a/app/assets/javascripts/mumuki_laboratory/application/offline-runner.js b/app/assets/javascripts/mumuki_laboratory/application/offline-runner.js new file mode 100644 index 000000000..047dc8e7f --- /dev/null +++ b/app/assets/javascripts/mumuki_laboratory/application/offline-runner.js @@ -0,0 +1,74 @@ +(() => { + const BasicLocalTestRunner = new class { + // This basic runner does not perform any test evaluation, but sets a default + // passed state, with no test results. This could be improved in the future. + runTests(solution, exercise, result) { + result.status = "passed"; + result.test_results = [] + } + } + + const MulangLocalExpectationsRunner = new class { + _getMulangCode(solution, exercise, result) { + return result.mulangAst ? mulang.astCode(result.mulangAst) : mulang.nativeCode(exercise.language, solution); + } + + _expectationsFailed(analysisResult) { + return analysisResult.expectationResults.some((it) => !it.result); + } + + runExpectations(solution, exercise, result) { + try { + const analysisResult = this + ._getMulangCode(solution, exercise, result) + .analyse({ expectations: exercise.expectations }); + if (this._expectationsFailed(analysisResult) && result.status == 'passed') { + result.status = 'passed_with_warnings'; + } + result.expectations_html = ''; + } catch (e) { + console.warn(`[Mumuki::Laboratory::OfflineRunner] Mulang crashed with ${JSON.stringify(e)}`); + } + } + } + + function initialLocalResult() { + return { + // FIXME use a roadmap + guide_finished_by_solution: false, + // Attemps will be not considered when offline + remaining_attempts_html: null + }; + } + + mumuki.runSolutionLocally = function (exerciseId, solution) { + console.log('[Mumuki::Laboratory::OfflineRunner] Running solution...'); + + const exercise = mumuki.ExercisesStore.find(exerciseId); + + let result = initialLocalResult(); + + mumuki.localTestRunner.runTests(solution, exercise, result); + mumuki.localExpectationsRunner.runExpectations(solution, exercise, result); + + console.log(`[Mumuki::Laboratory::OfflineRunner] Done. Status is ${result.status}...`) + return Promise.resolve(result); + }; + + // Runners may call this function to set local test runners + mumuki.registerLocalTestRunner = function (runner) { + mumuki.localTestRunner = runner; + }; + + // Runners may call this function to set local expectation runners + mumuki.registerLocalExpectationsRunner = function (runner) { + mumuki.localExpectationsRunner = runner; + }; + + mumuki.load(() => { + mumuki.registerLocalTestRunner(BasicLocalTestRunner) + mumuki.registerLocalExpectationsRunner(MulangLocalExpectationsRunner) + }); +})(); + + diff --git a/app/assets/javascripts/mumuki_laboratory/application/polyfill.js b/app/assets/javascripts/mumuki_laboratory/application/polyfill.js new file mode 100644 index 000000000..950b0e779 --- /dev/null +++ b/app/assets/javascripts/mumuki_laboratory/application/polyfill.js @@ -0,0 +1,5 @@ +(() => { + // Allow polimorphism between standard promise and jquery promise + Promise.prototype.always = Promise.prototype.finally; + Promise.prototype.fail = Promise.prototype.catch; +})(); diff --git a/app/assets/javascripts/mumuki_laboratory/application/progress.js b/app/assets/javascripts/mumuki_laboratory/application/progress.js index 0a0dce364..bcdc3ff26 100644 --- a/app/assets/javascripts/mumuki_laboratory/application/progress.js +++ b/app/assets/javascripts/mumuki_laboratory/application/progress.js @@ -1,10 +1,19 @@ -var mumuki = mumuki || {}; - -(function (mumuki) { +(() => { + // Updates the current exercise progress indicator + mumuki.updateCurrentExerciseProgressBarAndShowModal = function (submission) { + $('.progress-list-item.active').attr('class', mumuki.progressListItemClassForStatus(submission.status, true)); + if(submission.guide_finished_by_solution) $('#guide-done').modal(); + }; - mumuki.updateProgressBarAndShowModal = function (data) { - $('.progress-list-item.active').attr('class', data.class_for_progress_list_item); - if(data.guide_finished_by_solution) $('#guide-done').modal(); + mumuki.progressListItemClassForStatus = function (status, active = false) { + return `progress-list-item text-center ${mumuki.classForStatus(status)} ${active ? 'active' : ''}`; }; -})(mumuki); + mumuki.load(() => { + // Update all exercises progress indicators + mumuki.Connection.renderExercisesProgressBar(); + + // Set the editor's current value + mumuki.Connection.configureExerciseEditorValue(); + }) +})(); diff --git a/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js b/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js new file mode 100644 index 000000000..09c8cb9ee --- /dev/null +++ b/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js @@ -0,0 +1,88 @@ +(() => { + + // ========================== + // View function for building + // the results UI + // ========================== + + function _messageForStatus(status) { + switch (status) { // FIXME i18n + case "errored": return "¡Ups! Tu solución no se puede ejecutar"; + case "failed": return "Tu solución no pasó las pruebas"; + case "passed_with_warnings": return "Tu solución funcionó, pero hay cosas que mejorar"; + case "passed": return "¡Muy bien! Tu solución pasó todas las pruebas"; + } + } + + function _iconForStatus(status) { + switch (status) { + case "errored": return "fa-minus-circle"; + case "failed": return "fa-times-circle"; + case "passed_with_warnings": return "fa-exclamation-circle"; + case "passed": return "fa-check-circle"; + } + } + + function _closeModalButtonHtml() { + const keepLearning = "¡Seguí aprendiendo!"; // FIXME i18n + return ``; + } + + function _retryButtonHtml() { + const retryMessage = "Reintentar"; // FIXME i18n + return ``; + } + + function _nextExerciseButton() { + return `Siguiente Ejercicio `; // TODO missing exercise title + } + + mumuki.classForStatus = function (status) { + switch (status) { + case "passed": return "success"; + case "failed": return "danger"; + case "passed_with_warnings": return "warning"; + case "errored": return "broken"; + case "pending": return "muted"; + } + }; + + mumuki.renderTitleHtml = function (status) { + return `

${_messageForStatus(status)}

`; + }; + + mumuki.renderButtonHtml = function (status) { + return ` +
+
+
+ ${status === 'passed' ? _nextExerciseButton() : _retryButtonHtml()} +
+
+
`; + }; + + mumuki.renderCorollaryHtml = function(status, exercise) { + const statusClass = mumuki.classForStatus(status); + if (exercise.layout == 'input_kids') { + return ` +
+
+ +
+ ${exercise.corollary} +
`; + } else { + return ` +
+

+ ${_messageForStatus(status)} +

+
+
+ ${status === 'passed' ? exercise.corollary : ''} +
+ ${mumuki.renderButtonHtml(status)}`; + } + } +})(); diff --git a/app/assets/javascripts/mumuki_laboratory/application/submission.js b/app/assets/javascripts/mumuki_laboratory/application/submission.js index b639c2700..2103bce53 100644 --- a/app/assets/javascripts/mumuki_laboratory/application/submission.js +++ b/app/assets/javascripts/mumuki_laboratory/application/submission.js @@ -15,15 +15,15 @@ var mumuki = mumuki || {}; success: function (data, submitButton) { this.submissionsResultsArea.html(data.html); data.status === 'aborted' ? this.error(submitButton) : submitButton.enable(); - mumuki.updateProgressBarAndShowModal(data); + submitButton.updateAttemptsLeft(data); + mumuki.updateCurrentExerciseProgressBarAndShowModal(data); }, error: function (submitButton) { this.submissionsResultsArea.html(''); this.submissionsErrorTemplate.show(); animateTimeoutError(submitButton); }, - done: function (data, submitButton) { - submitButton.updateAttemptsLeft(data); + done: function (submitButton) { mumuki.pin.scroll(); } }; @@ -69,13 +69,13 @@ var mumuki = mumuki || {}; mumuki.editor.syncContent(); var solution = getContent(); - bridge._submitSolution(solution).done(function (data) { + bridge.runCurrentExerciseSolution(solution).then(function (data) { resultsBox.success(data, submitButton); }).fail(function () { resultsBox.error(submitButton); - }).always(function (data) { + }).always(function () { $(document).renderMuComponents(); - resultsBox.done(data, submitButton); + resultsBox.done(submitButton); }); }); diff --git a/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js b/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js new file mode 100644 index 000000000..e6d22f8fd --- /dev/null +++ b/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js @@ -0,0 +1,43 @@ +(() => { + const SubmissionsStore = new class { + getLastSubmissionStatus(exerciseId) { + const submission = this.getLastSubmission(exerciseId); + return submission ? submission.result.status : 'pending'; + } + + getLastSubmission(exerciseId) { + const submission = window.localStorage.getItem(this._keyFor(exerciseId)); + if (!submission) return null; + return JSON.parse(submission); + } + + setLastSubmission(exerciseId, submission) { + window.localStorage.setItem(this._keyFor(exerciseId), this._asString(submission)); + } + + getCachedResultFor(exerciseId, newSolution) { + const lastSubmission = this.getLastSubmission(exerciseId); + if (!lastSubmission + || lastSubmission.result.status === 'aborted' + || !this._solutionEquals(lastSubmission, newSolution)) { + return null; + } + return lastSubmission.result; + } + + // private API + + _asString(object) { + return JSON.stringify(object); + } + + _keyFor(exerciseId) { + return `/exercise/${exerciseId}/submission`; + } + + _solutionEquals(submission, solution) { + return this._asString(submission.content) === this._asString(solution); + } + }; + mumuki.SubmissionsStore = SubmissionsStore; +})(); diff --git a/app/views/exercises/show.html.erb b/app/views/exercises/show.html.erb index 12791b36f..9e4708203 100644 --- a/app/views/exercises/show.html.erb +++ b/app/views/exercises/show.html.erb @@ -45,6 +45,8 @@ <%= render_exercise_input_layout(@exercise) %> <%= hidden_field_tag default_content_tag_id(@exercise), @default_content %> +<%= hidden_field_tag "mu-exercise-id", @exercise.id %> +<%= hidden_field_tag "mu-exercise-resource", @exercise.to_resource_h(markdownified: true).to_json %> <% end %> diff --git a/app/views/layouts/exercise_inputs/editors/_custom.html.erb b/app/views/layouts/exercise_inputs/editors/_custom.html.erb index dcb5104cf..602210d42 100644 --- a/app/views/layouts/exercise_inputs/editors/_custom.html.erb +++ b/app/views/layouts/exercise_inputs/editors/_custom.html.erb @@ -1,7 +1,6 @@ <%= render_runner_assets @exercise.language, :editor %> <%= render_custom_editor(@exercise) %> - <%= form.hidden_field :content, id: "mu-custom-editor-value", value: @current_content %> <%= form.hidden_field :content_extra, id: "mu-custom-editor-extra", value: @exercise.extra %> <%= form.hidden_field :content, id: "mu-custom-editor-test", value: @exercise.test %> diff --git a/lib/mumuki/laboratory/controllers/results_rendering.rb b/lib/mumuki/laboratory/controllers/results_rendering.rb index 1dfabf1be..0afb72b5b 100644 --- a/lib/mumuki/laboratory/controllers/results_rendering.rb +++ b/lib/mumuki/laboratory/controllers/results_rendering.rb @@ -10,7 +10,6 @@ module Mumuki::Laboratory::Controllers::ResultsRendering def render_results_json(assignment, results = {}) render json: results.merge(progress_json).merge( html: render_results_html(assignment), - title_html: render_results_title_html(assignment), button_html: render_results_button_html(assignment), expectations_html: render_results_expectations_html(assignment), remaining_attempts_html: remaining_attempts_text(assignment), @@ -20,7 +19,6 @@ def render_results_json(assignment, results = {}) def progress_json { guide_finished_by_solution: guide_finished_by_solution?, - class_for_progress_list_item: class_for_progress_list_item(@exercise, true) } end @@ -34,11 +32,6 @@ def results_partial(assignment) assignment.input_kids? ? 'exercise_solutions/kids_results' : 'exercise_solutions/results' end - def render_results_title_html(assignment) - render_to_string partial: 'exercise_solutions/results_title', - locals: {contextualization: assignment} - end - def render_results_button_html(assignment) render_to_string partial: 'exercise_solutions/kids_results_button', locals: {assignment: assignment} diff --git a/mumuki-laboratory.gemspec b/mumuki-laboratory.gemspec index 0f777d14f..0d6b7b147 100644 --- a/mumuki-laboratory.gemspec +++ b/mumuki-laboratory.gemspec @@ -40,6 +40,8 @@ Gem::Specification.new do |s| s.add_dependency 'turbolinks', '~> 5.0' s.add_dependency 'sprockets', '~> 3.7' + s.add_dependency 'mulangjs', '~> 5.0' + s.add_development_dependency 'pg', '~> 0.18.0' s.add_development_dependency 'bundler', '~> 2.0' end diff --git a/spec/controllers/exercise_solutions_controller_spec.rb b/spec/controllers/exercise_solutions_controller_spec.rb index c35e56fe8..0a1fb128b 100644 --- a/spec/controllers/exercise_solutions_controller_spec.rb +++ b/spec/controllers/exercise_solutions_controller_spec.rb @@ -18,8 +18,7 @@ it { expect(response.status).to eq 200 } it { expect(response.body).to json_eq( {status: :failed, guide_finished_by_solution: false}, - except: [:class_for_progress_list_item, - :html, :title_html, :button_html, + except: [:html, :button_html, :expectations_html, :remaining_attempts_html, :test_results]) } it { expect(Assignment.last.solution).to eq('asd')} diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb index 1c9aaa5be..19feb89b1 100644 --- a/spec/dummy/config/environments/development.rb +++ b/spec/dummy/config/environments/development.rb @@ -40,7 +40,7 @@ # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. - config.assets.debug = true + config.assets.debug = false # Suppress logger output for asset requests. config.assets.quiet = true diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index d508fff5f..9c1b8afa2 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190929180601) do +ActiveRecord::Schema.define(version: 20191018054039) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -173,6 +173,7 @@ t.text "choices" t.text "settings" t.text "custom_expectations" + t.text "offline_test" t.index ["guide_id"], name: "index_exercises_on_guide_id" t.index ["language_id"], name: "index_exercises_on_language_id" end diff --git a/vendor/assets/javascripts/hotjar.js b/vendor/assets/javascripts/hotjar.js deleted file mode 100644 index dc0ad2f80..000000000 --- a/vendor/assets/javascripts/hotjar.js +++ /dev/null @@ -1,8 +0,0 @@ -(function(h,o,t,j,a,r){ - h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)}; - h._hjSettings={hjid:646165,hjsv:6}; - a=o.getElementsByTagName('head')[0]; - r=o.createElement('script');r.async=1; - r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv; - a.appendChild(r); -})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');