summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/codemirror/panel.js112
-rw-r--r--js/codeq/comms.js8
-rw-r--r--js/codeq/core.js4
-rw-r--r--js/codeq/editor.js30
-rw-r--r--js/codeq/hint.js23
-rw-r--r--js/codeq/language.js56
-rw-r--r--js/codeq/login.js7
-rw-r--r--js/codeq/navigation.js15
-rw-r--r--js/codeq/problem.js193
-rw-r--r--js/codeq/prolog.js721
-rw-r--r--js/codeq/python.js664
-rw-r--r--js/codeq/statusbar.js2
-rw-r--r--js/codeq/translation.js7
13 files changed, 1088 insertions, 754 deletions
diff --git a/js/codemirror/panel.js b/js/codemirror/panel.js
new file mode 100644
index 0000000..ba29484
--- /dev/null
+++ b/js/codemirror/panel.js
@@ -0,0 +1,112 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ CodeMirror.defineExtension("addPanel", function(node, options) {
+ options = options || {};
+
+ if (!this.state.panels) initPanels(this);
+
+ var info = this.state.panels;
+ var wrapper = info.wrapper;
+ var cmWrapper = this.getWrapperElement();
+
+ if (options.after instanceof Panel && !options.after.cleared) {
+ wrapper.insertBefore(node, options.before.node.nextSibling);
+ } else if (options.before instanceof Panel && !options.before.cleared) {
+ wrapper.insertBefore(node, options.before.node);
+ } else if (options.replace instanceof Panel && !options.replace.cleared) {
+ wrapper.insertBefore(node, options.replace.node);
+ options.replace.clear();
+ } else if (options.position == "bottom") {
+ wrapper.appendChild(node);
+ } else if (options.position == "before-bottom") {
+ wrapper.insertBefore(node, cmWrapper.nextSibling);
+ } else if (options.position == "after-top") {
+ wrapper.insertBefore(node, cmWrapper);
+ } else {
+ wrapper.insertBefore(node, wrapper.firstChild);
+ }
+
+ var height = (options && options.height) || node.offsetHeight;
+ this._setSize(null, info.heightLeft -= height);
+ info.panels++;
+ return new Panel(this, node, options, height);
+ });
+
+ function Panel(cm, node, options, height) {
+ this.cm = cm;
+ this.node = node;
+ this.options = options;
+ this.height = height;
+ this.cleared = false;
+ }
+
+ Panel.prototype.clear = function() {
+ if (this.cleared) return;
+ this.cleared = true;
+ var info = this.cm.state.panels;
+ this.cm._setSize(null, info.heightLeft += this.height);
+ info.wrapper.removeChild(this.node);
+ if (--info.panels == 0) removePanels(this.cm);
+ };
+
+ Panel.prototype.changed = function(height) {
+ var newHeight = height == null ? this.node.offsetHeight : height;
+ var info = this.cm.state.panels;
+ this.cm._setSize(null, info.height += (newHeight - this.height));
+ this.height = newHeight;
+ };
+
+ function initPanels(cm) {
+ var wrap = cm.getWrapperElement();
+ var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
+ var height = parseInt(style.height);
+ var info = cm.state.panels = {
+ setHeight: wrap.style.height,
+ heightLeft: height,
+ panels: 0,
+ wrapper: document.createElement("div")
+ };
+ wrap.parentNode.insertBefore(info.wrapper, wrap);
+ var hasFocus = cm.hasFocus();
+ info.wrapper.appendChild(wrap);
+ if (hasFocus) cm.focus();
+
+ cm._setSize = cm.setSize;
+ if (height != null) cm.setSize = function(width, newHeight) {
+ if (newHeight == null) return this._setSize(width, newHeight);
+ info.setHeight = newHeight;
+ if (typeof newHeight != "number") {
+ var px = /^(\d+\.?\d*)px$/.exec(newHeight);
+ if (px) {
+ newHeight = Number(px[1]);
+ } else {
+ info.wrapper.style.height = newHeight;
+ newHeight = info.wrapper.offsetHeight;
+ info.wrapper.style.height = "";
+ }
+ }
+ cm._setSize(width, info.heightLeft += (newHeight - height));
+ height = newHeight;
+ };
+ }
+
+ function removePanels(cm) {
+ var info = cm.state.panels;
+ cm.state.panels = null;
+
+ var wrap = cm.getWrapperElement();
+ info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
+ wrap.style.height = info.setHeight;
+ cm.setSize = cm._setSize;
+ cm.setSize();
+ }
+});
diff --git a/js/codeq/comms.js b/js/codeq/comms.js
index 2d934bd..5ac3d66 100644
--- a/js/codeq/comms.js
+++ b/js/codeq/comms.js
@@ -164,8 +164,8 @@
// AJAX communication support functions (loading of static web resources)
// ================================================================================
- var languageCache = {},// language defs, keyed by language identifier
- problemCache = {},// problem cache, 3-level, keyed by: language, problem group, and problem identifier
+ var languageCache = {}, // language defs, keyed by language identifier
+ problemCache = {}, // problem cache, 3-level, keyed by: language, problem group, and problem identifier
ajaxGet = function (url) {
return Q.Promise(function (resolve, reject, notify) {
$.ajax({
@@ -407,6 +407,10 @@
);
languageCache[identifier] = x;
return x;
+ },
+
+ 'getProblemDef': function (language, group, problem) {
+ return ajaxGet(ajaxPrefix + language + '/' + group + '/' + problem + '/problem.json');
}
};
})();
diff --git a/js/codeq/core.js b/js/codeq/core.js
index ef3648f..01382d1 100644
--- a/js/codeq/core.js
+++ b/js/codeq/core.js
@@ -391,5 +391,7 @@
codeq.setLang(lang || 'en'); // initial language setting
// go to login
codeq.globalStateMachine.transition('login');
- });
+
+ //For performance reasons, the Tooltip and Popover data-apis are opt-in, meaning you must initialize them yourself.
+ $('[data-toggle="popover"]').popover()});
})();
diff --git a/js/codeq/editor.js b/js/codeq/editor.js
new file mode 100644
index 0000000..e7d15f9
--- /dev/null
+++ b/js/codeq/editor.js
@@ -0,0 +1,30 @@
+codeq.makeEditor = function (elt, options) {
+ var statusBar = document.createElement("div"),
+ updateStatusBar = function (pos) {
+ statusBar.innerHTML = 'line ' + (pos.line+1) + ', column ' + (pos.ch+1);
+ },
+ editor;
+
+ options.cursorHeight = 0.85;
+ options.lineNumbers = true;
+ options.matchBrackets = true;
+ options.extraKeys = {
+ // replace tabs with spaces
+ Tab: function (cm) {
+ var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
+ cm.replaceSelection(spaces);
+ }
+ };
+ editor = CodeMirror(elt, options),
+
+ statusBar.className = 'editor-statusbar';
+ updateStatusBar({line: 0, ch: 0});
+
+ editor.addPanel(statusBar, {position: 'bottom'});
+ editor.on('cursorActivity', function (instance) {
+ var pos = instance.getDoc().getCursor();
+ updateStatusBar(pos);
+ });
+
+ return editor;
+};
diff --git a/js/codeq/hint.js b/js/codeq/hint.js
index e1c7a6b..1b97afc 100644
--- a/js/codeq/hint.js
+++ b/js/codeq/hint.js
@@ -7,10 +7,11 @@
var firstCharacterPos = {'line': 0, 'ch': 0},
sel_no_scroll = {'scroll': false};
- codeq.makeHinter = function (jqHints, jqEditor, editor, hintDefs, planDef) {
+ codeq.makeHinter = function (jqHints, jqEditor, editor, trNamespace, hintDefsA, commonHintDefsA, planDef) {
var hintCounter = 0, // for generating unique class-names
hintCleaners = [],
planIdx = 0,
+ dictionary = [],
clearHints = function () {
var i;
@@ -130,6 +131,25 @@
}
};
+ codeq.tr.registerDictionary(trNamespace, dictionary);
+
+ // TODO: below is a temporary code to bridge the old implementation with the new data format
+ if (planDef.sl) planDef = planDef.sl;
+ else planDef = planDef.en || [];
+ var hintDefs = {}, t1, t2, k;
+ if (hintDefsA.sl) t1 = hintDefsA.sl;
+ else t1 = hintDefsA.en || {};
+ if (commonHintDefsA.sl) t2 = commonHintDefsA.sl;
+ else t2 = commonHintDefsA.en || {};
+ for (k in t2) {
+ if (!t2.hasOwnProperty(k)) continue;
+ hintDefs[k] = t2[k];
+ }
+ for (k in t1) {
+ if (!t1.hasOwnProperty(k)) continue;
+ hintDefs[k] = t1[k];
+ }
+
return {
/** Display the next "planning" hint and return whether there are
* any more available.
@@ -195,6 +215,7 @@
'destroy': function () {
clearHints();
+ codeq.tr.unregisterDictionary(trNamespace);
jqHints.empty();
jqHints = null;
jqEditor = null;
diff --git a/js/codeq/language.js b/js/codeq/language.js
index be2a319..aac4e78 100644
--- a/js/codeq/language.js
+++ b/js/codeq/language.js
@@ -1,30 +1,28 @@
-/**
- * Created by robert on 9/18/15.
- */
-
-(function(){
- var jqScreen = $('#screen_language'),
- jqProlog = $('#choose-prolog'),
- jqPython = $('#choose-python'),
- chooseProlog = function () {codeq.globalStateMachine.transition('problem', 'prolog');},
- choosePython = function () {codeq.globalStateMachine.transition('problem', 'python');};
-
- codeq.globalStateMachine.register('language',{
- 'enter': function(){
- $('#navigation-login').css('display', '');
- $("#navigation-language").addClass("active").css('display', '');
-
- jqScreen.css('display', '');
- jqProlog.on('click', chooseProlog);
- jqPython.on('click', choosePython);
- },
- 'exit' : function(){
- jqProlog.off();
- jqPython.off();
- jqScreen.css('display', 'none');
-
- $('#navigation-login').css('display', 'none');
- $('#navigation-language').css('display', 'none').removeClass("active");
- }
- });
+/**
+ * Created by robert on 9/18/15.
+ */
+
+(function(){
+ var jqScreen = $('#screen_language'),
+ jqProlog = $('#choose-prolog'),
+ jqPython = $('#choose-python'),
+ chooseProlog = function () {codeq.globalStateMachine.transition('problem', 'prolog');},
+ choosePython = function () {codeq.globalStateMachine.transition('problem', 'python');};
+
+ codeq.globalStateMachine.register('language',{
+ 'enter': function(){
+ $("#navigation-language").addClass("active").css('display', '');
+
+ jqScreen.css('display', '');
+ jqProlog.on('click', chooseProlog);
+ jqPython.on('click', choosePython);
+ },
+ 'exit' : function(){
+ jqProlog.off();
+ jqPython.off();
+ jqScreen.css('display', 'none');
+
+ $('#navigation-language').css('display', 'none').removeClass("active");
+ }
+ });
})(); \ No newline at end of file
diff --git a/js/codeq/login.js b/js/codeq/login.js
index 02a6517..e3204d3 100644
--- a/js/codeq/login.js
+++ b/js/codeq/login.js
@@ -37,9 +37,7 @@
codeq.globalStateMachine.register('login',{
'enter': function(){
$("#submit").on('click', loginFun);
-
- $("#navigation-login").addClass("active");
- $('#navigation-login').css('display', '');
+ //$('#modalLogin').modal();
$("#screen_login").css('display', '');
$('#disabled').css('display', 'none');
@@ -48,9 +46,6 @@
$("#submit").off('click', loginFun);
$("#screen_login").css('display', 'none');
$("#password").val('');
-
- $('#navigation-login').css('display', 'none');
- $("#navigation-login").removeClass("active");
}
});
})(); \ No newline at end of file
diff --git a/js/codeq/navigation.js b/js/codeq/navigation.js
index 4445a3d..80fd321 100644
--- a/js/codeq/navigation.js
+++ b/js/codeq/navigation.js
@@ -57,7 +57,7 @@
History.pushState({'state': name, 'params': Array.prototype.slice.apply(arguments, [1])}, null, '?s=' + name);
}
catch (e) {
- codeq.log.error('init: History.pushState() failed for new state ' + name+'. Error:'+e);
+ codeq.log.error('init: History.pushState() failed for new state ' + name+'. Error:'+e, e);
}
},
'destroy': function () {
@@ -81,10 +81,6 @@
//setup all the buttons in the banner
- $('#navigation-login').on('click', function(e){
- codeq.globalStateMachine.transition('login');
- e.preventDefault();//prevent this since we'll trigger a page reload otherwise
- });
$('#navigation-language').on('click', function(e){
codeq.globalStateMachine.transition('language');
e.preventDefault();
@@ -101,4 +97,13 @@
codeq.globalStateMachine.transition('prolog');
e.preventDefault();
});
+ $('#navigation-logout').on('click', function(e){
+ codeq.globalStateMachine.transition('login');
+ e.preventDefault();//prevent this since we'll trigger a page reload otherwise
+ });
+ $('#navigation-profile').on('click', function(e){
+ codeq.globalStateMachine.transition('profile');
+ e.preventDefault();//prevent this since we'll trigger a page reload otherwise
+ });
+
})(); \ No newline at end of file
diff --git a/js/codeq/problem.js b/js/codeq/problem.js
index 3e218d8..7ae54d8 100644
--- a/js/codeq/problem.js
+++ b/js/codeq/problem.js
@@ -2,8 +2,93 @@
var jqScreen = $('#screen_problem'),
languageCache = {}, // keyed by language identifier: processed data about languages
translationCache = [], // keys are autogenerated in ta(), a value is a dictionary of translations of a translation key for every language
+ problemCache = {}, // problem data cache, 3-level, keyed by: language, problem group, and problem identifier
langs, Nlangs, // constants, set on init
+ // ================================================================================
+ // Hint processing: extract hints from the translations and return them in the
+ // processed form, hint key -> translation language -> value
+ // ================================================================================
+
+ processHints = function (rawTranslations) {
+ var defaultHint = {}, // here we put all the hints with their default translations
+ allHints = {}, // the result
+ allHintKeys = [],
+ tr, key, i, lang, hint, h, j;
+ // find the default hint translations, they will form the basis of default hints
+ tr = chooseDefaultTranslation(rawTranslations, 'hint') || {};
+ for (key in tr) { // copy the hints
+ if (!tr.hasOwnProperty(key)) continue;
+ defaultHint[key] = tr[key];
+ allHintKeys.push(key);
+ }
+ // copy any hints not in the default hints to the default hints
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ tr = rawTranslations[lang];
+ if (!tr || !tr.hint) continue; // skip unavailable translations or translations with no hints
+ hint = tr.hint;
+ for (key in hint) {
+ if (!hint.hasOwnProperty(key) || !hint[key]) continue;
+ if (!key in defaultHint) {
+ defaultHint[key] = hint[key];
+ allHintKeys.push(key);
+ }
+ }
+ }
+ // create all translations for hints
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ tr = rawTranslations[lang];
+ // set up hints
+ if (!tr || !tr.hint) {
+ // there's no hint in the current language, copy the default in its entirety
+ allHints[lang] = defaultHint;
+ }
+ else {
+ // make a copy of all hints, using the default hint value where a hint value is missing
+ hint = {};
+ allHints[lang] = hint;
+ h = tr.hint;
+ for (j = allHintKeys.length; j >= 0; j--) {
+ key = allHintKeys[j];
+ hint[key] = h[key] || defaultHint[key];
+ }
+ }
+ }
+ return allHints;
+ },
+
+ // ================================================================================
+ // Plan processing: extract plans from the translations and return them in the
+ // processed form, hint key -> translation language -> value
+ // ================================================================================
+
+ processPlans = function (rawTranslations) {
+ // find the default plan translation
+ var defaultPlan = chooseDefaultTranslation(rawTranslations, 'plan') || [],
+ allPlans = {}, // the result
+ i, lang, tr;
+ // create all translations for plan
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ tr = rawTranslations[lang];
+ // set up plan
+ if (!tr || !tr.plan) {
+ // there's no plan in the current language, copy the default plan
+ allPlans[lang] = defaultPlan;
+ }
+ else {
+ allPlans[lang] = tr.plan;
+ }
+ }
+ return allPlans;
+ },
+
+ // ================================================================================
+ // Group + problems directory processing
+ // ================================================================================
+
chooseTranslation = function (keyword, lang, currentDict, enDict, translations) {
var tr = currentDict[keyword],
otherLang;
@@ -65,50 +150,56 @@
*/
createLanguageData = function (data, languageIdentifier) { // data is the content of language.json
var li = languageIdentifier, // a shorthand
- groups = data.groups || {},
+ rawTranslations = data.translations || {},
+ groups = data.groups || [],
+ Ngroups = groups.length,
html = [],
problemReferences = [],
- groupIdentifier, group, problems, problemIdentifier, problem;
- var langDict = convertTranslations(data.translations, 'name', 'description'), // this will be the resulting dictionary: multi-level keys that lead up to the lang-dict
+ group, problems, Nproblems, problem, i, j;
+ var langDict = convertTranslations(rawTranslations, 'name', 'description'), // this will be the resulting dictionary: multi-level keys that lead up to the lang-dict
groupDict, problemDict;
// title: HTML structure for "name" and "desc"
html.push('<h1 class="language-title translatable" ', ta(langDict.name), '></h1><hr>');
html.push('<div class="language-description translatable" ', ta(langDict.description), '></div>');
// content: problem directory
html.push('<ul class="language-problems">');
- for (groupIdentifier in groups) {
- if (!groups.hasOwnProperty(groupIdentifier)) continue;
- group = groups[groupIdentifier] || {};
+ for (i = 0; i < Ngroups; i++) {
+ group = groups[i] || {};
groupDict = convertTranslations(group.translations, 'name', 'description'); // the group-level translations, added will
// group content
html.push('<li><div class="group-title translatable" ', ta(groupDict.name), '></div>');
html.push('<div class="group-description translatable" ', ta(groupDict.description), '></div>');
html.push('<ul class="group-problems">');
// problem content
- problems = group.problems || {};
- for (problemIdentifier in problems) {
- if (!problems.hasOwnProperty(problemIdentifier)) continue;
- problem = problems[problemIdentifier] || {};
+ problems = group.problems || [];
+ Nproblems = problems.length;
+ for (j = 0; j < Nproblems; j++) {
+ problem = problems[j] || {};
problemDict = convertTranslations(problem.translations, 'name');
html.push('<li><a class="problem-', '' + problemReferences.length, ' translatable" ', ta(problemDict.name), '></a></li>');
- problemReferences.push({'g': groupIdentifier, 'p': problemIdentifier});
+ problemReferences.push({'g': group.identifier || 'nogroup', 'p': problem.identifier || 'noproblem', 'id': problem.id});
}
html.push('</ul></li>');
}
html.push('</ul>');
return {
- 'language': languageIdentifier,
- 'html': html.join(''),
- 'refs': problemReferences,
- 'hints': {} // TODO: prepare common hints for the language
+ 'language': languageIdentifier, // 'prolog', 'python', ...
+ 'html': html.join(''), // the DOM structure (without textual content), as HTML text
+ 'refs': problemReferences, // array of problem info {g: group, p: problem, id: problem_id}, referenced from DOM <a> elements
+ 'hints': processHints(rawTranslations) // hint translations: keyword -> lang -> value
};
},
+ // ================================================================================
+ // DOM instantiation.
+ // The transition to problem solving takes place here, after the user clicks on a
+ // problem and all the required data is loaded.
+ // ================================================================================
/**
* Instantiates the screen from the given processed data.
*/
- createDom = function (data) {
+ createDom = function (data) { // data is the (cached) result of createLanguageData()
var language = data.language;
jqScreen.html(data.html);
codeq.tr.translateDom(jqScreen);
@@ -119,12 +210,15 @@
codeq.log.error('Clicked on a problem link having erroneous index: ' + index);
return;
}
- // transition
codeq.wait(
- codeq.comms.getProblem(language, ref.g, ref.p)
- .then(function (data) {
- if (data.code !== 0) throw new Error('Failed to obtain problem data, code: ' + data.code + ', message: ' + data.message);
- codeq.globalStateMachine.transition(language, data);
+ Q.all([
+ codeq.comms.getProblem(language, ref.g, ref.p), // TODO: use ref.id instead // the current solution
+ getProblemData(language, ref.g, ref.p) // the (cached) result of processProblemData()
+ ])
+ .spread(function (userProblemData, generalProblemData) {
+ if (userProblemData.code !== 0) throw new Error('Failed to obtain user problem data, code: ' + userProblemData.code + ', message: ' + userProblemData.message);
+ if (!generalProblemData) throw new Error('General problem data is not defined');
+ codeq.globalStateMachine.transition(language, generalProblemData, data.hints, userProblemData.solution);
})
)
.fail(function (reason) {
@@ -134,6 +228,61 @@
.done();
});
},
+
+ // ================================================================================
+ // Problem definition processing
+ // ================================================================================
+
+ chooseDefaultTranslation = function (rawTranslations, translationKey) {
+ var tr = rawTranslations.en, // try English as the default
+ lang;
+ if (tr && tr[translationKey]) return tr[translationKey];
+ for (lang in rawTranslations) { // find a translation with hints
+ if (!rawTranslations.hasOwnProperty(lang) || rawTranslations[lang]) continue;
+ tr = rawTranslations[lang];
+ if (tr[translationKey]) return tr[translationKey];
+ }
+ return null; // default must be chosen by the caller
+ },
+
+ processProblemData = function (rawData, language, group, problem) {
+ var rawTranslations = rawData.translations || {};
+ return {
+ 'language': language,
+ 'group': group,
+ 'problem': problem,
+ 'id': rawData.id,
+ 'translations': convertTranslations(rawTranslations, 'title', 'name', 'slug', 'description'), // GUI translations: keyword -> lang -> value
+ 'hint': processHints(rawTranslations), // hint translations: keyword -> lang -> value
+ 'plan': processPlans(rawTranslations) // plan translations: keyword -> lang -> value
+ };
+ },
+
+ getProblemData = function (language, group, problem) {
+ var langCache = problemCache[language],
+ groupCache, cachedProblem, promise;
+ if (langCache) {
+ groupCache = langCache[group];
+ if (!groupCache) {
+ groupCache = {};
+ langCache[group] = groupCache;
+ }
+ }
+ else {
+ langCache = {};
+ problemCache[language] = langCache;
+ groupCache = {};
+ langCache[group] = groupCache;
+ }
+ cachedProblem = groupCache[problem];
+ if (cachedProblem) return Q(cachedProblem);
+ return codeq.comms.getProblemDef(language, group, problem).then(function (rawData) {
+ var data = processProblemData(rawData);
+ groupCache[problem] = data;
+ return data;
+ });
+ },
+
currentLanguage; // the currently active language
// ================================================================================
@@ -154,7 +303,6 @@
'enter': function(language){
var data = null; // language data
- $('#navigation-login').css('display', '');
$('#navigation-language').css('display', '');
$("#navigation-problem").addClass("active").css('display', '');
@@ -180,7 +328,6 @@
},
'exit' : function(){
jqScreen.css('display', 'none');
- $('#navigation-login').css('display', 'none');
$('#navigation-language').css('display', 'none');
$('#navigation-problem').css('display', 'none').removeClass("active");
}
diff --git a/js/codeq/prolog.js b/js/codeq/prolog.js
index be6b0c8..87ac353 100644
--- a/js/codeq/prolog.js
+++ b/js/codeq/prolog.js
@@ -1,357 +1,364 @@
-/**
- * Created by robert on 9/17/15.
- *
- * The prolog state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen.
- */
-
-(function() {
- var subScreens, //this will be the actual (sub)state machine
- stateNameTag = 'stateName', //a tag for data which is added to some html elements
- jqScreen = $('#screen_prolog'), // the screen container element
- //quadrants
- jqDescription = jqScreen.find('.block1'),
- jqCode = jqScreen.find('.block2'),
- jqConsole = jqScreen.find('.block3'),
- jqInfo = jqScreen.find('.block4'),
- jqAllQuadrants = jqDescription.add(jqCode).add(jqConsole).add(jqInfo), // all the quadrants
- // buttons
- jqBtnPlan = jqScreen.find('.btn-plan'),
- jqBtnHint = jqScreen.find('.btn-hint'),
- jqBtnTest = jqScreen.find('.btn-test'),
- jqAllButtons = jqBtnPlan.add(jqBtnHint).add(jqBtnTest), // all the buttons
- // misc
- currentSubState = null,
- transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below)
- substates = {
- 'description': {
- 'enter': function () {
- currentSubState = 'block1';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- },
- 'code': {
- 'enter': function () {
- currentSubState = 'block2';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- },
- 'info': {
- 'enter': function () {
- currentSubState = 'block4';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- },
- 'console': {
- 'enter': function () {
- currentSubState = 'block3';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- }
- };
- var prologHandler; //created when we enter the prolog state and destroyed once we leave it
- codeq.globalStateMachine.register('prolog', {
- 'enter': function (data) {
- $('#navigation-login').css('display', '');
- $('#navigation-language').css('display', '');
- $('#navigation-problem').css('display', '');
- $("#navigation-prolog").addClass("active");
- $('#navigation-prolog').css('display', '');
-
- jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly
- prologHandler = createPrologHandler(data.data);
- subScreens = codeq.makeStateMachine(substates);
- subScreens.transition(jqDescription.data(stateNameTag));
-/* Q.delay(100).then(function(){
- jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading
- }).done();*/
- jqAllButtons.on(transitionEventName, function (event) {
- subScreens.transition('info'); // set focus on the hints quadrant
- event.stopPropagation(); // don't allow the event to go on and trigger further transition
- });
- jqAllQuadrants.on(transitionEventName, function () {
- subScreens.transition($(this).data(stateNameTag));
- });
- },
- 'exit': function () {
- jqAllButtons.off(); // unregister all event handlers
- jqAllQuadrants.off();
- jqScreen.css('display', 'none');
-// jqAllQuadrants.removeClass('transition');
- prologHandler.destroy();
- prologHandler = null;
- subScreens.destroy();
- subScreens = null;
- jqScreen.addClass('block1');
-
- $('#navigation-login').css('display', 'none');
- $('#navigation-language').css('display', 'none');
- $('#navigation-problem').css('display', 'none');
- $("#navigation-prolog").removeClass("active");
- $('#navigation-prolog').css('display', 'none');
- }
- });
-
- jqDescription.data(stateNameTag, 'description');
- jqCode.data(stateNameTag, 'code');
- jqConsole.data(stateNameTag, 'console');
- jqInfo.data(stateNameTag, 'info');
-
- // a constant
- var firstCharacterPos = {'line': 0, 'ch': 0};
-
- var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
- var promptMode = true, // default: query composition; alternative: query result browsing
- manualStop = false,// if the user stopped showing next answers (false) or if there are no more answers (true)
- terminal = codeq.makeConsole(jqConsole, {
- 'greeting': 'CodeQ Prolog terminal proxy',
- 'autoHistory': true
- }),
- tcs = function terminalCommandSuccess (data) {
- var t, lines, i;
- if (data.code === 0) {
- t = data.terminal;
- terminal.append(t.messages.join('\n'), 'output');
- promptMode = !t.have_more;
- }
- else {
- terminal.append(data.message, 'error');
- promptMode = true;
- }
- if (promptMode) {
- terminal.setLineBuffered();
- terminal.append(manualStop ? '?- ' : '.\n?- ', 'output');
- }
- },
- tcf = function terminalCommandFailed (error) {
- promptMode = true;
- terminal.setLineBuffered();
- terminal.append(error + '\n', 'error');
- terminal.append('?- ', 'output');
- };
-
- terminal.onKeypress = function (c) {
- if (promptMode) return c; // query composition: return the character unchanged
- switch (c) {
- case ' ':
- case ';':
- case 'n':
- case 'r':
- return ';'; // show next answer on space, semicolon, 'n' or 'r'
- default:
- return '.'; // everything else: stop searching for answers
- }
- };
-
- terminal.onInput = function (command) {
- if (promptMode) {
- promptMode = false;
- manualStop = false;
- terminal.setNotBuffered();
- return codeq.comms.sendQuery({
- 'problem_id': problem_id,
- 'step': 'run',
- 'program': editor.getDoc().getValue(),
- 'query': command,
- 'trace': activityHandler.addAndPurge({'typ': 'slv', 'qry': command})
- }, problem_id).then(tcs, tcf);
- }
- else {
- terminal.append('\n', 'input');
- if (command == ';') {
- // show next answer
- return codeq.comms.sendQuery({
- 'problem_id': problem_id,
- 'step': 'next',
- 'trace': activityHandler.addAndPurge({'typ': 'nxt'})
- }, problem_id).then(tcs, tcf);
- }
- else {
- // stop searching for answers
- manualStop = true;
- return codeq.comms.sendQuery({
- 'problem_id': problem_id,
- 'step': 'end',
- 'trace': activityHandler.addAndPurge({'typ': 'stp'})
- }, problem_id).then(tcs, tcf);
- }
-
- }
- };
-
- terminal.leftmostCol = 3;
- terminal.append('?- ', 'output');
-
- return terminal;
- };
-
- var makeActivityHandler = function (editor, problem_id) {
- var lastActivityMillis = Date.now(),
- deltaActivityMillis = function deltaActivityMillisFunc () {
- var now = Date.now(),
- dt = now - lastActivityMillis;
- lastActivityMillis = now;
- return dt;
- },
- queue = [],
- ts = null,
- timer = function () {
- var promise;
- ts = null;
- if (queue.length === 0) return Q(true);
- promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id);
- queue.length = 0;
- return promise;
- },
- flush = function () {
- clearTimeout(ts);
- return timer();
- };
-
- return {
- 'queueTrace': function (trace) {
- trace['dt'] = deltaActivityMillis();
- queue.push(trace);
- if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds
- return this;
- },
- 'flush': flush,
- 'addAndPurge': function (trace) {
- var accumulatedTrace = queue;
- queue = [];
- trace['dt'] = deltaActivityMillis();
- accumulatedTrace.push(trace);
- if (ts !== null) {
- clearTimeout(ts);
- ts = null;
- }
- return accumulatedTrace;
- }
- };
- };
-
- /**
- * Creates a new handler for the given Prolog assignment definition.
- *
- * @param {PrologTaskDef} info
- * @returns {{destroy: Function, processServerHints: Function}}
- */
- createPrologHandler = function (info) {
- var problem = info.problem,
- jqDescriptionContent = jqDescription.find('.description'),
- jqEditor = jqCode.find('.code_editor'),
- jqTerminal = jqConsole.find('.console'),
- jqHints = jqInfo.find('.hints'),
- editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true }),
- activityHandler = makeActivityHandler(editor, problem.id),
- terminal = makePrologTerminalHandler(jqTerminal, editor, problem.id, activityHandler),
- hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint, problem.plan),
- commError = function (error) {
- alert(error);
- };
-
- editor.setValue(info.solution);
- $('#screen_prolog .title').text(problem.slug);
- jqDescriptionContent.html(problem.description);
- jqBtnPlan.prop('disabled', (problem.plan || '').length == 0);
-
- editor.on('change', function (instance, changeObj) {
- var doc = editor.getDoc(),
- pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from));
-
- if (changeObj.removed) {
- activityHandler.queueTrace({'typ': 'r', 'off': pos, 'len': codeq.codePointCount(changeObj.removed.join(''))});
- }
-
- if (changeObj.text) {
- activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text});
- }
- });
-
- jqBtnPlan.on('click', function () {
- if (!hinter.planNext()) {
- jqBtnPlan.prop('disabled', true).blur();
- }
- });
- jqBtnHint.on('click', function () {
- terminal.append('hint.\n', 'input');
- terminal.inputDisable();
- var doc = editor.getDoc();
- codeq.comms.sendHint({
- 'language': 'prolog',
- 'program': editor.getDoc().getValue(),
- 'problem_id': problem.id
- })
- .then(function (data) {
- if (data.code === 0) {
- hinter.handle(data.hints);
- }
- else {
- terminal.append(data.message + '\n', 'error');
- }
- })
- .fail(commError)
- .fin(function () {
- terminal.inputEnable();
- terminal.append('?- ', 'output');
- })
- .done();
- });
- jqBtnTest.on('click', function () {
- terminal.append('test.\n', 'input');
- terminal.inputDisable();
- var doc = editor.getDoc();
- codeq.comms.sendTest({
- 'language': 'prolog',
- 'program': editor.getDoc().getValue(),
- 'problem_id': problem.id
- })
- .then(function (data) {
- if (data.code === 0) {
- hinter.handle(data.hints);
- }
- else {
- terminal.append(data.message + '\n', 'error');
- }
- })
- .fail(commError)
- .fin(function () {
- terminal.inputEnable();
- terminal.append('?- ', 'output');
- })
- .done();
- });
-
- return {
- destroy: function () {
- $('#screen_prolog .title').text('');//empty the title text
- jqAllButtons.off();
- editor.off('change');
- hinter.destroy();
- terminal.destroy();
- jqDescriptionContent.empty();
- jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it
- jqTerminal.empty(); // TODO: the same with the console
- jqDescriptionContent = null;
- jqEditor = null;
- jqTerminal = null;
- jqHints = null;
- }
- };
- };
-})();
+/**
+ * Created by robert on 9/17/15.
+ *
+ * The prolog state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen.
+ */
+
+(function() {
+ var subScreens, //this will be the actual (sub)state machine
+ stateNameTag = 'stateName', //a tag for data which is added to some html elements
+ jqScreen = $('#screen_prolog'), // the screen container element
+ //quadrants
+ jqDescription = jqScreen.find('.block1'),
+ jqCode = jqScreen.find('.block2'),
+ jqConsole = jqScreen.find('.block3'),
+ jqInfo = jqScreen.find('.block4'),
+ jqAllQuadrants = jqDescription.add(jqCode).add(jqConsole).add(jqInfo), // all the quadrants
+ // buttons
+ jqBtnPlan = jqScreen.find('.btn-plan'),
+ jqBtnHint = jqScreen.find('.btn-hint'),
+ jqBtnTest = jqScreen.find('.btn-test'),
+ jqAllButtons = jqBtnPlan.add(jqBtnHint).add(jqBtnTest), // all the buttons
+ // misc
+ currentSubState = null,
+ transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below)
+ substates = {
+ 'description': {
+ 'enter': function () {
+ currentSubState = 'block1';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ },
+ 'code': {
+ 'enter': function () {
+ currentSubState = 'block2';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ },
+ 'info': {
+ 'enter': function () {
+ currentSubState = 'block4';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ },
+ 'console': {
+ 'enter': function () {
+ currentSubState = 'block3';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ }
+ };
+ var prologHandler; //created when we enter the prolog state and destroyed once we leave it
+ codeq.globalStateMachine.register('prolog', {
+ 'enter': function (problemDef, commonHints, currentSolution) {
+ $('#navigation-language').css('display', '');
+ $('#navigation-problem').css('display', '');
+ $("#navigation-prolog").addClass("active");
+ $('#navigation-prolog').css('display', '');
+
+ jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly
+ prologHandler = createPrologHandler(problemDef, commonHints, currentSolution);
+ subScreens = codeq.makeStateMachine(substates);
+ subScreens.transition(jqDescription.data(stateNameTag));
+/* Q.delay(100).then(function(){
+ jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading
+ }).done();*/
+ jqAllButtons.on(transitionEventName, function (event) {
+ subScreens.transition('info'); // set focus on the hints quadrant
+ event.stopPropagation(); // don't allow the event to go on and trigger further transition
+ });
+ jqAllQuadrants.on(transitionEventName, function () {
+ subScreens.transition($(this).data(stateNameTag));
+ });
+ },
+ 'exit': function () {
+ jqAllButtons.off(); // unregister all event handlers
+ jqAllQuadrants.off();
+ jqScreen.css('display', 'none');
+// jqAllQuadrants.removeClass('transition');
+ prologHandler.destroy();
+ prologHandler = null;
+ subScreens.destroy();
+ subScreens = null;
+ jqScreen.addClass('block1');
+
+ $('#navigation-language').css('display', 'none');
+ $('#navigation-problem').css('display', 'none');
+ $("#navigation-prolog").removeClass("active");
+ $('#navigation-prolog').css('display', 'none');
+ }
+ });
+
+ jqDescription.data(stateNameTag, 'description');
+ jqCode.data(stateNameTag, 'code');
+ jqConsole.data(stateNameTag, 'console');
+ jqInfo.data(stateNameTag, 'info');
+
+ // a constant
+ var firstCharacterPos = {'line': 0, 'ch': 0};
+
+ var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
+ var promptMode = true, // default: query composition; alternative: query result browsing
+ manualStop = false,// if the user stopped showing next answers (false) or if there are no more answers (true)
+ terminal = codeq.makeConsole(jqConsole, {
+ 'greeting': 'CodeQ Prolog terminal proxy',
+ 'autoHistory': true
+ }),
+ tcs = function terminalCommandSuccess (data) {
+ var t, lines, i;
+ if (data.code === 0) {
+ t = data.terminal;
+ terminal.append(t.messages.join('\n'), 'output');
+ promptMode = !t.have_more;
+ }
+ else {
+ terminal.append(data.message, 'error');
+ promptMode = true;
+ }
+ if (promptMode) {
+ terminal.setLineBuffered();
+ terminal.append(manualStop ? '?- ' : '.\n?- ', 'output');
+ }
+ },
+ tcf = function terminalCommandFailed (error) {
+ promptMode = true;
+ terminal.setLineBuffered();
+ terminal.append(error + '\n', 'error');
+ terminal.append('?- ', 'output');
+ };
+
+ terminal.onKeypress = function (c) {
+ if (promptMode) return c; // query composition: return the character unchanged
+ switch (c) {
+ case ' ':
+ case ';':
+ case 'n':
+ case 'r':
+ return ';'; // show next answer on space, semicolon, 'n' or 'r'
+ default:
+ return '.'; // everything else: stop searching for answers
+ }
+ };
+
+ terminal.onInput = function (command) {
+ if (promptMode) {
+ promptMode = false;
+ manualStop = false;
+ terminal.setNotBuffered();
+ return codeq.comms.sendQuery({
+ 'problem_id': problem_id,
+ 'step': 'run',
+ 'program': editor.getDoc().getValue(),
+ 'query': command,
+ 'trace': activityHandler.addAndPurge({'typ': 'slv', 'qry': command})
+ }, problem_id).then(tcs, tcf);
+ }
+ else {
+ terminal.append('\n', 'input');
+ if (command == ';') {
+ // show next answer
+ return codeq.comms.sendQuery({
+ 'problem_id': problem_id,
+ 'step': 'next',
+ 'trace': activityHandler.addAndPurge({'typ': 'nxt'})
+ }, problem_id).then(tcs, tcf);
+ }
+ else {
+ // stop searching for answers
+ manualStop = true;
+ return codeq.comms.sendQuery({
+ 'problem_id': problem_id,
+ 'step': 'end',
+ 'trace': activityHandler.addAndPurge({'typ': 'stp'})
+ }, problem_id).then(tcs, tcf);
+ }
+
+ }
+ };
+
+ terminal.leftmostCol = 3;
+ terminal.append('?- ', 'output');
+
+ return terminal;
+ };
+
+ var makeActivityHandler = function (editor, problem_id) {
+ var lastActivityMillis = Date.now(),
+ deltaActivityMillis = function deltaActivityMillisFunc () {
+ var now = Date.now(),
+ dt = now - lastActivityMillis;
+ lastActivityMillis = now;
+ return dt;
+ },
+ queue = [],
+ ts = null,
+ timer = function () {
+ var promise;
+ ts = null;
+ if (queue.length === 0) return Q(true);
+ promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id);
+ queue.length = 0;
+ return promise;
+ },
+ flush = function () {
+ clearTimeout(ts);
+ return timer();
+ };
+
+ return {
+ 'queueTrace': function (trace) {
+ trace['dt'] = deltaActivityMillis();
+ queue.push(trace);
+ if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds
+ return this;
+ },
+ 'flush': flush,
+ 'addAndPurge': function (trace) {
+ var accumulatedTrace = queue;
+ queue = [];
+ trace['dt'] = deltaActivityMillis();
+ accumulatedTrace.push(trace);
+ if (ts !== null) {
+ clearTimeout(ts);
+ ts = null;
+ }
+ return accumulatedTrace;
+ }
+ };
+ };
+
+ codeq.on('init', function (args) {
+ codeq.tr.registerDictionary('prolog', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active
+ });
+
+ /**
+ * Creates a new handler for the given Prolog assignment definition.
+ *
+ * @param {PrologTaskDef} info
+ * @returns {{destroy: Function, processServerHints: Function}}
+ */
+ createPrologHandler = function (problemDef, commonHints, currentSolution) {
+ var //problem = info.problem,
+ jqDescriptionContent = jqDescription.find('.description'),
+ jqEditor = jqCode.find('.code_editor'),
+ jqTerminal = jqConsole.find('.console'),
+ jqHints = jqInfo.find('.hints'),
+ editor = codeq.makeEditor(jqEditor[0], {
+ mode: 'prolog'
+ }),
+ activityHandler = makeActivityHandler(editor, problemDef.id),
+ terminal = makePrologTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler),
+ hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'prolog_hints', problemDef.hint, commonHints, problemDef.plan),
+ commError = function (error) {
+ alert(error);
+ };
+
+ codeq.tr.registerDictionary('prolog', problemDef.translations);
+ codeq.tr.translateDom(jqScreen);
+ if (currentSolution) editor.setValue(currentSolution);
+// $('#screen_prolog .title').text(problem.slug);
+// jqDescriptionContent.html(problem.description);
+ jqBtnPlan.prop('disabled', ((problemDef.plan && problemDef.plan.sl) || []).length == 0);
+
+ editor.on('change', function (instance, changeObj) {
+ var doc = editor.getDoc(),
+ pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from));
+
+ if (changeObj.removed) {
+ activityHandler.queueTrace({'typ': 'r', 'off': pos, 'len': codeq.codePointCount(changeObj.removed.join(''))});
+ }
+
+ if (changeObj.text) {
+ activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text});
+ }
+ });
+
+ jqBtnPlan.on('click', function () {
+ if (!hinter.planNext()) {
+ jqBtnPlan.prop('disabled', true).blur();
+ }
+ });
+ jqBtnHint.on('click', function () {
+ terminal.append('hint.\n', 'input');
+ terminal.inputDisable();
+ var doc = editor.getDoc();
+ codeq.comms.sendHint({
+ 'language': 'prolog',
+ 'program': editor.getDoc().getValue(),
+ 'problem_id': problemDef.id
+ })
+ .then(function (data) {
+ if (data.code === 0) {
+ hinter.handle(data.hints);
+ }
+ else {
+ terminal.append(data.message + '\n', 'error');
+ }
+ })
+ .fail(commError)
+ .fin(function () {
+ terminal.inputEnable();
+ terminal.append('?- ', 'output');
+ })
+ .done();
+ });
+ jqBtnTest.on('click', function () {
+ terminal.append('test.\n', 'input');
+ terminal.inputDisable();
+ var doc = editor.getDoc();
+ codeq.comms.sendTest({
+ 'language': 'prolog',
+ 'program': editor.getDoc().getValue(),
+ 'problem_id': problemDef.id
+ })
+ .then(function (data) {
+ if (data.code === 0) {
+ hinter.handle(data.hints);
+ }
+ else {
+ terminal.append(data.message + '\n', 'error');
+ }
+ })
+ .fail(commError)
+ .fin(function () {
+ terminal.inputEnable();
+ terminal.append('?- ', 'output');
+ })
+ .done();
+ });
+
+ return {
+ destroy: function () {
+ $('#screen_prolog .title').text('');//empty the title text
+ jqAllButtons.off();
+ editor.off('change');
+ hinter.destroy();
+ terminal.destroy();
+ jqDescriptionContent.empty();
+ jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it
+ jqTerminal.empty(); // TODO: the same with the console
+ jqDescriptionContent = null;
+ jqEditor = null;
+ jqTerminal = null;
+ jqHints = null;
+ codeq.tr.registerDictionary('prolog', codeq.tr.emptyDictionary);
+ }
+ };
+ };
+})();
diff --git a/js/codeq/python.js b/js/codeq/python.js
index e80bbb7..e20fe06 100644
--- a/js/codeq/python.js
+++ b/js/codeq/python.js
@@ -1,329 +1,335 @@
-/**
- * Created by robert on 9/17/15.
- *
- * The python state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen.
- *
- * Currentyl it is mostly a copy of the prolog state (the exception is of course the makePythonTerminalHandler instead of makePrologTerminalHandler)
- */
-
-(function() {
- var subScreens, //this will be the actual (sub)state machine
- stateNameTag = 'stateName', //a tag for data which is added to some html elements
- jqScreen = $('#screen_python'), // the screen container element
- //quadrants
- jqDescription = jqScreen.find('.block1'),
- jqCode = jqScreen.find('.block2'),
- jqConsole = jqScreen.find('.block3'),
- jqInfo = jqScreen.find('.block4'),
- jqAllQuadrants = jqDescription.add(jqCode).add(jqConsole).add(jqInfo), // all the quadrants
- // buttons
- jqBtnPlan = jqScreen.find('.btn-plan'),
- jqBtnHint = jqScreen.find('.btn-hint'),
- jqBtnTest = jqScreen.find('.btn-test'),
- jqBtnRun = jqScreen.find('.btn-run'),
- jqBtnStop = jqScreen.find('.btn-stop'),
- jqInfoButtons = jqBtnPlan.add(jqBtnHint).add(jqBtnTest), // all info-focusing buttons
- jqAllButtons = jqInfoButtons.add(jqBtnRun).add(jqBtnStop), // all buttons
- // misc
- currentSubState = null,
- transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below)
- substates = {
- 'description': {
- 'enter': function () {
- currentSubState = 'block1';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- },
- 'code': {
- 'enter': function () {
- currentSubState = 'block2';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- },
- 'info': {
- 'enter': function () {
- currentSubState = 'block4';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- },
- 'console': {
- 'enter': function () {
- currentSubState = 'block3';
- jqScreen.addClass(currentSubState);
- },
- 'exit': function () {
- jqScreen.removeClass(currentSubState);
- currentSubState = null;
- }
- }
- };
- var pythonHandler; //created when we enter the python state and destroyed once we leave it
- codeq.globalStateMachine.register('python', {
- 'enter': function (data) {
- $('#navigation-login').css('display', '');
- $('#navigation-language').css('display', '');
- $('#navigation-problem').css('display', '');
- $("#navigation-python").addClass("active");
- $('#navigation-python').css('display', '');
-
- jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly
- pythonHandler = createPythonHandler(data.data);
- subScreens = codeq.makeStateMachine(substates);
- subScreens.transition(jqDescription.data(stateNameTag));
-/* Q.delay(100).then(function(){
- jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading
- }).done();*/
- jqInfoButtons.on(transitionEventName, function (event) {
- subScreens.transition('info'); // set focus on the hints quadrant
- event.stopPropagation(); // don't allow the event to go on and trigger further transition
- });
- jqBtnRun.on(transitionEventName, function (event) {
- subScreens.transition('console'); // set focus on the hints quadrant
- event.stopPropagation(); // don't allow the event to go on and trigger further transition
- });
-
- jqAllQuadrants.on(transitionEventName, function () {
- subScreens.transition($(this).data(stateNameTag));
- });
- },
- 'exit': function () {
- jqAllButtons.off(); // unregister all event handlers
- jqAllQuadrants.off();
- jqScreen.css('display', 'none');
-// jqAllQuadrants.removeClass('transition');
- pythonHandler.destroy();
- pythonHandler = null;
- subScreens.destroy();
- subScreens = null;
- jqScreen.addClass('block1');
-
- $('#navigation-login').css('display', 'none');
- $('#navigation-language').css('display', 'none');
- $('#navigation-problem').css('display', 'none');
- $("#navigation-python").removeClass("active");
- $('#navigation-python').css('display', 'none');
- }
- });
-
- jqDescription.data(stateNameTag, 'description');
- jqCode.data(stateNameTag, 'code');
- jqConsole.data(stateNameTag, 'console');
- jqInfo.data(stateNameTag, 'info');
-
- // a constant
- var firstCharacterPos = {'line': 0, 'ch': 0};
-
- var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
- var terminal = codeq.makeConsole(jqConsole, {
- 'greeting': 'CodeQ Python terminal proxy',
- 'autoHistory': true
- }),
- tcs = function terminalCommandSuccess (data) {
- if (data.code !== 0) {
- terminal.append(data.message, 'error');
- }
- },
- tcf = function terminalCommandFailed (error) {
- terminal.append(error + '\n', 'error');
- };
-
- terminal.onInput = function (text) {
- terminal.leftmostCol = 0;
- return codeq.comms.sendPythonPush({
- 'text': text + '\n'
- }).then(tcs, tcf);
- };
-
- codeq.comms.on('terminal_output', function (data) {
- var text = data.text,
- lines = text.split('\n');
- terminal.append(text, 'output');
- terminal.leftmostCol = lines[lines.length-1].length;
- });
-
- terminal.leftmostCol = 1;
-
- return terminal;
- };
-
- var makeActivityHandler = function (editor, problem_id) {
- var lastActivityMillis = Date.now(),
- deltaActivityMillis = function deltaActivityMillisFunc () {
- var now = Date.now(),
- dt = now - lastActivityMillis;
- lastActivityMillis = now;
- return dt;
- },
- queue = [],
- ts = null,
- timer = function () {
- var promise;
- ts = null;
- if (queue.length === 0) return Q(true);
- promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id);
- queue.length = 0;
- return promise;
- },
- flush = function () {
- clearTimeout(ts);
- return timer();
- };
-
- return {
- 'queueTrace': function (trace) {
- trace['dt'] = deltaActivityMillis();
- queue.push(trace);
- if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds
- return this;
- },
- 'flush': flush,
- 'addAndPurge': function (trace) {
- var accumulatedTrace = queue;
- queue = [];
- trace['dt'] = deltaActivityMillis();
- accumulatedTrace.push(trace);
- if (ts !== null) {
- clearTimeout(ts);
- ts = null;
- }
- return accumulatedTrace;
- }
- };
- };
-
-
-
- /**
- * Creates a new handler for the given Prolog assignment definition.
- *
- * @param {PrologTaskDef} info
- * @returns {{destroy: Function, processServerHints: Function}}
- */
- var createPythonHandler = function (info) {
- var problem = info.problem,
- jqDescriptionContent = jqDescription.find('.description'),
- jqEditor = jqCode.find('.code_editor'),
- jqTerminal = jqConsole.find('.console'),
- jqHints = jqInfo.find('.hints'),
- editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true, mode: 'python' }),
- activityHandler = makeActivityHandler(editor, problem.id),
- terminal = makePythonTerminalHandler(jqTerminal, editor, problem.id, activityHandler),
- hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint, problem.plan),
- commError = function (error) {
- alert(error);
- };
-
- editor.setValue(info.solution);
- $('#screen_python .title').text(problem.slug);
- jqDescriptionContent.html(problem.description);
- jqBtnPlan.prop('disabled', (problem.plan || '').length == 0);
-
- editor.on('change', function (instance, changeObj) {
- var doc = editor.getDoc(),
- pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from));
-
- if (changeObj.removed) {
- activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed.join(''))});
- }
-
- if (changeObj.text) {
- activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text});
- }
- });
-
- jqBtnPlan.on('click', function () {
- if (!hinter.planNext()) {
- jqBtnPlan.prop('disabled', true).blur();
- }
- });
- jqBtnHint.on('click', function () {
- var doc = editor.getDoc();
- codeq.comms.sendHint({
- 'language': 'python',
- 'program': editor.getDoc().getValue(),
- 'problem_id': problem.id
- })
- .then(function (data) {
- if (data.code === 0) {
- hinter.handle(data.hints);
- }
- else {
- terminal.append('error: ' + data.message);
- }
- })
- .fail(commError)
- .done();
- });
- jqBtnTest.on('click', function () {
- var doc = editor.getDoc();
- codeq.comms.sendTest({
- 'language': 'python',
- 'program': editor.getDoc().getValue(),
- 'problem_id': problem.id
- })
- .then(function (data) {
- if (data.code === 0) {
- hinter.handle(data.hints);
- }
- else {
- terminal.append('error: ' + data.message);
- }
- })
- .fail(commError)
- .done();
- });
- jqBtnRun.on('click', function () {
- var program = editor.getDoc().getValue();
- codeq.comms.sendPythonStop({})
- .then(function () {
- codeq.comms.sendPythonExec({
- 'program': program
- });
- })
- .fail(commError)
- .done();
- // focus the terminal
- jqTerminal.click();
- });
- jqBtnStop.on('click', function () {
- codeq.comms.sendPythonStop({})
- .fail(commError)
- .done();
- });
-
- // TODO first line of interpreter output is buffered without this, why?
- codeq.comms.sendPythonPush({
- 'text': ''
- });
-
- return {
- destroy: function () {
- $('#screen_python .title').text('');//empty the title text
- jqAllButtons.off();
- editor.off('change');
- codeq.comms.off('terminal_output'); // stop listening for the terminal events from server
- hinter.destroy();
- terminal.destroy();
- jqDescriptionContent.empty();
- jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it
- jqTerminal.empty(); // TODO: the same with the console
- jqDescriptionContent = null;
- jqEditor = null;
- jqTerminal = null;
- jqHints = null;
- }
- };
- };
-})();
+/**
+ * Created by robert on 9/17/15.
+ *
+ * The python state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen.
+ *
+ * Currentyl it is mostly a copy of the prolog state (the exception is of course the makePythonTerminalHandler instead of makePrologTerminalHandler)
+ */
+
+(function() {
+ var subScreens, //this will be the actual (sub)state machine
+ stateNameTag = 'stateName', //a tag for data which is added to some html elements
+ jqScreen = $('#screen_python'), // the screen container element
+ //quadrants
+ jqDescription = jqScreen.find('.block1'),
+ jqCode = jqScreen.find('.block2'),
+ jqConsole = jqScreen.find('.block3'),
+ jqInfo = jqScreen.find('.block4'),
+ jqAllQuadrants = jqDescription.add(jqCode).add(jqConsole).add(jqInfo), // all the quadrants
+ // buttons
+ jqBtnPlan = jqScreen.find('.btn-plan'),
+ jqBtnHint = jqScreen.find('.btn-hint'),
+ jqBtnTest = jqScreen.find('.btn-test'),
+ jqBtnRun = jqScreen.find('.btn-run'),
+ jqBtnStop = jqScreen.find('.btn-stop'),
+ jqInfoButtons = jqBtnPlan.add(jqBtnHint).add(jqBtnTest), // all info-focusing buttons
+ jqAllButtons = jqInfoButtons.add(jqBtnRun).add(jqBtnStop), // all buttons
+ // misc
+ currentSubState = null,
+ transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below)
+ substates = {
+ 'description': {
+ 'enter': function () {
+ currentSubState = 'block1';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ },
+ 'code': {
+ 'enter': function () {
+ currentSubState = 'block2';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ },
+ 'info': {
+ 'enter': function () {
+ currentSubState = 'block4';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ },
+ 'console': {
+ 'enter': function () {
+ currentSubState = 'block3';
+ jqScreen.addClass(currentSubState);
+ },
+ 'exit': function () {
+ jqScreen.removeClass(currentSubState);
+ currentSubState = null;
+ }
+ }
+ };
+ var pythonHandler; //created when we enter the python state and destroyed once we leave it
+ codeq.globalStateMachine.register('python', {
+ 'enter': function (problemDef, commonHints, currentSolution) {
+ $('#navigation-language').css('display', '');
+ $('#navigation-problem').css('display', '');
+ $("#navigation-python").addClass("active");
+ $('#navigation-python').css('display', '');
+
+ jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly
+ pythonHandler = createPythonHandler(problemDef, commonHints, currentSolution);
+ subScreens = codeq.makeStateMachine(substates);
+ subScreens.transition(jqDescription.data(stateNameTag));
+/* Q.delay(100).then(function(){
+ jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading
+ }).done();*/
+ jqInfoButtons.on(transitionEventName, function (event) {
+ subScreens.transition('info'); // set focus on the hints quadrant
+ event.stopPropagation(); // don't allow the event to go on and trigger further transition
+ });
+ jqBtnRun.on(transitionEventName, function (event) {
+ subScreens.transition('console'); // set focus on the hints quadrant
+ event.stopPropagation(); // don't allow the event to go on and trigger further transition
+ });
+
+ jqAllQuadrants.on(transitionEventName, function () {
+ subScreens.transition($(this).data(stateNameTag));
+ });
+ },
+ 'exit': function () {
+ jqAllButtons.off(); // unregister all event handlers
+ jqAllQuadrants.off();
+ jqScreen.css('display', 'none');
+// jqAllQuadrants.removeClass('transition');
+ pythonHandler.destroy();
+ pythonHandler = null;
+ subScreens.destroy();
+ subScreens = null;
+ jqScreen.addClass('block1');
+
+ $('#navigation-language').css('display', 'none');
+ $('#navigation-problem').css('display', 'none');
+ $("#navigation-python").removeClass("active");
+ $('#navigation-python').css('display', 'none');
+ }
+ });
+
+ jqDescription.data(stateNameTag, 'description');
+ jqCode.data(stateNameTag, 'code');
+ jqConsole.data(stateNameTag, 'console');
+ jqInfo.data(stateNameTag, 'info');
+
+ // a constant
+ var firstCharacterPos = {'line': 0, 'ch': 0};
+
+ var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
+ var terminal = codeq.makeConsole(jqConsole, {
+ 'greeting': 'CodeQ Python terminal proxy',
+ 'autoHistory': true
+ }),
+ tcs = function terminalCommandSuccess (data) {
+ if (data.code !== 0) {
+ terminal.append(data.message, 'error');
+ }
+ },
+ tcf = function terminalCommandFailed (error) {
+ terminal.append(error + '\n', 'error');
+ };
+
+ terminal.onInput = function (text) {
+ terminal.leftmostCol = 0;
+ return codeq.comms.sendPythonPush({
+ 'text': text + '\n'
+ }).then(tcs, tcf);
+ };
+
+ codeq.comms.on('terminal_output', function (data) {
+ var text = data.text,
+ lines = text.split('\n');
+ terminal.append(text, 'output');
+ terminal.leftmostCol = lines[lines.length-1].length;
+ });
+
+ terminal.leftmostCol = 1;
+
+ return terminal;
+ };
+
+ var makeActivityHandler = function (editor, problem_id) {
+ var lastActivityMillis = Date.now(),
+ deltaActivityMillis = function deltaActivityMillisFunc () {
+ var now = Date.now(),
+ dt = now - lastActivityMillis;
+ lastActivityMillis = now;
+ return dt;
+ },
+ queue = [],
+ ts = null,
+ timer = function () {
+ var promise;
+ ts = null;
+ if (queue.length === 0) return Q(true);
+ promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id);
+ queue.length = 0;
+ return promise;
+ },
+ flush = function () {
+ clearTimeout(ts);
+ return timer();
+ };
+
+ return {
+ 'queueTrace': function (trace) {
+ trace['dt'] = deltaActivityMillis();
+ queue.push(trace);
+ if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds
+ return this;
+ },
+ 'flush': flush,
+ 'addAndPurge': function (trace) {
+ var accumulatedTrace = queue;
+ queue = [];
+ trace['dt'] = deltaActivityMillis();
+ accumulatedTrace.push(trace);
+ if (ts !== null) {
+ clearTimeout(ts);
+ ts = null;
+ }
+ return accumulatedTrace;
+ }
+ };
+ };
+
+ codeq.on('init', function (args) {
+ codeq.tr.registerDictionary('python', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active
+ });
+
+ /**
+ * Creates a new handler for the given Prolog assignment definition.
+ *
+ * @param {PrologTaskDef} info
+ * @returns {{destroy: Function, processServerHints: Function}}
+ */
+ var createPythonHandler = function (problemDef, commonHints, currentSolution) {
+ var //problem = info.problem,
+ jqDescriptionContent = jqDescription.find('.description'),
+ jqEditor = jqCode.find('.code_editor'),
+ jqTerminal = jqConsole.find('.console'),
+ jqHints = jqInfo.find('.hints'),
+ editor = codeq.makeEditor(jqEditor[0], {
+ mode: 'python',
+ indentUnit: 4
+ }),
+ activityHandler = makeActivityHandler(editor, problemDef.id),
+ terminal = makePythonTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler),
+ hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'python_hints', problemDef.hint, commonHints, problemDef.plan),
+ commError = function (error) {
+ alert(error);
+ };
+
+ codeq.tr.registerDictionary('python', problemDef.translations);
+ codeq.tr.translateDom(jqScreen);
+ if (currentSolution) editor.setValue(currentSolution);
+// $('#screen_python .title').text(problem.slug);
+// jqDescriptionContent.html(problem.description);
+ jqBtnPlan.prop('disabled', ((problemDef.plan && problemDef.plan.sl) || []).length == 0);
+
+ editor.on('change', function (instance, changeObj) {
+ var doc = editor.getDoc(),
+ pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from));
+
+ if (changeObj.removed) {
+ activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed.join(''))});
+ }
+
+ if (changeObj.text) {
+ activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text});
+ }
+ });
+
+ jqBtnPlan.on('click', function () {
+ if (!hinter.planNext()) {
+ jqBtnPlan.prop('disabled', true).blur();
+ }
+ });
+ jqBtnHint.on('click', function () {
+ var doc = editor.getDoc();
+ codeq.comms.sendHint({
+ 'language': 'python',
+ 'program': editor.getDoc().getValue(),
+ 'problem_id': problemDef.id
+ })
+ .then(function (data) {
+ if (data.code === 0) {
+ hinter.handle(data.hints);
+ }
+ else {
+ terminal.append('error: ' + data.message);
+ }
+ })
+ .fail(commError)
+ .done();
+ });
+ jqBtnTest.on('click', function () {
+ var doc = editor.getDoc();
+ codeq.comms.sendTest({
+ 'language': 'python',
+ 'program': editor.getDoc().getValue(),
+ 'problem_id': problemDef.id
+ })
+ .then(function (data) {
+ if (data.code === 0) {
+ hinter.handle(data.hints);
+ }
+ else {
+ terminal.append('error: ' + data.message);
+ }
+ })
+ .fail(commError)
+ .done();
+ });
+ jqBtnRun.on('click', function () {
+ var program = editor.getDoc().getValue();
+ codeq.comms.sendPythonStop({})
+ .then(function () {
+ codeq.comms.sendPythonExec({
+ 'program': program
+ });
+ })
+ .fail(commError)
+ .done();
+ // focus the terminal
+ jqTerminal.click();
+ });
+ jqBtnStop.on('click', function () {
+ codeq.comms.sendPythonStop({})
+ .fail(commError)
+ .done();
+ });
+
+ // TODO first line of interpreter output is buffered without this, why?
+ codeq.comms.sendPythonPush({
+ 'text': ''
+ });
+
+ return {
+ destroy: function () {
+ $('#screen_python .title').text('');//empty the title text
+ jqAllButtons.off();
+ editor.off('change');
+ codeq.comms.off('terminal_output'); // stop listening for the terminal events from server
+ hinter.destroy();
+ terminal.destroy();
+ jqDescriptionContent.empty();
+ jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it
+ jqTerminal.empty(); // TODO: the same with the console
+ jqDescriptionContent = null;
+ jqEditor = null;
+ jqTerminal = null;
+ jqHints = null;
+ codeq.tr.registerDictionary('python', codeq.tr.emptyDictionary);
+ }
+ };
+ };
+})();
diff --git a/js/codeq/statusbar.js b/js/codeq/statusbar.js
index 2134b93..56070bd 100644
--- a/js/codeq/statusbar.js
+++ b/js/codeq/statusbar.js
@@ -6,7 +6,7 @@
(function () {
var langs = codeq.supportedLangs,
- jqMenu = jqBar.find('.dropdown-menu'),
+ jqMenu = jqLang.find('.dropdown-menu'),
lang, cssClass;
for (lang in langs) {
if (!langs.hasOwnProperty(lang)) continue;
diff --git a/js/codeq/translation.js b/js/codeq/translation.js
index af39cdc..d4278ac 100644
--- a/js/codeq/translation.js
+++ b/js/codeq/translation.js
@@ -6,6 +6,7 @@
translationKey = jqElt.data('tkey'),
dict = dicts[dictionaryKey],
translations, html, key;
+ if (dict === codeq.tr.emptyDictionary) return; // silent ignore
if (!dict) {
codeq.log.error('Cannot find translation dictionary ' + dictionaryKey);
return;
@@ -61,6 +62,12 @@
dicts[name] = dict;
},
+ 'unregisterDictionary': function (name) {
+ delete dicts[name];
+ },
+
+ 'emptyDictionary': {}, // use this with registerDictionary when you don't want any translations
+
'translateDom': function (jqTopElt) {
var lang = codeq.getLang();
jqTopElt.find('.translatable').each(function () {