diff --git a/runestone/__main__.py b/runestone/__main__.py index b2c96c15f..52fe0baf0 100644 --- a/runestone/__main__.py +++ b/runestone/__main__.py @@ -30,6 +30,7 @@ def init(): conf_dict['author'] = click.prompt("Your Name ", default=getpass.getuser()) conf_dict['project_title'] = click.prompt("Title for this project ", default="Runestone Default") conf_dict['python3'] = click.prompt("Use Simple Python3 Semantics ", default="false") + conf_dict['basecourse'] = conf_dict['project_name'] if conf_dict['use_services'] == "true": conf_dict['login_req'] = click.prompt("Require login ", default="false") conf_dict['master_url'] = click.prompt("URL for ajax server ", default="http://127.0.0.1:8000") diff --git a/runestone/activecode/css/activecode.css b/runestone/activecode/css/activecode.css index 02565ad8a..7fbb6a54e 100644 --- a/runestone/activecode/css/activecode.css +++ b/runestone/activecode/css/activecode.css @@ -92,6 +92,6 @@ border: 2px solid black; } -ol.arabic { +.full_width ol { max-width: 100% !important; } \ No newline at end of file diff --git a/runestone/activecode/js/activecode.js b/runestone/activecode/js/activecode.js index 2bfc5809d..6a3ddf44c 100755 --- a/runestone/activecode/js/activecode.js +++ b/runestone/activecode/js/activecode.js @@ -551,7 +551,7 @@ errorText.NotImplementedErrorFix = "For now the only way to fix this is to not u ActiveCode.prototype.setTimeLimit = function (timer) { - var timelimit = this.timeLimit; + var timelimit = this.timelimit; if (timer !== undefined ) { timelimit = timer } @@ -986,11 +986,14 @@ AudioTour.prototype.tour = function (divid, audio_type, bcount) { // str+=""; + + var dir = "http://media.interactivepython.org/" + eBookConfig.basecourse + "/audio/" + //var dir = "../_static/audio/" str += ""; this.ahash[akey] = lnums; this.aname.push(akey); diff --git a/runestone/assess/js/fitb.js b/runestone/assess/js/fitb.js index 192f81f03..41640494a 100644 --- a/runestone/assess/js/fitb.js +++ b/runestone/assess/js/fitb.js @@ -250,7 +250,7 @@ FITB.prototype.checkFITBStorage = function () { // Starts chain of functions which ends with feedBack() displaying feedback to user this.evaluateAnswers(); this.renderFITBFeedback(); - var answerInfo = "answer:" + this.given_arr + ":" + (this.isCorrect ? "correct" : "no"); + var answerInfo = "answer:" + this.given_arr + ":" + (this.correct ? "correct" : "no"); this.logBookEvent({"event": "fillb", "act": answerInfo, "div_id": this.divid}); this.enableCompareButton.disabled = false; }; diff --git a/runestone/assess/js/mchoice.js b/runestone/assess/js/mchoice.js index d252aaf5f..b8e38ac68 100644 --- a/runestone/assess/js/mchoice.js +++ b/runestone/assess/js/mchoice.js @@ -232,12 +232,12 @@ MultipleChoice.prototype.renderMCFormButtons = function () { }); if (this.multipleanswers) { this.submitButton.addEventListener("click", function () { - this.processMCMASubmission(); + this.processMCMASubmission(true); }.bind(this), false); } else { this.submitButton.addEventListener("click", function (ev) { ev.preventDefault(); - this.processMCMFSubmission(); + this.processMCMFSubmission(true); }.bind(this), false); } // end else this.optsForm.appendChild(this.submitButton); @@ -313,7 +313,9 @@ MultipleChoice.prototype.restoreMultipleSelect = function () { $(this.optionArray[b].input).attr("checked", "true"); } } - this.enableMCComparison(); + if (this.useRunestoneServices) { + this.enableMCComparison(); + } } // end for } // end if } // end if len > 0 @@ -346,12 +348,14 @@ MultipleChoice.prototype.restoreRadio = function () { === Processing MC Submissions === ===============================*/ -MultipleChoice.prototype.processMCMASubmission = function () { +MultipleChoice.prototype.processMCMASubmission = function (logFlag) { // Called when the submit button is clicked this.getSubmittedOpts(); this.scoreMCMASubmission(); this.populateMCMALocalStorage(); - this.logMCMAsubmission(); + if (logFlag) { + this.logMCMAsubmission(); + } this.renderMCMAFeedBack(); if (this.useRunestoneServices) { this.enableMCComparison(); @@ -428,12 +432,14 @@ MultipleChoice.prototype.renderMCMAFeedBack = function () { } }; -MultipleChoice.prototype.processMCMFSubmission = function () { +MultipleChoice.prototype.processMCMFSubmission = function (logFlag) { // Called when the submit button is clicked this.getSubmittedOpts(); this.populateMCMFLocalStorage(); this.scoreMCMFSubmission(); - this.logMCMFsubmission(); + if (logFlag) { + this.logMCMFsubmission(); + } this.renderMCMFFeedback(this.givenArray[0] == this.correctIndexList[0], this.singlefeedback); if (this.useRunestoneServices) { this.enableMCComparison(); diff --git a/runestone/assess/js/timed.js b/runestone/assess/js/timed.js index c314916f3..12148f2f7 100644 --- a/runestone/assess/js/timed.js +++ b/runestone/assess/js/timed.js @@ -349,7 +349,7 @@ Timed.prototype.handlePrevAssessment = function () { this.running = 0; this.done = 1; $(this.timedDiv).show(); - this.submitTimedProblems(); + this.submitTimedProblems(false); // do not log these results } Timed.prototype.startAssessment = function () { @@ -508,7 +508,7 @@ Timed.prototype.finishAssessment = function () { this.running = 0; this.done = 1; this.taken = 1; - this.submitTimedProblems(); + this.submitTimedProblems(true); // log results this.checkScore(); this.displayScore(); this.storeScore(); @@ -518,9 +518,9 @@ Timed.prototype.finishAssessment = function () { } }; -Timed.prototype.submitTimedProblems = function () { +Timed.prototype.submitTimedProblems = function (logFlag) { for (var i = 0; i < this.renderedQuestionArray.length; i++) { - this.renderedQuestionArray[i].processTimedSubmission(); + this.renderedQuestionArray[i].processTimedSubmission(logFlag); } if (!this.showFeedback) { this.hideTimedFeedback(); diff --git a/runestone/assess/js/timedmc.js b/runestone/assess/js/timedmc.js index 86bbe94fb..8a1064c4b 100644 --- a/runestone/assess/js/timedmc.js +++ b/runestone/assess/js/timedmc.js @@ -133,13 +133,13 @@ TimedMC.prototype.hideFeedback = function () { } }; -TimedMC.prototype.processTimedSubmission = function () { +TimedMC.prototype.processTimedSubmission = function (logFlag) { for (var i = 0; i < this.optionArray.length; i++) { this.optionArray[i]["input"].disabled = true; } if (this.multipleanswers) { - this.processMCMASubmission(); + this.processMCMASubmission(logFlag); } else { - this.processMCMFSubmission(); + this.processMCMFSubmission(logFlag); } }; diff --git a/runestone/common/css/runestone-custom-sphinx-bootstrap.css b/runestone/common/css/runestone-custom-sphinx-bootstrap.css index e1f219565..5ab489d42 100644 --- a/runestone/common/css/runestone-custom-sphinx-bootstrap.css +++ b/runestone/common/css/runestone-custom-sphinx-bootstrap.css @@ -48,12 +48,22 @@ div.section { padding-left:0; padding-right:0; } - .container .section >*:not(.section) { +.container .section >*:not(.section) { max-width: 500pt; margin-left: auto; margin-right: auto; } +/* This rule is meant to override the behavior of the + previous rule since it is not possible to exclude + more than one section in the not() part of the rule +*/ +.container .section div.full-width.container { + margin-left: auto; + margin-right: auto; + max-width: 90%; +} + .container .section>img { display: block; margin-left: auto; diff --git a/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html b/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html index e623f1f37..f56911322 100644 --- a/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html +++ b/runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html @@ -216,6 +216,7 @@ eBookConfig.isLoggedIn = false; eBookConfig.useRunestoneServices = {{ use_services }}; eBookConfig.python3 = {{ python3 }}; + eBookConfig.basecourse = '{{ basecourse }}';
diff --git a/runestone/common/project_template/pavement.tmpl b/runestone/common/project_template/pavement.tmpl index fbf2e8128..ef6366919 100644 --- a/runestone/common/project_template/pavement.tmpl +++ b/runestone/common/project_template/pavement.tmpl @@ -30,7 +30,8 @@ options( 'course_url':master_url, 'use_services': '%(use_services)s', 'python3': '%(python3)s', - 'dburl': '%(dburl)s' + 'dburl': '%(dburl)s', + 'basecourse': '%(basecourse)s' } ) ) diff --git a/runestone/parsons/css/parsons.css b/runestone/parsons/css/parsons.css index 8973d7e2d..7dcb5fef2 100644 --- a/runestone/parsons/css/parsons.css +++ b/runestone/parsons/css/parsons.css @@ -1,6 +1,7 @@ /** Stylesheet for the puzzles */ .sortable-code { + position: static; padding-left: 0px; margin-left: 2%; display:inline-block; @@ -19,6 +20,12 @@ margin-left: 0; border: 1px solid #efefff;; } +.sortable-code ul:empty { + padding-bottom: 30px; +} +.sortable-code li, .sortable-code li:before, .sortable-code li:after { + box-sizing: content-box; +} ul.output { background-color: #FFA; } @@ -72,13 +79,13 @@ li.correctPosition, .testcase.pass { .testcase .msg { font-weight: bold; } .testcase .error { color: red;} .testcase .feedback { font-weight: bolder;} -.testcase.fail .expected, .testcase.fail .actual { - color: red; +.testcase .fail .expected, .testcase .fail .actual { + color: red; font-weight: bolder; } -.testcase .output { - display: block; - white-space: pre; +.testcase .output { + display: block; + white-space: pre; background-color: #555555; color: white; font-size: 12px; @@ -104,6 +111,7 @@ li.correctPosition, .testcase.pass { display: block; color: red; } + .parsons{ /*background-color: #fbfcfd; border-color: #e6e6e6; diff --git a/runestone/parsons/js/lib/jquery.ui.touch-punch.min.js b/runestone/parsons/js/lib/jquery.ui.touch-punch.min.js new file mode 100644 index 000000000..31272ce6f --- /dev/null +++ b/runestone/parsons/js/lib/jquery.ui.touch-punch.min.js @@ -0,0 +1,11 @@ +/*! + * jQuery UI Touch Punch 0.2.3 + * + * Copyright 2011–2014, Dave Furfero + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Depends: + * jquery.ui.widget.js + * jquery.ui.mouse.js + */ +!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); \ No newline at end of file diff --git a/runestone/parsons/js/lib/lis.js b/runestone/parsons/js/lib/lis.js index 1ab4e3066..7abb21ae9 100644 --- a/runestone/parsons/js/lib/lis.js +++ b/runestone/parsons/js/lib/lis.js @@ -95,6 +95,31 @@ var LIS = {}; return _.difference(list, best); }; + // Returns an array of those indices of the input that + // are not included in the chosen longest increasing + // subsequence of the input values. + LIS.best_lise_inverse_indices = function(input) { + var decks = this.patience_sort(_.toArray(input)), + lises = this.find_lises(decks), + best = this.best_lise(lises), + inverse_indices = []; + var j = 0; + for (var i = 0; i < best.length; i++) { + for ( ; j < input.length; j++) { + if (input[j] === best[i]) { + j++; + break; + } else { + inverse_indices.push(j); + } + } + } + for ( ; j < input.length; j++) { + inverse_indices.push(j); + } + return inverse_indices; + }; + // Takes an iterable sequence and returns a list of the inverses of // all the longest increasing subsequences of this sequence /* diff --git a/runestone/parsons/js/parsons.js b/runestone/parsons/js/parsons.js index 60fe7f655..42a82764d 100644 --- a/runestone/parsons/js/parsons.js +++ b/runestone/parsons/js/parsons.js @@ -10,8 +10,18 @@ return "Ohjelma sisältää vääriä palasia tai palasten järjestys on väärä. Tämä on mahdollista korjata siirtämällä, poistamalla tai vaihtamalla korostettuja palasia.";}, lines_missing: function() { return "Ohjelmassasi on liian vähän palasia, jotta se toimisi oikein.";}, + lines_too_many: function() { + return "Ohjelmassasi on liian monta palasta, jotta se toimisi oikein.";}, no_matching: function(lineNro) { - return "Korostettu palanen (" + lineNro + ") on sisennetty Pythonin kieliopin vastaisesti."; }, + return "Korostettu palanen (" + lineNro + ") on sisennetty kieliopin vastaisesti."; }, + no_matching_open: function(lineNro, block) { + return "Rivillä " + lineNro + " päätettävää " + block + + " lohkoa ei ole aloitettu."; }, + no_matching_close: function(lineNro, block) { + return block + " lohkoa riviltä " + lineNro + " ei ole päätetty."; }, + block_close_mismatch: function(closeLine, closeBlock, openLine, inBlock) { + return "Ei voi päättää lohkoa " + closeBlock + " rivillä " + closeLine + + " oltaessa vielä lohkossa " + inBlock + " riviltä " + openLine; }, block_structure: function(lineNro) { return "Korostettu palanen (" + lineNro + ") on sisennetty väärään koodilohkoon."; }, unittest_error: function(errormsg) { @@ -37,8 +47,18 @@ return "Code fragments in your program are wrong, or in wrong order. This can be fixed by moving, removing, or replacing highlighted fragments.";}, lines_missing: function() { return "Your program has too few code fragments.";}, + lines_too_many: function() { + return "Your program has too many code fragments.";}, no_matching: function(lineNro) { - return "Based on python syntax, the highlighted fragment (" + lineNro + ") is not correctly indented."; }, + return "Based on language syntax, the highlighted fragment (" + lineNro + ") is not correctly indented."; }, + no_matching_open: function(lineNro, block) { + return "The " + block + " ended on line " + lineNro + " never started."; }, + no_matching_close: function(lineNro, block) { + return "Block " + block + " defined on line " + lineNro + " not ended properly"; + }, + block_close_mismatch: function(closeLine, closeBlock, openLine, inBlock) { + return "Cannot end block " + closeBlock + " on line " + closeLine + " when still inside block " + inBlock + " started on line " + openLine; + }, block_structure: function(lineNro) { return "The highlighted fragment " + lineNro + " belongs to a wrong block (i.e. indentation)."; }, unittest_error: function(errormsg) { return "Error in parsing/executing your program constructor
+function Parsons (opts) {
+ if (opts) {
+ this.init(opts);
+ }
+}
+Parsons.prototype = new RunestoneBase();
+
+/*=======================================
+== Initialize basic Parsons attributes ==
+=======================================*/
+Parsons.prototype.init = function (opts) {
+ RunestoneBase.apply(this, arguments);
+ var orig = opts.orig; // entire element that will be replaced by new HTML
+ this.origElem = orig;
+ this.divid = orig.id;
+ this.children = this.origElem.childNodes; // this contains all of the child elements of the entire tag...
+ this.contentArray = [];
+ this.question = null;
+ Parsons.counter++; // Unique identifier
+ this.counterId = Parsons.counter;
+
+ this.getQuestion();
+ this.formatCode();
+ this.createParsonsView();
+};
+
+Parsons.counter = 0; // Initialize counter
+
+/*========================
+== Update object values ==
+========================*/
+Parsons.prototype.getQuestion = function () { // Finds question text and stores it in this.question
+ for (var i = 0; i < this.children.length; i++) {
+ if ($(this.children[i]).is("[data-question]")) {
+ this.question = this.children[i];
+ break;
+ }
+ }
+};
+
+Parsons.prototype.formatCode = function () {
+ var fulltext = $(this.origElem).html();
+ var delimiter = this.question.outerHTML;
+ var temp = fulltext.split(delimiter);
+ var content = temp[1];
+ this.contentArray = content.split("---");
+ if (this.contentArray.length === 1) { // If there are no ---, then every line is its own block
+ this.contentArray = content.split("\n");
+ }
+ // remove newline characters that precede and follow the --- delimiters
+ for (var i = 0; i < this.contentArray.length; i++) {
+ while (this.contentArray[i][0] === "\n") {
+ this.contentArray[i] = this.contentArray[i].slice(1);
+ }
+ while (this.contentArray[i][this.contentArray[i].length - 1] === "\n") {
+ this.contentArray[i] = this.contentArray[i].slice(0, -1);
+ }
+ }
+ // Replace newline characters with the literal characters \n
+ for (var i = 0; i < this.contentArray.length; i++) {
+ if (this.contentArray[i].indexOf("\n") !== -1) {
+ var newString = "";
+ for (var j = 0; j < this.contentArray[i].length; j ++) {
+ if (this.contentArray[i][j] === "\n") {
+ newString += "\\n";
+ } else {
+ newString += this.contentArray[i][j];
+ }
+ }
+ this.contentArray[i] = newString;
+ }
+ }
+ this.fmtCode = this.contentArray.join("\n");
+};
+
+/*====================================
+== Creating/appending new HTML tags ==
+====================================*/
+Parsons.prototype.createParsonsView = function () { // Create DOM elements
+ this.containingDiv = document.createElement("div");
+ $(this.containingDiv).addClass("parsons alert alert-warning");
+ this.containingDiv.id = "parsons-" + this.counterId;
+
+ this.parsTextDiv = document.createElement("div");
+ $(this.parsTextDiv).addClass("parsons-text");
+ this.parsTextDiv.innerHTML = this.question.innerHTML;
+ this.containingDiv.appendChild(this.parsTextDiv);
+
+ this.leftClearDiv = document.createElement("div");
+ this.leftClearDiv.style["clear"] = "left";
+ this.containingDiv.appendChild(this.leftClearDiv);
+
+ this.origDiv = document.createElement("div");
+ this.origDiv.id = "parsons-orig-" + this.counterId;
+ this.origDiv.style["display"] = "none";
+ this.origDiv.innerHTML = this.fmtCode;
+ this.containingDiv.appendChild(this.origDiv);
+
+ this.sortContainerDiv = document.createElement("div");
+ $(this.sortContainerDiv).addClass("sortable-code-container");
+ this.containingDiv.appendChild(this.sortContainerDiv);
+
+ this.sortTrashDiv = document.createElement("div");
+ this.sortTrashDiv.id = "parsons-sortableTrash-" + this.counterId;
+ $(this.sortTrashDiv).addClass("sortable-code");
+ this.sortContainerDiv.appendChild(this.sortTrashDiv);
+
+ this.sortCodeDiv = document.createElement("div");
+ this.sortCodeDiv.id = "parsons-sortableCode-" + this.counterId;
+ $(this.sortCodeDiv).addClass("sortable-code");
+ this.sortContainerDiv.appendChild(this.sortCodeDiv);
+
+ this.otherLeftClearDiv = document.createElement("div");
+ this.otherLeftClearDiv.style["clear"] = "left";
+ this.sortContainerDiv.appendChild(this.otherLeftClearDiv);
+
+ this.parsonsControlDiv = document.createElement("div");
+ $(this.parsonsControlDiv).addClass("parsons-controls");
+ this.containingDiv.appendChild(this.parsonsControlDiv);
+
+ this.checkButt = document.createElement("button");
+ $(this.checkButt).attr("class", "btn btn-success");
+ this.checkButt.textContent = "Check Me";
+ this.checkButt.id = "checkMe" + this.counterId;
+ this.parsonsControlDiv.appendChild(this.checkButt);
+
+ this.resetButt = document.createElement("button");
+ $(this.resetButt).attr("class", "btn btn-default");
+ this.resetButt.textContent = "Reset";
+ this.resetButt.id = "reset" + this.counterId;
+ this.parsonsControlDiv.appendChild(this.resetButt);
+
+ this.setButtonFunctions();
+
+ this.messageDiv = document.createElement("div");
+ this.messageDiv.id = "parsons-message-" + this.counterId;
+ this.parsonsControlDiv.appendChild(this.messageDiv);
+ $(this.messageDiv).hide();
+
+ $(this.origElem).replaceWith(this.containingDiv);
+
+ this.createParsonsWidget();
+};
+
+Parsons.prototype.setButtonFunctions = function () {
+ $pjQ(this.resetButt).click(function (event) {
+ event.preventDefault();
+ this.pwidget.shuffleLines();
+
+ // set min width and height
+ var sortableul = $("#ul-parsons-sortableCode-" + this.counterId);
+ var trashul = $("#ul-parsons-sortableTrash-" + this.counterId);
+ var sortableHeight = sortableul.height();
+ var sortableWidth = sortableul.width();
+ var trashWidth = trashul.width();
+ var trashHeight = trashul.height();
+ var minHeight = Math.max(trashHeight, sortableHeight);
+ var minWidth = Math.max(trashWidth, sortableWidth);
+ trashul.css("min-height", minHeight + "px");
+ sortableul.css("min-height", minHeight + "px");
+ trashul.css("min-width", minWidth + "px");
+ sortableul.css("min-width", minWidth + "px");
+ $(this.messageDiv).hide();
+ }.bind(this));
+ $pjQ(this.checkButt).click(function (event) {
+ event.preventDefault();
+ var hash = this.pwidget.getHash("#ul-parsons-sortableCode-" + this.counterId);
+ localStorage.setItem(this.divid, hash);
+ hash = this.pwidget.getHash("#ul-parsons-sortableTrash-" + this.counterId);
+ localStorage.setItem(this.divid + "-trash", hash);
+
+ this.pwidget.getFeedback();
+ $(this.messageDiv).fadeIn(100);
+
+ }.bind(this));
+};
+
+/*================================
+== Create Parsons functionality ==
+================================*/
+
+Parsons.prototype.createParsonsWidget = function () {
+ // First do animation stuff
+ $("#parsons-" + this.counterId).not(".sortable-code").not(".parsons-controls").on("click", function () {
+ $("html, body").animate({
+ scrollTop: ($("#parsons-" + this.counterId).offset().top - 50)
+ }, 700);
+ }).find(".sortable-code, .parsons-controls").click(function (e) {
+ return false;
+ });
+
+ this.pwidget = new ParsonsWidget({
+ "sortableId": "parsons-sortableCode-" + this.counterId,
+ "trashId": "parsons-sortableTrash-" + this.counterId,
+ "max_wrong_lines": 1,
+ "solution_label": "Drop blocks here",
+ "feedback_cb": this.displayErrors.bind(this)
+ });
+
+ this.pwidget.init($pjQ(this.origDiv).text());
+ this.pwidget.shuffleLines();
+ this.tryLocalStorage();
+ this.styleNewHTML();
+};
+
+Parsons.prototype.styleNewHTML = function () {
+ // set min width and height
+ var sortableul = $("#ul-parsons-sortableCode-" + this.counterId);
+ var trashul = $("#ul-parsons-sortableTrash-" + this.counterId);
+ var sortableHeight = sortableul.height();
+ var sortableWidth = sortableul.width();
+ var trashWidth = trashul.width();
+ var trashHeight = trashul.height();
+ var minHeight = Math.max(trashHeight, sortableHeight);
+ var minWidth = Math.max(trashWidth, sortableWidth);
+ var test = document.getElementById("ul-parsons-sortableTrash-" + this.counterId);
+ trashul.css("min-height", minHeight + "px");
+ sortableul.css("min-height", minHeight + "px");
+ sortableul.height(minHeight);
+ trashul.css("min-width", minWidth + "px");
+ sortableul.css("min-width", minWidth + "px");
+ test.minWidth = minWidth + "px";
+};
+
+Parsons.prototype.displayErrors = function (fb) { // Feedback function
+ if (fb.errors.length > 0) {
+ var hash = this.pwidget.getHash("#ul-parsons-sortableCode-" + this.counterId);
+ $(this.messageDiv).fadeIn(500);
+ $(this.messageDiv).attr("class", "alert alert-danger");
+ $(this.messageDiv).html(fb.errors[0]);
+ this.logBookEvent({"event": "parsons", "act": hash, "div_id": this.divid});
+ } else {
+ this.logBookEvent({"event": "parsons", "act": "yes", "div_id": this.divid});
+ $(this.messageDiv).fadeIn(100);
+ $(this.messageDiv).attr("class", "alert alert-success");
+ $(this.messageDiv).html("Perfect!");
+ }
+};
+
+Parsons.prototype.tryLocalStorage = function () {
+ if (localStorage.getItem(this.divid) && localStorage.getItem(this.divid + "-trash")) {
+ try {
+ var solution = localStorage.getItem(this.divid);
+ var trash = localStorage.getItem(this.divid + "-trash");
+ this.pwidget.createHTMLFromHashes(solution, trash);
+ this.pwidget.getFeedback();
+ } catch(err) {
+ var text = "An error occured restoring old " + this.divid + " state. Error: ";
+ console.log(text + err.message);
+ }
+ }
+};
+
+$(document).ready(function () {
+ $pjQ("[data-component=parsons]").each(function (index) {
+ prsList[this.id] = new Parsons({"orig": this});
+ });
+
+});
diff --git a/runestone/parsons/js/parsonsaux.js b/runestone/parsons/js/parsonsaux.js
deleted file mode 100644
index 3ef2c8a7a..000000000
--- a/runestone/parsons/js/parsonsaux.js
+++ /dev/null
@@ -1,971 +0,0 @@
-var _p = _.noConflict(); // No conflict version of underscore
-
-// regexp used for trimming
-var trimRegexp = /^\s*(.*?)\s*$/;
-var translations = {
- fi: {
- trash_label: "Raahaa rivit ohjelmaasi tästä",
- solution_label: "Muodosta ratkaisusi tähän",
- order: function () {
- return "Ohjelma sisältää vääriä palasia tai palasten järjestys on väärä. Tämä on mahdollista korjata siirtämällä, poistamalla tai vaihtamalla korostettuja palasia.";
- },
- lines_missing: function () {
- return "Ohjelmassasi on liian vähän palasia, jotta se toimisi oikein.";
- },
- no_matching: function (lineNro) {
- return "Korostettu palanen (" + lineNro + ") on sisennetty Pythonin kieliopin vastaisesti.";
- },
- block_structure: function (lineNro) {
- return "Korostettu palanen (" + lineNro + ") on sisennetty väärään koodilohkoon.";
- },
- unittest_error: function (errormsg) {
- return "Virhe ohjelman jäsentämisessä/suorituksessa
" + errormsg + "";
- },
- unittest_output_assertion: function (expected, actual) {
- return "Odotettu tulostus: " + expected + "" +
- "Ohjelmasi tulostus: " + actual + "";
- },
- unittest_assertion: function (expected, actual) {
- return "Odotettu arvo: " + expected + "
" +
- "Ohjelmasi antama arvo: " + actual + "";
- },
- variabletest_assertion: function (varname, expected, actual) {
- return "Muuttujan " + varname + " odotettu arvo: " + expected + " " +
- "Ohjelmasi antama arvo: " + actual + "";
- }
- },
- en: {
- trash_label: "Drag from here",
- solution_label: "Construct your solution here",
- order: function () {
- return "Code fragments in your program are wrong, or in wrong order. This can be fixed by moving, removing, or replacing highlighted fragments.";
- },
- lines_missing: function () {
- return "Your program has too few code fragments.";
- },
- no_matching: function (lineNro) {
- return "Based on python syntax, the highlighted fragment (" + lineNro + ") is not correctly indented.";
- },
- block_structure: function (lineNro) {
- return "The highlighted fragment " + lineNro + " belongs to a wrong block (i.e. indentation).";
- },
- unittest_error: function (errormsg) {
- return "Error in parsing/executing your program
" + errormsg + "";
- },
- unittest_output_assertion: function (expected, actual) {
- return "Expected output: " + expected + "" +
- "Output of your program: " + actual + "";
- },
- unittest_assertion: function (expected, actual) {
- return "Expected value: " + expected + "
" +
- "Actual value: " + actual + "";
- },
- variabletest_assertion: function (varname, expected, actual) {
- return "Expected value of variable " + varname + ": " + expected + "
" +
- "Actual value: " + actual + "";
- }
- }
-};
-
-// Different graders
-
-// Grader that will execute the code and check variable values after that
-// Expected and supported options:
-// - vartests (required): array of variable test objects
-// Each variable test object can/must have the following properties:
-// - initcode: code that will be prepended before the learner solution code
-// - code: code that will be appended after the learner solution code
-// - message (required): a textual description of the test, shown to learner
-// Properties specifying what is tested:
-// - variables: an object with properties for each variable name to
-// be tested; the value of the property is the expected
-// value
-// or
-// - variable: a variable name to be tested
-// - expected: expected value of the variable after code execution
-var VariableCheckGrader = function (parson) {
- this.parson = parson;
-};
-// Executes the given code using Skulpt and returns an object with variable
-// values of the variables given in the variables array.
-// Possible errors will be in the _error property of the returned object.
-// Output of the code will be in _output property of the result.
-// Example: this._python_exec('x=0\ny=2\nprint x', ['x', 'y'])
-// will return object {'x': 0, 'y': 2, '_output': '0'}
-VariableCheckGrader.prototype._python_exec = function (code, variables) {
- var output = "",
- mainmod,
- result = {"variables": {}},
- varname;
- // configure Skulpt
- Sk.execLimit = this.parson.options.exec_limit || 2500; // time limit for the code to run
- Sk.configure({ output: function (str) { output += str; },
- python3: this.parson.options.python3 || false
- });
- try {
- mainmod = Sk.importMainWithBody("", false, code);
- } catch (e) {
- return {"_output": output, "_error": "" + e};
- }
- for (var i = 0; i < variables.length; i++) {
- varname = variables[i];
- result.variables[varname] = mainmod.tp$getattr(varname);
- }
- result._output = output;
- return result;
-};
-VariableCheckGrader.prototype.formatVariableValue = function (varValue) {
- var varType = typeof varValue;
- if (varType === "undefined" || varValue === null) {
- return "None";
- } else if (varType === "string") { // show strings in quotes
- return "'" + varValue + "'";
- } else if (varType === "boolean") { // Python booleans with capital first letter
- return varValue ? "True" : "False";
- } else if ($.isArray(varValue)) { // JavaScript arrays
- return "[" + varValue.join(", ") + "]";
- } else if (varType === "object" && varValue.tp$name === "str") { // Python strings
- return "'" + varValue.v + "'";
- } else if (varType === "object" && varValue.tp$name === "list") { // Python lists
- return "[" + varValue.v.join(", ") + "]";
- } else {
- return varValue;
- }
-};
-// Fix or strip line numbers in the (error) message
-// Basically removes the number of lines in prependCode from the line number shown.
-VariableCheckGrader.prototype.stripLinenumberIfNeeded = function (msg, prependCode, studentCode) {
- var lineNbrRegexp = /.*on line ([0-9]+).*/;
- // function that fixes the line numbers in student feedback
- var match = msg.match(lineNbrRegexp);
- if (match) {
- var lineNo = parseInt(match[1], 10),
- lowerLimit = prependCode ?
- prependCode.split("\n").length
- : 0,
- upperLimit = lowerLimit + studentCode.split("\n").length - 1;
- // if error in prepended code or tests, remove the line number
- if (lineNo <= lowerLimit || lineNo > upperLimit) {
- return msg.replace(" on line " + lineNo, "");
- } else if (lowerLimit > 0) {
- // if error in student code, make sure the line number matches student lines
- return msg.replace(" on line " + lineNo, " on line " + (lineNo - lowerLimit));
- }
- }
- return msg;
-};
-VariableCheckGrader.prototype.grade = function () {
- var parson = this.parson,
- that = this,
- feedback = "",
- log_errors = [],
- all_passed = true;
- $.each(parson.options.vartests, function (index, testdata) {
- // var $lines = $("#sortable li");
- var student_code = parson._codelinesAsString();
- var executableCode = (testdata.initcode || "") + "\n" + student_code + "\n" + (testdata.code || "");
- var variables, expectedVals;
- if ("variables" in testdata) {
- variables = _p.keys(testdata.variables);
- expectedVals = testdata.variables;
- } else {
- variables = [testdata.variable];
- expectedVals = {};
- expectedVals[testdata.variable] = testdata.expected;
- }
- var res = that._python_exec(executableCode, variables);
- var testcaseFeedback = "",
- success = true,
- log_entry = {"code": testdata.code, "msg": testdata.message},
- expected_value,
- actual_value;
- if ("_error" in res) {
- testcaseFeedback += parson.translations.unittest_error(that.stripLinenumberIfNeeded(res._error,
- testdata.initcode,
- student_code));
- success = false;
- log_entry.type = "error";
- log_entry.errormsg = res._error;
- } else {
- log_entry.type = "assertion";
- log_entry.variables = {};
- for (var j = 0; j < variables.length; j++) {
- var variable = variables[j];
- if (variable === "__output") { // checking output of the program
- expected_value = testdata.expected;
- actual_value = res._output;
- testcaseFeedback += parson.translations.unittest_output_assertion(expected_value, actual_value);
- } else {
- expected_value = that.formatVariableValue(expectedVals[variable]);
- actual_value = that.formatVariableValue(res.variables[variable].v);
- testcaseFeedback += parson.translations.variabletest_assertion(variable, expected_value, actual_value) + "
";
- }
- log_entry.variables[variable] = {expected: expected_value, actual: actual_value};
- if (actual_value !== expected_value) { // should we do a strict test??
- success = false;
- }
- }
- }
- all_passed = all_passed && success;
- log_entry.success = success;
- log_errors.push(log_entry);
- feedback += "" + testdata.message + "
" +
- testcaseFeedback + "";
- });
- return { html: feedback, "log_errors": log_errors, success: all_passed };
-};
-// Grader that will execute student code and Skulpt unittests
-var UnitTestGrader = function (parson) {
- this.parson = parson;
-};
-// copy the line number fixer from VariableCheckGrader
-UnitTestGrader.prototype.stripLinenumberIfNeeded = VariableCheckGrader.prototype.stripLinenumberIfNeeded;
-// do the grading
-UnitTestGrader.prototype.grade = function () {
- var success = true,
- parson = this.parson,
- unittests = parson.options.unittests,
- studentCode = parson._codelinesAsString(),
- feedbackHtml = "", // HTML to be returned as feedback
- result, mainmod;
-
- var executableCode = studentCode + "\n" + unittests;
-
- // if there is code to add before student code, add it
- if (parson.options.unittest_code_prepend) {
- executableCode = parson.options.unittest_code_prepend + "\n" + executableCode;
- }
-
- function builtinRead (x) {
- if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined) {
- throw "File not found: '" + x + "'";
- }
- return Sk.builtinFiles["files"][x];
- }
-
- // configuration for Skulpt
- Sk.execLimit = parson.options.exec_limit || 2500; // time limit for the code to run
- Sk.configure({output: console ? console.log : function () {},
- read: builtinRead,
- python3: parson.options.python3 || false
- });
- try {
- mainmod = Sk.importMainWithBody("", false, executableCode);
- result = JSON.parse(mainmod.tp$getattr("_test_result").v);
- } catch (e) {
- result = [{status: "error", _error: e.toString() }];
- }
-
- // go through the results and generate HTML feedback
- for (var i = 0, l = result.length; i < l; i++) {
- var res = result[i];
- feedbackHtml += "";
- if (res.status === "error") { // errors in execution
- feedbackHtml += parson.translations.unittest_error(this.stripLinenumberIfNeeded(res._error,
- parson.options.unittest_code_prepend,
- studentCode));
- success = false;
- } else { // passed or failed tests
- feedbackHtml += "" + this.stripLinenumberIfNeeded(res.feedback) + "
";
- feedbackHtml += "Expected " + res.expected +
- "" + res.test + "" + res.actual +
- "";
- if (res.status === "fail") {
- success = false;
- }
- }
- feedbackHtml += "";
- }
-
- return { html: feedbackHtml, result: result, success: success };
-};
-
-// The 'original' grader for giving line based feedback.
-
-var LineBasedGrader = function (parson) {
- this.parson = parson;
-};
-LineBasedGrader.prototype.grade = function (elementId) {
- var parson = this.parson;
- var elemId = elementId || parson.options.sortableId;
- var student_code = parson.normalizeIndents(parson.getModifiedCode("#ul-" + elemId));
- var lines_to_check = Math.min(student_code.length, parson.model_solution.length);
- var errors = [], log_errors = [];
- var incorrectLines = [], lines = [];
- var id, line, i;
- var wrong_order = false;
-
- // remove distractors from lines and add all those to the set of misplaced lines
- for (i = 0; i < student_code.length; i++) {
- id = parseInt(student_code[i].id.replace(parson.id_prefix, ""), 10);
- line = parson.getLineById(parson.id_prefix + id);
- if (line.distractor) {
- incorrectLines.push(id);
- wrong_order = true;
- $("#" + parson.id_prefix + id).addClass("incorrectPosition");
- } else {
- lines.push(id);
- }
- }
-
- var inv = LIS.best_lise_inverse(lines);
- _p.each(inv, function (itemId) {
- $("#" + parson.id_prefix + itemId).addClass("incorrectPosition");
- incorrectLines.push(itemId);
- });
- if (inv.length > 0 || errors.length > 0) {
- wrong_order = true;
- log_errors.push({type: "incorrectPosition", lines: incorrectLines});
- }
-
- if (wrong_order) {
- errors.push(parson.translations.order());
- }
-
- // Always show this feedback
- if (parson.model_solution.length < student_code.length) {
- // $("#ul-" + elemId).addClass("incorrect");
- // errors.push("Too many lines in your solution.");
- log_errors.push({type: "tooManyLines", lines: student_code.length});
- } else if (parson.model_solution.length > student_code.length) {
- $("#ul-" + elemId).addClass("incorrect");
- errors.push(parson.translations.lines_missing());
- log_errors.push({type: "tooFewLines", lines: student_code.length});
- }
-
- if (errors.length === 0) { // check indent if no other errors
- for (i = 0; i < lines_to_check; i++) {
- var code_line = student_code[i];
- var model_line = parson.model_solution[i];
- if (code_line.indent !== model_line.indent &&
- ((!parson.options.first_error_only) || errors.length === 0)) {
- $("#" + code_line.id).addClass("incorrectIndent");
- errors.push(parson.translations.block_structure(i + 1));
- log_errors.push({type: "incorrectIndent", line: (i + 1)});
- }
- if (code_line.code === model_line.code &&
- code_line.indent === model_line.indent &&
- errors.length === 0) {
- $("#" + code_line.id).addClass("correctPosition");
- }
- }
- }
-
- return {errors: errors, log_errors: log_errors, success: (errors.length === 0)};
-};
-
-var python_indents = [],
- spaces = "";
-for (var counter = 0; counter < 20; counter++) {
- python_indents[counter] = spaces;
- spaces += " ";
-}
-
-var defaultToggleTypeHandlers = {
- boolean: ["True", "False"],
- compop: ["<", ">", "<=", ">=", "==", "!="],
- mathop: ["+", "-", "*", "/"],
- boolop: ["and", "or"],
- range: function ($item) {
- var min = parseFloat($item.data("min") || "0", 10),
- max = parseFloat($item.data("max") || "10", 10),
- step = parseFloat($item.data("step") || "1", 10),
- opts = [],
- curr = min;
- while (curr <= max) {
- opts.push("" + curr);
- curr += step;
- }
- return opts;
- }
-};
-var addToggleableElements = function (widget) {
- // toggleable elements are only enabled for unit tests
- if (!widget.options.unittests && !widget.options.vartests) { return; }
- var handlers = $.extend(defaultToggleTypeHandlers, widget.options.toggleTypeHandlers),
- context = $("#" + widget.options.sortableId + ", #" + widget.options.trashId);
- $(".jsparson-toggle", context).each(function (index, item) {
- var type = $(item).data("type");
- if (!type) { return; }
- var handler = handlers[type],
- jspOptions;
- if ($.isFunction(handler)) {
- jspOptions = handler($(item));
- } else {
- jspOptions = handler;
- }
- if (jspOptions && $.isArray(jspOptions)) {
- $(item).attr("data-jsp-options", JSON.stringify(jspOptions));
- }
- });
- // register a click handler for all the toggleable elements (and unregister existing)
- context.off("click", ".jsparson-toggle").on("click", ".jsparson-toggle", function () {
- var $this = $(this),
- curVal = $this.text(),
- choices = $this.data("jsp-options"),
- newVal = choices[(choices.indexOf(curVal) + 1) % choices.length],
- $parent = $this.parent("li");
- // change the shown toggle element
- $this.text(newVal);
- // log the event
- widget.addLogEntry({type: "toggle", oldvalue: curVal, newvalue: newVal,
- target: $parent[0].id,
- toggleindex: $parent.find(".jsparson-toggle").index($this)});
- });
-};
-
-var ParsonsWidget = function (options) {
- this.modified_lines = [];
- this.extra_lines = [];
- this.model_solution = [];
-
- // To collect statistics, feedback should not be based on this
- this.user_actions = [];
-
- // State history for feedback purposes
- this.state_path = [];
- this.states = {};
-
- var defaults = {
- "incorrectSound": false,
- "x_indent": 50,
- "feedback_cb": false,
- "first_error_only": true,
- "max_wrong_lines": 10,
- "lang": "en"
- };
- this.options = jQuery.extend({}, defaults, options);
- this.feedback_exists = false;
- this.id_prefix = options["sortableId"] + "codeline";
- if (translations.hasOwnProperty(this.options.lang)) {
- this.translations = translations[this.options.lang];
- } else {
- this.translations = translations["en"];
- }
-
- // translate trash_label and solution_label
- if (!this.options.hasOwnProperty("trash_label")) {
- this.options.trash_label = this.translations.trash_label;
- }
- if (!this.options.hasOwnProperty("solution_label")) {
- this.options.solution_label = this.translations.solution_label;
- }
- this.FEEDBACK_STYLES = { "correctPosition": "correctPosition",
- "incorrectPosition": "incorrectPosition",
- "correctIndent": "correctIndent",
- "incorrectIndent": "incorrectIndent"};
-
- // initialize the grader
- if (typeof (this.options.unittests) !== "undefined") { // unittests are specified
- this.grader = new UnitTestGrader(this);
- } else if (typeof (this.options.vartests) !== "undefined") { // tests for variable values
- this.grader = new VariableCheckGrader(this);
- } else { // 'traditional' parson feedback
- this.grader = new LineBasedGrader(this);
- }
-};
-
-// Public methods
-ParsonsWidget.prototype.parseLine = function (spacePrefixedLine) {
- return {
- code: spacePrefixedLine.replace(trimRegexp, "$1").replace(/\\n/g, "\n"),
- indent: spacePrefixedLine.length - spacePrefixedLine.replace(/^\s+/, "").length
- };
-};
-
-ParsonsWidget.prototype.parseCode = function (lines, max_distractors) {
- var distractors = [],
- indented = [],
- widgetData = [],
- lineObject,
- errors = [],
- that = this;
- $.each(lines, function (index, item) {
- if (item.search(/#distractor\s*$/) >= 0) {
- lineObject = {
- code: item.replace(/#distractor\s*$/, "").replace(trimRegexp, "$1").replace(/\\n/, "\n"),
- indent: -1,
- distractor: true,
- orig: index
- };
- if (lineObject.code.length > 0) {
- distractors.push(lineObject);
- }
- } else {
- lineObject = that.parseLine(item);
- if (lineObject.code.length > 0) {
- lineObject.distractor = false;
- lineObject.orig = index;
- indented.push(lineObject);
- }
- }
- });
-
- // Normalize indents and make sure indentation is valid
- var normalized = this.normalizeIndents(indented);
- var _that = this;
- $.each(normalized, function (index, item) {
- if (item.indent < 0) {
- errors.push(_that.translations.no_matching(normalized.orig));
- }
- widgetData.push(item);
- });
-
- // Remove extra distractors
- var permutation = this.getRandomPermutation(distractors.length);
- var selected_distractors = [];
- for (var i = 0; i < max_distractors; i++) {
- selected_distractors.push(distractors[permutation[i]]);
- widgetData.push(distractors[permutation[i]]);
- }
-
- return {
- solution: $.extend(true, [], normalized),
- distractors: $.extend(true, [], selected_distractors),
- widgetInitial: $.extend(true, [], widgetData),
- errors: errors};
-};
-
-ParsonsWidget.prototype.init = function (text) {
- var initial_structures = this.parseCode(text.split("\n"), this.options.max_wrong_lines);
- this.model_solution = initial_structures.solution;
- this.extra_lines = initial_structures.distractors;
- this.modified_lines = initial_structures.widgetInitial;
- this.alternatives = {};
- var that = this;
-
- $.each(this.modified_lines, function (index, item) {
- item.id = that.id_prefix + index;
- item.indent = 0;
- if (that.alternatives.hasOwnProperty(item.code)) {
- that.alternatives[item.code].push(index);
- } else {
- that.alternatives[item.code] = [index];
- }
- });
-
-};
-
-ParsonsWidget.prototype.getHash = function (searchString) {
- var ids = [];
- var hash = [];
- ids = $(searchString).sortable("toArray");
- for (var i = 0; i < ids.length; i++) {
- hash.push(ids[i].replace(this.id_prefix, "") + "_" + this.getLineById(ids[i]).indent);
- }
- // prefix with something to handle empty output situations
- if (hash.length === 0) {
- return "-";
- } else {
- return hash.join("-");
- }
-};
-
-ParsonsWidget.prototype.solutionHash = function () {
- return this.getHash("#ul-" + this.options.sortableId);
-};
-
-ParsonsWidget.prototype.trashHash = function () {
- return this.getHash("#ul-" + this.options.trashId);
-};
-
-ParsonsWidget.prototype.whatWeDidPreviously = function () {
- var hash = this.solutionHash();
- var previously = this.states[hash];
- if (!previously) { return undefined; }
- var visits = _p.filter(this.state_path, function (state) {
- return state === hash;
- }).length - 1;
- var i, stepsToLast = 0, s,
- outputStepTypes = ["removeOutput", "addOutput", "moveOutput"];
- for (i = this.state_path.length - 2; i > 0; i--) {
- s = this.states[this.state_path[i]];
- if (s && outputStepTypes.indexOf(s.type) !== -1) {
- stepsToLast++;
- }
- if (hash === this.state_path[i]) { break; }
- }
- return $.extend(false, {"visits": visits, stepsToLast: stepsToLast}, previously);
-};
-
-/**
- * Returns states of the toggles for logging purposes
- */
-ParsonsWidget.prototype._getToggleStates = function () {
- // var context = $("#" + this.options.sortableId + ", #" + this.options.trashId),
- // toggles = $(".jsparson-toggle", context),
- var toggleStates = {};
- $("#" + this.options.sortableId + " .jsparson-toggle").each(function () {
- if (!toggleStates.output) {
- toggleStates.output = [];
- }
- toggleStates.output.push($(this).text());
- });
- if (this.options.trashId) {
- toggleStates.input = [];
- $("#" + this.options.trashId + " .jsparson-toggle").each(function () {
- toggleStates.input.push($(this).text());
- });
- }
- if ((toggleStates.output && toggleStates.output.length > 0) ||
- (toggleStates.input && toggleStates.input.length > 0)) {
- return toggleStates;
- } else {
- return undefined;
- }
-};
-
-ParsonsWidget.prototype.addLogEntry = function (entry) {
- var state, previousState;
- var logData = {
- time: new Date(),
- output: this.solutionHash(),
- type: "action"
- };
-
- if (this.options.trashId) {
- logData.input = this.trashHash();
- }
-
- if (entry.target) {
- entry.target = entry.target.replace(this.id_prefix, "");
- }
-
- // add toggle states to log data if there are toggles
- var toggles = this._getToggleStates();
- if (toggles) {
- logData.toggleStates = toggles;
- }
-
- state = logData.output;
-
- jQuery.extend(logData, entry);
- this.user_actions.push(logData);
-
- // Updating the state history
- if (this.state_path.length > 0) {
- previousState = this.state_path[this.state_path.length - 1];
- this.states[previousState] = logData;
- }
-
- // Add new item to the state path only if new and previous states are not equal
- if (this.state_path[this.state_path.length - 1] !== state) {
- this.state_path.push(state);
- }
- // callback for reacting to actions
- if ($.isFunction(this.options.action_cb)) {
- this.options.action_cb.call(this, logData);
- }
-};
-
-/**
- * Update indentation of a line based on new coordinates
- * leftDiff horizontal difference from (before and after drag) in px
- ***/
-ParsonsWidget.prototype.updateIndent = function (leftDiff, id) {
-
- var code_line = this.getLineById(id);
- var new_indent = code_line.indent + Math.floor(leftDiff / this.options.x_indent);
- new_indent = Math.max(0, new_indent);
- code_line.indent = new_indent;
-
- return new_indent;
-};
-
-/**
- *
- * @param id
- * @return
- */
-ParsonsWidget.prototype.getLineById = function (id) {
- var index = -1;
- for (var i = 0; i < this.modified_lines.length; i++) {
- if (this.modified_lines[i].id === id) {
- index = i;
- break;
- }
- }
- return this.modified_lines[index];
-};
-
-/** Does not use the current object - only the argument */
-ParsonsWidget.prototype.normalizeIndents = function (lines) {
-
- var normalized = [];
- var new_line;
- var match_indent = function (index) {
- // return line index from the previous lines with matching indentation
- for (var i = index - 1; i >= 0; i--) {
- if (lines[i].indent === lines[index].indent) {
- return normalized[i].indent;
- }
- }
- return -1;
- };
- for (var i = 0; i < lines.length; i++) {
- // create shallow copy from the line object
- new_line = jQuery.extend({}, lines[i]);
- if (i === 0) {
- new_line.indent = 0;
- if (lines[i].indent !== 0) {
- new_line.indent = -1;
- }
- } else if (lines[i].indent === lines[i - 1].indent) {
- new_line.indent = normalized[i - 1].indent;
- } else if (lines[i].indent > lines[i - 1].indent) {
- new_line.indent = normalized[i - 1].indent + 1;
- } else {
- // indentation can be -1 if no matching indentation exists, i.e. IndentationError in Python
- new_line.indent = match_indent(i);
- }
- normalized[i] = new_line;
- }
- return normalized;
-};
-
-/**
- * Retrieve the code lines based on what is in the DOM
- *
- * TODO(petri) refactor to UI
- * */
-ParsonsWidget.prototype.getModifiedCode = function (search_string) {
- // ids of the the modified code
- var lines_to_return = [],
- that = this;
- $(search_string).find("li").each(function (index, item) {
- lines_to_return.push({id: $(item).attr("id"),
- indent: parseInt($(item).css("margin-left"), 10) / that.options.x_indent});
- });
- return lines_to_return;
-};
-
-ParsonsWidget.prototype.hashToIDList = function (hash) {
- // var lines = [];
- var lineValues;
- // var lineObject;
- var h;
-
- if (hash === "-" || hash === "" || hash === null) {
- h = [];
- } else {
- h = hash.split("-");
- }
-
- var ids = [];
- for (var i = 0; i < h.length; i++) {
- lineValues = h[i].split("_");
- ids.push(this.modified_lines[lineValues[0]].id);
- }
- return ids;
-};
-
-ParsonsWidget.prototype.updateIndentsFromHash = function (hash) {
- // var lines = [];
- var lineValues;
- // var lineObject;
- var h;
-
- if (hash === "-" || hash === "" || hash === null) {
- h = [];
- } else {
- h = hash.split("-");
- }
-
- var ids = [];
- for (var i = 0; i < h.length; i++) {
- lineValues = h[i].split("_");
- this.modified_lines[lineValues[0]].indent = Number(lineValues[1]);
- this.updateHTMLIndent(this.modified_lines[lineValues[0]].id);
- }
- return ids;
-};
-
-/**
- * TODO(petri) refoctor to UI
- */
-ParsonsWidget.prototype.displayError = function (message) {
- if (this.options.incorrectSound && $.sound) {
- $.sound.play(this.options.incorrectSound);
- }
- alert(message);
-};
-
-ParsonsWidget.prototype.colorFeedback = function (elemId) {
- return new LineBasedGrader(this).grade(elemId);
-};
-
-ParsonsWidget.prototype._codelinesAsString = function () {
- // var $lines = $("#" + this.options.sortableId + " li");
- var student_code = this.normalizeIndents(this.getModifiedCode("#ul-" + this.options.sortableId));
- var executableCode = "";
- $.each(student_code, function (index, item) {
- // split codeblocks on br elements
- var lines = $("#" + item.id).html().split(/
/);
- // go through all the lines
- for (var i = 0; i < lines.length; i++) {
- // add indents and get the text for the line (to remove the syntax highlight html elements)
- executableCode += python_indents[item.indent] + $("" + lines[i] + "").text() + "\n";
- }
- });
- return executableCode;
-};
-
-/**
- * @return
- * TODO(petri): Separate UI from here
- */
-ParsonsWidget.prototype.getFeedback = function () {
- this.feedback_exists = true;
- var fb = this.grader.grade();
- if (this.options.feedback_cb) {
- this.options.feedback_cb(fb); // TODO(petri): what is needed?
- }
- // if answer is correct, mark it in the UI
- if (fb.success) {
- $("#ul-" + this.options.sortableId).addClass("correct");
- }
- // log the feedback and return; based on the type of grader
- if ("html" in fb) { // unittest/vartests type feedback
- this.addLogEntry({type: "feedback", errors: fb.result, success: fb.success, toggles: this._getToggleStates()});
- return { feedback: fb.html, success: fb.success };
- } else {
- this.addLogEntry({type: "feedback", errors: fb.log_errors, success: fb.success});
- return fb.errors;
- }
-};
-
-ParsonsWidget.prototype.clearFeedback = function () {
- if (this.feedback_exists) {
- $("#ul-" + this.options.sortableId).removeClass("incorrect correct");
- var li_elements = $("#ul-" + this.options.sortableId + " li");
- $.each(this.FEEDBACK_STYLES, function (index, value) {
- li_elements.removeClass(value);
- });
- }
- this.feedback_exists = false;
-};
-
-ParsonsWidget.prototype.getRandomPermutation = function (n) {
- var permutation = [];
- var i;
- for (i = 0; i < n; i++) {
- permutation.push(i);
- }
- var swap1, swap2, tmp;
- for (i = 0; i < n; i++) {
- swap1 = Math.floor(Math.random() * n);
- swap2 = Math.floor(Math.random() * n);
- tmp = permutation[swap1];
- permutation[swap1] = permutation[swap2];
- permutation[swap2] = tmp;
- }
- return permutation;
-};
-
-ParsonsWidget.prototype.shuffleLines = function () {
- var permutation = this.getRandomPermutation(this.modified_lines.length);
- var idlist = [];
- for (var i = 0; i < permutation.length; i++) {
- idlist.push(this.modified_lines[permutation[i]].id);
- }
- if (this.options.trashId) {
- this.createHTMLFromLists([], idlist);
- } else {
- this.createHTMLFromLists(idlist, []);
- }
- addToggleableElements(this);
-};
-
-ParsonsWidget.prototype.createHTMLFromHashes = function (solutionHash, trashHash) {
- var solution = this.hashToIDList(solutionHash);
- var trash = this.hashToIDList(trashHash);
- this.createHTMLFromLists(solution, trash);
- this.updateIndentsFromHash(solutionHash);
-};
-
-ParsonsWidget.prototype.updateHTMLIndent = function (codelineID) {
- var line = this.getLineById(codelineID);
- $("#" + codelineID).css("margin-left", this.options.x_indent * line.indent + "px");
-};
-
-ParsonsWidget.prototype.codeLineToHTML = function (codeline) {
- return "" + codeline.code + "<\/li>";
-};
-
-ParsonsWidget.prototype.codeLinesToHTML = function (codelineIDs, destinationID) {
- var lineHTML = [];
- for (var id = 0; id < codelineIDs.length; id++) {
- var line = this.getLineById(codelineIDs[id]);
- lineHTML.push(this.codeLineToHTML(line));
- }
- return "" + lineHTML.join("") + "
";
-};
-
-/** modifies the DOM by inserting exercise elements into it */
-ParsonsWidget.prototype.createHTMLFromLists = function (solutionIDs, trashIDs) {
- var html;
- if (this.options.trashId) {
- html = (this.options.trash_label ? "" + this.options.trash_label + "
" : "") +
- this.codeLinesToHTML(trashIDs, this.options.trashId);
- $("#" + this.options.trashId).html(html);
- html = (this.options.solution_label ? "" + this.options.solution_label + "
" : "") +
- this.codeLinesToHTML(solutionIDs, this.options.sortableId);
- $("#" + this.options.sortableId).html(html);
- } else {
- html = this.codeLinesToHTML(solutionIDs, this.options.sortableId);
- $("#" + this.options.sortableId).html(html);
- }
-
- if (window.prettyPrint && (typeof (this.options.prettyPrint) === "undefined" || this.options.prettyPrint)) {
- prettyPrint();
- }
-
- var that = this;
- var sortable = $("#ul-" + this.options.sortableId).sortable(
- {
- start: function () { that.clearFeedback(); },
- stop: function (event, ui) {
- if ($(event.target)[0] !== ui.item.parent()[0]) {
- return;
- }
- that.updateIndent(ui.position.left - ui.item.parent().offset().left,
- ui.item[0].id);
- that.updateHTMLIndent(ui.item[0].id);
- that.addLogEntry({type: "moveOutput", target: ui.item[0].id}, true);
- },
- receive: function (event, ui) {
- /* var ind = that.updateIndent(ui.position.left - ui.item.parent().offset().left,
- ui.item[0].id); */
- that.updateHTMLIndent(ui.item[0].id);
- that.addLogEntry({type: "addOutput", target: ui.item[0].id}, true);
- },
- grid: [that.options.x_indent, 1 ]
- });
- sortable.addClass("output");
- if (this.options.trashId) {
- var trash = $("#ul-" + this.options.trashId).sortable(
- {
- connectWith: sortable,
- start: function () { that.clearFeedback(); },
- receive: function (event, ui) {
- that.getLineById(ui.item[0].id).indent = 0;
- that.updateHTMLIndent(ui.item[0].id);
- that.addLogEntry({type: "removeOutput", target: ui.item[0].id}, true);
- },
- stop: function (event, ui) {
- if ($(event.target)[0] !== ui.item.parent()[0]) {
- // line moved to output and logged there
- return;
- }
- that.addLogEntry({type: "moveInput", target: ui.item[0].id}, true);
- }
- });
- sortable.sortable("option", "connectWith", trash);
- }
- this.addLogEntry({type: "init", time: new Date(), bindings: this.modified_lines});
-};
-
-window["ParsonsWidget"] = ParsonsWidget;
-// allows _ and $ to be modified with noconflict without changing the globals
-// that parsons uses
diff --git a/runestone/parsons/parsons.py b/runestone/parsons/parsons.py
index b8e765ee5..f9d0db81d 100644
--- a/runestone/parsons/parsons.py
+++ b/runestone/parsons/parsons.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Michael Hewner
+# Copyright (C) 2012 Michael Hewner, Isaiah Mayerchak
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-__author__ = 'hewner'
+__author__ = 'isaiahmayerchak'
import re
from docutils import nodes
@@ -28,18 +28,16 @@ def setup(app):
app.add_stylesheet('parsons.css')
app.add_stylesheet('lib/prettify.css')
- # includes parsons specific javascript headers
- # parsons-noconflict reverts underscore and
- # jquery to their original versions
app.add_javascript('lib/jquery.min.js')
app.add_javascript('lib/jquery-ui.min.js')
+ app.add_javascript('lib/jquery.ui.touch-punch.min.js')
app.add_javascript('lib/prettify.js')
app.add_javascript('lib/underscore-min.js')
app.add_javascript('lib/lis.js')
+ app.add_javascript('parsons_setup.js')
app.add_javascript('parsons.js')
app.add_javascript('parsons-noconflict.js')
-
class ParsonsProblem(Assessment):
required_arguments = 1
optional_arguments = 0
@@ -50,7 +48,7 @@ class ParsonsProblem(Assessment):
def run(self):
"""
- Instructions for solving the problem should be written and then a line with -----
+ Instructions for solving the problem should be written and then a line with -----
signals the beginning of the code. If you want more than one line in a single
code block, seperate your code blocks with =====.
@@ -80,169 +78,28 @@ def findmax(alist):
"""
- template_values = {}
- template_values['qnumber'] = self.getNumber()
- template_values['unique_id'] = self.lineno
- template_values['instructions'] = ""
- code = self.content
-
+ TEMPLATE = '''
+
+ %(qnumber)s: %(instructions)s%(code)s
+ '''
+
+ self.options['divid'] = self.arguments[0]
+ self.options['qnumber'] = self.getNumber()
+ self.options['instructions'] = ""
+ self.options['code'] = self.content
if '-----' in self.content:
index = self.content.index('-----')
- template_values['instructions'] = "\n".join(self.content[:index])
- code = self.content[index + 1:]
+ self.options['instructions'] = "\n".join(self.content[:index])
+ self.options['code'] = self.content[index + 1:]
- if '=====' in code:
- template_values['code'] = self.parse_multiline_parsons(code);
- else:
- template_values['code'] = "\n".join(code)
+ if '=====' in self.options['code']:
+ self.options['code'] = "\n".join(self.options['code'])
- template_values['divid'] = self.arguments[0]
+ self.options['code'] = self.options['code'].replace('=====', '---')
+ else:
+ self.options['code'] = "\n".join(self.options['code'])
- TEMPLATE = '''
-
- %(qnumber)s: %(instructions)s
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'''
+ self.options['divid'] = self.arguments[0]
self.assert_has_content()
- return [nodes.raw('', TEMPLATE % template_values, format='html')]
-
- def parse_multiline_parsons(self, lines):
- current_block = []
- results = []
- for line in lines:
- if (line == '====='):
- results.append(self.convert_leading_whitespace_for_block(current_block))
- current_block = []
- else:
- current_block.append(line)
- results.append(self.convert_leading_whitespace_for_block(current_block))
- return "\n".join(results)
-
- def convert_leading_whitespace_for_block(self, block):
- whitespaceMatcher = re.compile("^\s*")
- initialWhitespace = whitespaceMatcher.match(block[0]).end()
- result = block[0]
- for line in block[1:]:
- result += '\\n' # not a line break...the literal characters \n
- result += line[initialWhitespace:]
- return result
+ return [nodes.raw('', TEMPLATE % self.options, format='html')]
diff --git a/runestone/usageAssignment/README.md b/runestone/usageAssignment/README.md
index 80c01febe..973dba2e0 100644
--- a/runestone/usageAssignment/README.md
+++ b/runestone/usageAssignment/README.md
@@ -5,14 +5,14 @@ Currently generates no HTML, just creates an assignment object, with deadlines,
.. usageassignment:: prep_1
:chapter: chap_name1[, chapname2]*
- :subchapter: subchapter_name[, subchaptername2]*
+ :subchapters: subchapter_name[, subchaptername2]*
:session_name:
:deadline:
:pct_required:
* :chapter: is a comma-separated list of chapter names
-* :subchapter: is a comma-separated list of chapter/subchapter paths
+* :subchapters: is a comma-separated list of chapter/subchapter paths
* :session_name: is the name that will be displayed to students on the progress page
* :deadline: is in the format 2015-10-21 16:30:00
* :pct_required: is a integer from 0-100 indicating what percentage of the activities need to be completed in order to get credit
diff --git a/runestone/usageAssignment/__init__.py b/runestone/usageAssignment/__init__.py
index 22aeccb47..0d03672c4 100644
--- a/runestone/usageAssignment/__init__.py
+++ b/runestone/usageAssignment/__init__.py
@@ -113,7 +113,7 @@ def run(self):
"""
.. usageassignment:: prep_1
:chapters: chap_name1[, chapname2]*
- :subchapter: subchapter_name[, subchaptername2]*
+ :subchapters: subchapter_name[, subchaptername2]*
:assignment_name:
:assignment_type:
:deadline:
@@ -163,11 +163,12 @@ def run(self):
results = session.query(SubChapter).filter(SubChapter.c.chapter_id == str(ch.id)).all()
sub_chs += results
# Add any explicit subchapters
- if 'sub_chapter' in self.options:
- for nm in self.options.get('sub_chapters').split(','):
+ if 'subchapters' in self.options:
+ for nm in self.options.get('subchapters').split(','):
(ch_dir, subch_name) = nm.strip().split('/')
- ch_id = session.query(Chapter).filter(Chapter.c.course_id == course_id, Chapter.chapter_label == ch_dir).first()
- sub_chs += session.query(SubChapter).filter(SubChapter.c.chapter_id == ch_id, SubChapter.c.chapter_label == subch).first()
+ ch_id = session.query(Chapter).filter(Chapter.c.course_id == course_name, Chapter.c.chapter_label == ch_dir).first().id
+ subch = session.query(SubChapter).filter(SubChapter.c.chapter_id == ch_id, SubChapter.c.sub_chapter_label == subch_name).first()
+ sub_chs.append(subch)
# Accumulate all the ActiveCodes that are to be run and URL paths to be visited
divs = []
diff --git a/setup.py b/setup.py
index ae9d539cc..d4be913a6 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
setup(
name='runestone',
description='Sphinx extensions for writing interactive documents.',
- version='2.1.3',
+ version='2.1.6',
author = 'Brad Miller',
author_email = 'bonelake@mac.com',
packages= find_packages(),
@@ -17,7 +17,7 @@
package_data = { '' : ['js/*.js', 'css/*.css', '*.txt']},
license='GPL',
url = 'https://github.com/RunestoneInteractive/RunestoneTools',
- download_url = 'https://github.com/RunestoneInteractive/RunestoneTools/tarball/2.0a6',
+ download_url = 'https://github.com/RunestoneInteractive/RunestoneTools/tarball/2.1.6',
keywords = ['runestone', 'sphinx', 'ebook'], # arbitrary keywords
classifiers=('Development Status :: 5 - Production/Stable',
'Environment :: Console',