summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Pušnik <marko.pusnik@guru.si>2015-09-30 19:07:37 +0200
committerMarko Pušnik <marko.pusnik@guru.si>2015-09-30 19:07:37 +0200
commit8bb68a1e979022ead00535c25403b341f2cb24bb (patch)
tree0ad16cf9acb81cbb6f8484f32fed657e8c66dabb
parentba6135a83273c625097fe7cdb59319a51acca31e (diff)
parent0da1117cfc28688633be7b8382aa60435bf740eb (diff)
Merge branch 'master' of odie.guru.si:codeq-web
Conflicts: index.html
-rw-r--r--index.html62
-rw-r--r--js/codeq/comms.js14
-rw-r--r--js/codeq/core.js63
-rw-r--r--js/codeq/hint.js238
-rw-r--r--js/codeq/language.js8
-rw-r--r--js/codeq/login.js4
-rw-r--r--js/codeq/navigation.js6
-rw-r--r--js/codeq/problem.js42
-rw-r--r--js/codeq/python.js2
-rw-r--r--js/codeq/robot.js300
-rw-r--r--js/codeq/translation.js9
-rw-r--r--res/en.json15
-rw-r--r--res/sl.json15
13 files changed, 688 insertions, 90 deletions
diff --git a/index.html b/index.html
index 700a170..f7971bf 100644
--- a/index.html
+++ b/index.html
@@ -36,10 +36,11 @@
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
- <li style="display: none;" id="navigation-language"><a href="">Language</a></li>
- <li style="display: none;" id="navigation-problem"><a href="">Problem</a></li>
- <li style="display: none;" id="navigation-python"><a href="">Python</a></li>
- <li style="display: none;" id="navigation-prolog"><a href="">Prolog</a></li>
+ <li style="display: none;" id="navigation-language"><a href="" data-tkey="language">Language</a></li>
+ <li style="display: none;" id="navigation-problem"><a href="" data-tkey="problem">Problem</a></li>
+ <li style="display: none;" id="navigation-python"><a href="" data-tkey="python">Python</a></li>
+ <li style="display: none;" id="navigation-prolog"><a href="" data-tkey="prolog">Prolog</a></li>
+ <li style="display: none;" id="navigation-robot"><a href="" data-tkey="robot">Robot</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<p class="navbar-text" id="signed-in-title"></p>
@@ -54,10 +55,10 @@
<span class="glyphicon glyphicon glyphicon-user"></span>
</a>
<ul class="dropdown-menu">
- <li id="navigation-logout"><a href="#">Logout</a></li>
- <li id="navigation-profile"><a href="#">Profile</a></li>
+ <li id="navigation-logout"><a href="#" data-tkey="logout">Logout</a></li>
+ <li id="navigation-profile"><a href="#" data-tkey="profile">Profile</a></li>
<li role="separator" class="divider"></li>
- <li><a href="#" data-toggle="modal" data-target="#modalChangePassword">Change password</a></li>
+ <li><a href="#" data-toggle="modal" data-target="#modalChangePassword" data-tkey="change_pass">Change password</a></li>
</ul>
</li>
<li class="dropdown">
@@ -65,7 +66,7 @@
<div class="dropdown-menu" style="background-color: #fff">
<form class="form col-sm-12">
<div class="form-group">
- <span class="small">Language</span>
+ <span class="small" data-tkey="language">Language</span>
<a class="text-muted" data-container="body" data-toggle="popover" data-trigger="hover" data-placement="left" data-html="true" data-content="<span class='small'>Select the UI language.</span>" data-original-title="" title=""><i class="glyphicon glyphicon-question-sign"></i></a><br>
<select class="form-control">
<option value="sl">Slovensko</option>
@@ -74,7 +75,7 @@
</select>
</div>
<div class="form-group">
- <span class="small">
+ <span class="small" data-tkey="language">
Language
</span>
<a class="text-muted" data-container="body" data-toggle="popover" data-trigger="hover" data-placement="left" data-html="true" data-content="<span class='small'>Select the UI language.</span>" data-original-title="" title=""><i class="glyphicon glyphicon-question-sign"></i></a><br>
@@ -164,9 +165,9 @@
<div class="col-lg-3 col-md-6 col-sm-12 block block2" style="padding-top: 51px; padding-bottom: 21px;">
<nav class="navbar navbar-default block-toolbar">
<div class="container-fluid">
- <button type="button" class="btn btn-default navbar-btn btn-plan">Plan</button>
- <button type="button" class="btn btn-default navbar-btn btn-hint">Hint</button>
- <button type="button" class="btn btn-default navbar-btn btn-test">Test</button>
+ <button type="button" class="btn btn-default navbar-btn btn-plan" data-tkey="btn_plan">Plan</button>
+ <button type="button" class="btn btn-default navbar-btn btn-hint" data-tkey="btn_hint">Hint</button>
+ <button type="button" class="btn btn-default navbar-btn btn-test" data-tkey="btn_test">Test</button>
</div>
</nav>
<div class="code_editor"></div>
@@ -195,10 +196,42 @@
<div class="col-lg-3 col-md-6 col-sm-12 block block2" style="padding-top: 51px; padding-bottom: 21px;">
<nav class="navbar navbar-default block-toolbar">
<div class="container-fluid">
+ <button type="button" class="btn btn-default navbar-btn btn-plan" data-tkey="btn_plan">Plan</button>
+ <button type="button" class="btn btn-default navbar-btn btn-hint" data-tkey="btn_hint">Hint</button>
+ <button type="button" class="btn btn-default navbar-btn btn-test" data-tkey="btn_test">Test</button>
+ <button type="button" class="btn btn-default navbar-btn btn-run" data-tkey="btn_run">Run</button>
+ <button type="button" class="btn btn-default navbar-btn btn-stop" data-tkey="btn_stop">Stop</button>
+ </div>
+ </nav>
+ <div class="code_editor"></div>
+ <div class="block-label">Code</div>
+ </div>
+ <div class="col-lg-3 col-md-6 col-sm-12 block block3">
+ <div class="console"></div>
+ <div class="block-label">Console</div>
+ </div>
+ <div class="col-lg-3 col-md-6 col-sm-12 block block4">
+ <div class="hints"></div>
+ <div class="block-label">Hints</div>
+ </div>
+ </div><!--/row-->
+ </div><!--container-->
+
+ <!-- problem screen: robot -->
+ <div class="container-fluid quadrants block1" id="screen_robot" style="display: none;">
+ <div class="row">
+ <div class="col-lg-3 col-md-6 col-sm-12 block block1">
+ <h2 class="title translatable" data-dict="robot" data-tkey="slug"></h2>
+ <div class="description translatable" data-dict="robot" data-tkey="description"></div>
+ <div class="block-label">Instructions</div>
+ </div>
+ <div class="col-lg-3 col-md-6 col-sm-12 block block2">
+ <nav class="navbar navbar-default">
+ <div class="container-fluid">
<button type="button" class="btn btn-default navbar-btn btn-plan">Plan</button>
<button type="button" class="btn btn-default navbar-btn btn-hint">Hint</button>
- <button type="button" class="btn btn-default navbar-btn btn-test">Test</button>
<button type="button" class="btn btn-default navbar-btn btn-run">Run</button>
+ <input type="text" id="robot_ip" placeholder="Robot's IP"></input>
<button type="button" class="btn btn-default navbar-btn btn-stop">Stop</button>
</div>
</nav>
@@ -222,7 +255,7 @@
<h2>
Profile
<div class="btn-group btn-group-xs hidden-md pull-right">
- <a href="" data-toggle="modal" data-target="#modalChangePassword" class="btn btn-default">Change Password</a>
+ <a href="" data-toggle="modal" data-target="#modalChangePassword" class="btn btn-default" data-tkey="change_pass">Change Password</a>
<a href="#" class="btn btn-default" id="btnProfileGoBack">Go back</a>
</div>
</h2>
@@ -438,6 +471,7 @@
<script src="js/codeq/hint.js"></script>
<script src="js/codeq/prolog.js"></script>
<script src="js/codeq/python.js"></script>
+ <script src="js/codeq/robot.js"></script>
<script src="js/codeq/login.js"></script>
<script src="js/codeq/profile.js"></script>
<script src="js/codeq/language.js"></script>
diff --git a/js/codeq/comms.js b/js/codeq/comms.js
index ecca6ff..f416e88 100644
--- a/js/codeq/comms.js
+++ b/js/codeq/comms.js
@@ -165,7 +165,6 @@
// ================================================================================
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({
@@ -197,7 +196,7 @@
});
});
},
- ajaxPrefix;
+ ajaxPrefix, ajaxDataPrefix, ajaxResPrefix;
ajaxPrefix = location.pathname;
if (!ajaxPrefix) ajaxPrefix = '/';
@@ -206,7 +205,8 @@
ajaxPrefix[ajaxPrefix.length - 1] = '';
ajaxPrefix = ajaxPrefix.join('/');
}
- ajaxPrefix = ajaxPrefix + 'data/';
+ ajaxDataPrefix = ajaxPrefix + 'data/';
+ ajaxResPrefix = ajaxPrefix + 'res/';
// ================================================================================
// This module's API methods
@@ -399,7 +399,7 @@
return Q(x);
}
}
- x = ajaxGet(ajaxPrefix + identifier + '/language.json').then(
+ x = ajaxGet(ajaxDataPrefix + identifier + '/language.json').then(
function getLanguageDefSuccess(data) {
languageCache[identifier] = data;
return data; // proxy further
@@ -414,7 +414,11 @@
},
'getProblemDef': function (language, group, problem) {
- return ajaxGet(ajaxPrefix + language + '/' + group + '/' + problem + '/problem.json');
+ return ajaxGet(ajaxDataPrefix + language + '/' + group + '/' + problem + '/problem.json');
+ },
+
+ 'getGuiTranslation': function (lang) {
+ return ajaxGet(ajaxResPrefix + lang + '.json');
}
};
})();
diff --git a/js/codeq/core.js b/js/codeq/core.js
index 01382d1..29b497c 100644
--- a/js/codeq/core.js
+++ b/js/codeq/core.js
@@ -372,6 +372,49 @@
// The boot sequence
// ================================================================================
+ var loadGuiTranslations = function () {
+ var langs = codeq.availableLangs,
+ loaders = [],
+ loaderLangs = [],
+ i, lang;
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ loaders.push(codeq.comms.getGuiTranslation(lang));
+ loaderLangs.push(lang);
+ }
+ return Q.all(loaders)
+ .then(function (results) {
+ // convert translations into something that the translation module can use
+ var dictionary = {},
+ i, json, key, lang, translations;
+ for (i = results.length - 1; i >= 0; i--) {
+ json = results[i];
+ lang = loaderLangs[i];
+ for (key in json) {
+ if (!json.hasOwnProperty(key)) continue;
+ translations = dictionary[key];
+ if (translations) translations[lang] = json[key];
+ else {
+ translations = {};
+ dictionary[key] = translations;
+ translations[lang] = json[key];
+ }
+ }
+ }
+ // ensure each key contains all translations
+ for (key in dictionary) {
+ if (!dictionary.hasOwnProperty(key)) continue;
+ translations = dictionary[key];
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ if (!(lang in translations)) translations[lang] = "(Untranslated: " + lang + ")";
+ }
+ }
+ // register with the system
+ codeq.tr.registerDictionary('gui', dictionary);
+ });
+ };
+
$(document).ready(function () {
// set the language
var navigatorLang = navigator.language || navigator.browserLanguage, // language reported by browser
@@ -387,11 +430,17 @@
codeq.availableLangs.push(key);
}
- codeq.fire('init'); // tell any interested modules that we are not initialized, perhaps they want to initialize too
- 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()});
+ // the boot chain: must be a sequence of .then() terminated with .done()
+ loadGuiTranslations()
+ .then(function () {
+ codeq.fire('init'); // tell any interested modules that we are now initialized, perhaps they want to initialize too
+ codeq.setLang(lang || 'en'); // initial language setting, this also translates the GUI
+ // 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()
+ })
+ .done();
+ });
})();
diff --git a/js/codeq/hint.js b/js/codeq/hint.js
index d30f934..6f54717 100644
--- a/js/codeq/hint.js
+++ b/js/codeq/hint.js
@@ -12,6 +12,7 @@
hintCleaners = [],
planIdx = 0,
dictionary = [],
+ jqHintsContainer = jqHints.parent(),
clearHints = function () {
var i;
@@ -44,45 +45,192 @@
});
},
- typeHandlers = {
- 'static': function (type, template, serverHint) {
- var args = serverHint ? serverHint.args : null,
- jqContainer, jqButton, i, N, tmpl, tmplIsObject;
- if (template instanceof Array) { // unwrap the template if there's only one
- if (template.length == 0) template = '';
- else if (template.length == 1) template = template[0] + ''; // it must be a string
+ prepareStaticHintContent = function (hintContent, indices, hintId) {
+ var content = {},
+ langs = codeq.availableLangs,
+ skippedLangs = [],
+ Nhints = 0, // count hints and remember the count
+ haveIndices = indices instanceof Array,
+ i, lang, j, node, tmpContent;
+ langLoop:
+ for (j = langs.length - 1; j >= 0; j--) {
+ lang = langs[j];
+ node = hintContent[lang];
+ if (!node) {
+ // no translation for this language, mark for later
+ skippedLangs.push(lang);
+ }
+ else {
+ if (haveIndices) {
+ // traverse node's indices
+ for (i = 0; i < indices.length; i++) {
+ node = node[indices[i]];
+ if (!node) {
+ // index out of bounds, mark this language for later
+ codeq.log.error('Cannot reference hint ' + hintId + ' with indices ' + indices);
+ skippedLangs.push(lang);
+ continue langLoop;
+ }
+ }
+ }
+ if (typeof node === 'string') {
+ // we have a single string (= a simplex message), put it into an array
+ content[lang] = [node];
+ if (Nhints < 1) Nhints = 1;
+ }
+ else if (node instanceof Array) {
+ // we already have an array
+ if (node.length == 0) {
+ if (haveIndices) codeq.log.error('Hint ' + hintId + ' with indices ' + indices + ' for language ' + lang + ' contains an empty array');
+ else codeq.log.error('Hint ' + hintId + ' for language ' + lang + ' contains an empty array');
+ skippedLangs.push(lang);
+ continue;
+ }
+ // verify that each array element is a string or an object with a message
+ for (i = node.length - 1; i >= 0; i--) {
+ tmpContent = node[i];
+ if (typeof tmpContent === 'string') {
+ // this is okay
+ }
+ else if (tmpContent && (typeof tmpContent === 'object')) {
+ if (typeof tmpContent.message !== 'string') {
+ if (haveIndices) tmpContent.message = 'There is no message defined for hint ' + hintId + 'with indices ' + indices + ' in language ' + lang + ' at index ' + i;
+ else tmpContent.message = 'There is no message defined for hint ' + hintId + ' in language ' + lang + ' at index ' + i;
+ codeq.log.error(tmpContent.message);
+ }
+ }
+ else {
+ // not a string or an object with a message
+ if (haveIndices) tmpContent = 'There is no message defined for hint ' + hintId + 'with indices ' + indices + ' in language ' + lang + ' at index ' + i;
+ else tmpContent = 'There is no message defined for hint ' + hintId + ' in language ' + lang + ' at index ' + i;
+ node[i] = tmpContent;
+ codeq.log.error(tmpContent);
+ }
+ }
+ content[lang] = node;
+ if (Nhints < node.length) Nhints = node.length;
+ }
+ else if (node && (typeof node === 'object') && (typeof node.message === 'string')) {
+ // we have a single object with a message (= a complex message), put it into an array
+ content[lang] = [node];
+ if (Nhints < 1) Nhints = 1;
+ }
+ else {
+ if (haveIndices) codeq.log.error('Hint ' + hintId + ' with indices ' + indices + ' did not result in a terminal node for language ' + lang + ', but: ' + node);
+ else codeq.log.error('Hint ' + hintId + ' probably needs indices because it does not have a terminal node for language ' + lang + ', but: ' + node);
+ skippedLangs.push(lang);
+ }
}
- if (template instanceof Array) {
- codeq.log.debug('Processing an array of static hints');
+ }
+ if (Nhints === 0) {
+ // provide error feedback on display when there is no hint translation available in any language
+ if (haveIndices) tmpContent = ['No hints found for hint ' + hintId + ' with indices ' + indices];
+ else tmpContent = ['No hints found for hint ' + hintId];
+ codeq.log.error(tmpContent[0]);
+ for (j = langs.length - 1; j >= 0; j--) {
+ content[langs[j]] = tmpContent;
+ }
+ Nhints = 1;
+ }
+ else if (skippedLangs.length > 0) {
+ // choose a default content and assign it to skipped languages
+ lang = 'en'; // try English first
+ tmpContent = content[lang];
+ if (!tmpContent) {
+ // if no English exists, find one that does
+ for (lang in content) {
+ if (!content.hasOwnProperty(lang)) continue;
+ tmpContent = content[lang];
+ if (tmpContent) break;
+ }
+ }
+ codeq.log.error('Translations in languages ' + skippedLangs.join(', ') + ' are missing or erroneous for hint ' + hintId + ', replacing their content with translation for ' + lang);
+ // assign the default content to skipped languages
+ for (j = skippedLangs.length - 1; j >= 0; j--) {
+ content[skippedLangs[j]] = tmpContent;
+ }
+ }
+ content.hintLength = Nhints;
+ return content;
+ },
+
+ ta = function (trObj) { // an object of the form: {'en': 'english content', 'sl': 'slovenska vsebina'}
+ var result = ['data-dict="', trNamespace, '" data-tkey="', dictionary.length, '"'].join('');
+ dictionary.push(trObj);
+ return result;
+ },
+
+ typeHandlers = {
+ 'static': function (type, template, serverHint, hintId) {
+ var content = prepareStaticHintContent(template, serverHint.indices, hintId),
+ args = serverHint ? serverHint.args : null,
+ hintIndex = 0,
+ trButton = {},
+ Nhints = content.hintLength,
+ nextJqHint = function () {
+ var trContent = {},
+ langs = codeq.availableLangs,
+ lang, i, msg, jq, deltaHeight;
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ try {
+ msg = content[lang][hintIndex];
+ if (typeof msg === 'string') {
+ trContent[lang] = processTemplate(msg, args);
+ trButton[lang] = 'More...';
+ }
+ else {
+ trContent[lang] = processTemplate(msg.message, args);
+ trButton[lang] = msg.linkText;
+ }
+ }
+ catch (e) {
+ msg = 'Error processing hint ' + hintId + ' at index ' + hintIndex + ' for language ' + lang + ': ' + e;
+ codeq.log.error(msg, e);
+ trContent[lang] = msg;
+ }
+ }
+ jq = $('<div class="hint-static translatable" ' + ta(trContent) + '></div>');
+ hintIndex++;
+ if (jqButton) {
+ if (hintIndex < Nhints) {
+ jqButton.before(jq);
+ codeq.tr.translateDom(jqButton);
+ }
+ else {
+ jqButton.remove();
+ jqContainer.append(jq);
+ }
+ }
+ else {
+ jqContainer.append(jq);
+ }
+ codeq.tr.translateDom(jq);
+ // scroll into view if overflowing
+ deltaHeight = jqHints.height() - jqHintsContainer.height();
+ if (deltaHeight > 0) {
+ jqHintsContainer.scrollTop(deltaHeight);
+ }
+ },
+ jqContainer, jqButton;
+
+ if (Nhints > 1) {
+ // hint sequence
jqContainer = $('<div class="hint-static-group"></div>');
- jqButton = $('<a class="hint-static-link"></a>');
+ jqButton = $('<a class="hint-static-link translatable" ' + ta(trButton) + '></a>');
jqHints.append(jqContainer);
- N = template.length;
- tmpl = template[0];
- tmplIsObject = (typeof tmpl === 'object') && (tmpl !== null);
- jqContainer.append('<div class="hint-static">' + processTemplate((tmplIsObject ? tmpl.message : tmpl) || '', args) + '</div>');
jqContainer.append(jqButton);
- jqButton.text(tmplIsObject && tmpl.linkText ? tmpl.linkText : 'More...'); // TODO: translate "more"
- i = 1;
jqButton.on('click', function () {
- var tmpl = template[i],
- tmplIsObject = (typeof tmpl === 'object') && (tmpl !== null),
- jqNext = $('<div class="hint-static">' + processTemplate((tmplIsObject ? tmpl.message : tmpl) || '', args) + '</div>');
- i++;
- if (i < N) {
- jqButton.before(jqNext);
- jqButton.text(tmplIsObject && tmpl.linkText ? tmpl.linkText : 'More...'); // TODO: translate "more"
- }
- else {
- jqButton.remove();
- jqContainer.append(jqNext);
- }
+ nextJqHint();
});
}
else {
- codeq.log.debug('Processing a single static hint');
- jqHints.append('<div class="hint-static">' + processTemplate(template, args) + '</div>');
+ // a single hint
+ jqContainer = jqHints;
+ jqButton = null;
}
+ nextJqHint();
+
// no hint cleaner here, a static hint remains on the screen
},
@@ -137,7 +285,7 @@
hintCommonDefs = commonDef.hint_type,
hintProblemTr = problemDef.hint,
hintCommonTr = commonDef.hint,
- planDef = problemDef.plan.sl;
+ planDef = problemDef.plan;
return {
/**
@@ -146,12 +294,16 @@
*/
'planNext': function () {
if (planIdx < planDef.length) {
- typeHandlers['static']('static', planDef[planIdx], null);
+ typeHandlers.static('static', planDef[planIdx], {}, 'plan');
planIdx++;
}
return planIdx < planDef.length;
},
+ 'hasNextPlan': function () {
+ return planIdx < planDef.length;
+ },
+
/**
* Processes and display appropriately the server hints.
* TODO: sort hints so static and popup hints come first, and a (single) drop-down hint last
@@ -160,7 +312,8 @@
*/
'handle': function (serverHints) {
var n = serverHints.length,
- i, serverHint, hintId, hintDef, hintContent, hintType, hintTemplate, t, fn, indices;
+ i, serverHint, hintId, hintDef, hintContent, hintType, t, fn, indices;
+
clearHints();
mainLoop:
for (i = 0; i < n; i++) {
@@ -182,28 +335,18 @@
codeq.log.error('Hint without content: ' + hintId);
continue;
}
- if (serverHint.indices) {
- indices = serverHint.indices;
- for (i = 0; i < indices.length; i++) {
- hintContent = hintContent[indices[i]];
- if (!hintContent) {
- codeq.log.error('Cannot reference hint ' + hintId + ' with indices ' + serverHint.indices);
- continue mainLoop;
- }
- }
- }
t = typeof hintDef;
- if (t === 'string') hintType = hintDef;
- else if ((t === 'object') && (hintDef !== null)) hintType = hintDef.type;
+ if (t === 'string') hintType = hintDef; // currently a hint type is a string
+ else if ((t === 'object') && (hintDef !== null)) hintType = hintDef.type; // but in future we may use an object, if a definition becomes more complex
else {
- codeq.log.error('Cannot determine type of hint ' + hintId + ' from: ' + hintDef);
+ codeq.log.error('Cannot determine the type of hint ' + hintId + ' from: ' + hintDef);
continue;
}
fn = typeHandlers[hintType];
if (!fn) codeq.log.error('Unsupported hint type: ' + hintType);
- else fn(hintType, hintContent.sl, serverHint);
+ else fn(hintType, hintContent, serverHint, hintId);
}
},
@@ -213,6 +356,7 @@
jqHints.empty();
jqHints = null;
jqEditor = null;
+ jqHintsContainer = null;
editor = null;
}
};
diff --git a/js/codeq/language.js b/js/codeq/language.js
index aac4e78..0893e33 100644
--- a/js/codeq/language.js
+++ b/js/codeq/language.js
@@ -6,8 +6,10 @@
var jqScreen = $('#screen_language'),
jqProlog = $('#choose-prolog'),
jqPython = $('#choose-python'),
+ jqRobot = $('#choose-robot'),
chooseProlog = function () {codeq.globalStateMachine.transition('problem', 'prolog');},
- choosePython = function () {codeq.globalStateMachine.transition('problem', 'python');};
+ choosePython = function () {codeq.globalStateMachine.transition('problem', 'python');},
+ chooseRobot = function () {codeq.globalStateMachine.transition('problem', 'robot');};
codeq.globalStateMachine.register('language',{
'enter': function(){
@@ -16,13 +18,15 @@
jqScreen.css('display', '');
jqProlog.on('click', chooseProlog);
jqPython.on('click', choosePython);
+ jqRobot.on('click', chooseRobot);
},
'exit' : function(){
jqProlog.off();
jqPython.off();
+ jqRobot.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 e5a446b..7844dc4 100644
--- a/js/codeq/login.js
+++ b/js/codeq/login.js
@@ -16,9 +16,9 @@
if (data.code !== 0) throw new Error('Login failed, code: ' + data.code + ', message: ' + data.message);
//merge these settings into the already existing default settings
- sett = data.settings;
+ var sett = data.settings;
$.merge(true, codeq.settings, sett);
- if('lang' in sett && sett['lan'] in codeq.supportedLangs){
+ if('lan' in sett && sett['lan'] in codeq.supportedLangs){
codeq.setLang(sett['lan']);
}
diff --git a/js/codeq/navigation.js b/js/codeq/navigation.js
index 80fd321..4eff834 100644
--- a/js/codeq/navigation.js
+++ b/js/codeq/navigation.js
@@ -97,6 +97,10 @@
codeq.globalStateMachine.transition('prolog');
e.preventDefault();
});
+ $('#navigation-robot').on('click', function(e){
+ codeq.globalStateMachine.transition('robot');
+ e.preventDefault();
+ });
$('#navigation-logout').on('click', function(e){
codeq.globalStateMachine.transition('login');
e.preventDefault();//prevent this since we'll trigger a page reload otherwise
@@ -106,4 +110,4 @@
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 d07e5fa..e3de85a 100644
--- a/js/codeq/problem.js
+++ b/js/codeq/problem.js
@@ -83,25 +83,51 @@
if (!translation || !(translation instanceof Array)) return false;
return translation.length > 0;
},
+ /**
+ * Structurally converts the plan definition into something that the
+ * hint module can work with.
+ * The input is the translations object from problem.json in the form
+ * {'sl': { ..., 'plan': [hint1_sl, hint2_sl, ...]}, 'en': { ..., 'plan': [hint1_en, hint2_en, ...]}, ...}
+ * The output is a list of hints in the plan, translations are in each hint:
+ * [{'sl': hint1_sl, 'en': hint1_en, ...}, {'sl': hint2_sl, 'en': hint2_en}, ...]
+ */
processPlans = function (rawTranslations) {
// find the default plan translation
var defaultPlan = chooseDefaultTranslation(rawTranslations, 'plan', defaultPlanCondition) || [],
- allPlans = {}, // the result
- i, lang, tr;
- // create all translations for plan
+ result = [],
+ i, lang, tr, plan, j, fragment;
+ if (defaultPlan.length == 0) return result; // no plan
+ // copy all translations from plan
for (i = langs.length - 1; i >= 0; i--) {
lang = langs[i];
tr = rawTranslations[lang];
// set up plan
if (tr && defaultPlanCondition(tr.plan)) {
- allPlans[lang] = tr.plan;
+ plan = tr.plan;
}
else {
// there's no plan in the current language, copy the default plan
- allPlans[lang] = defaultPlan;
+ plan = defaultPlan;
+ }
+ if (!(plan instanceof Array)) plan = [plan];
+ for (j = 0; j < plan.length; j++) {
+ if (j < result.length) fragment = result[j];
+ else {
+ fragment = {};
+ result.push(fragment);
+ }
+ fragment[lang] = plan[j];
}
}
- return allPlans;
+ // ensure each plan element has all translations
+ for (j = result.length - 1; j >= 0; j--) {
+ fragment = result[j];
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ if (!fragment[lang]) fragment[lang] = 'Missing plan for language ' + lang + ' at index ' + j;
+ }
+ }
+ return result;
},
// ================================================================================
@@ -240,7 +266,7 @@
.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.commonDef, userProblemData.solution);
+ codeq.globalStateMachine.transition(language, generalProblemData, data.commonDef, userProblemData.data.solution);
})
)
.fail(function (reason) {
@@ -356,4 +382,4 @@
$('#navigation-problem').css('display', 'none').removeClass("active");
}
});
-})(); \ No newline at end of file
+})();
diff --git a/js/codeq/python.js b/js/codeq/python.js
index 8a9e92f..59e010a 100644
--- a/js/codeq/python.js
+++ b/js/codeq/python.js
@@ -233,7 +233,7 @@
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);
+ jqBtnPlan.prop('disabled', !hinter.hasNextPlan());
editor.on('change', function (instance, changeObj) {
var doc = editor.getDoc(),
diff --git a/js/codeq/robot.js b/js/codeq/robot.js
new file mode 100644
index 0000000..f2f4a9b
--- /dev/null
+++ b/js/codeq/robot.js
@@ -0,0 +1,300 @@
+/**
+ * The robot state of the state machine. When it is entered it'll prepare the code editor and load a sub-state machine which represents the 3 different parts of 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_robot'), // 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'),
+ jqBtnRun = jqScreen.find('.btn-run'),
+ jqBtnStop = jqScreen.find('.btn-stop'),
+ jqInfoButtons = jqBtnPlan.add(jqBtnHint), // 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 robotHandler; //created when we enter the robot state and destroyed once we leave it
+ codeq.globalStateMachine.register('robot', {
+ 'enter': function (problemDef, commonDef, currentSolution) {
+ $('#navigation-language').css('display', '');
+ $('#navigation-problem').css('display', '');
+ $("#navigation-robot").addClass("active");
+ $('#navigation-robot').css('display', '');
+
+ jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly
+ robotHandler = createRobotHandler(problemDef, commonDef, 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');
+ robotHandler.destroy();
+ robotHandler = null;
+ subScreens.destroy();
+ subScreens = null;
+ jqScreen.addClass('block1');
+
+ $('#navigation-language').css('display', 'none');
+ $('#navigation-problem').css('display', 'none');
+ $("#navigation-robot").removeClass("active");
+ $('#navigation-robot').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 makeRobotTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
+ var terminal = codeq.makeConsole(jqConsole, {
+ 'greeting': 'CodeQ Robot terminal proxy',
+ 'autoHistory': true
+ });
+
+ terminal.onInput = function (text) {
+ terminal.append('Not implemented.\n', '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('robot', 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 createRobotHandler = function (problemDef, commonDef, 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 = makeRobotTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler),
+ hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'robot_hints', problemDef, commonDef),
+ commError = function (error) {
+ alert(error);
+ };
+
+ codeq.tr.registerDictionary('robot', problemDef.translations);
+ codeq.tr.translateDom(jqScreen);
+ if (currentSolution) editor.setValue(currentSolution);
+ jqBtnPlan.prop('disabled', !hinter.hasNextPlan());
+
+ 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': 'robot',
+ '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 url = 'http://' + $('#robot_ip').val() + ':8000/run',
+ doc = editor.getDoc();
+
+ $.ajax(url, {
+ method: 'POST',
+ data: JSON.stringify({program: doc.getValue()})
+ }).done(function (data) {
+ if (data.code === 0) {
+ terminal.append('<run>\n', 'output');
+ }
+ else {
+ terminal.append('error: ' + data.message + '\n');
+ }
+ }).fail(function (jqXHR, textStatus, err) {
+ commError('Could not access '+url);
+ });
+ });
+ jqBtnStop.on('click', function () {
+ var url = 'http://' + $('#robot_ip').val() + ':8000/stop';
+
+ $.ajax(url, {
+ method: 'POST'
+ }).done(function (data) {
+ if (data.code === 0) {
+ terminal.append('<stop>\n', 'output');
+ }
+ else {
+ terminal.append('error: ' + data.message + '\n');
+ }
+ }).fail(function (jqXHR, textStatus, err) {
+ commError('Could not access '+url);
+ });
+ });
+
+ return {
+ destroy: function () {
+ $('#screen_robot .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('robot', codeq.tr.emptyDictionary);
+ }
+ };
+ };
+})();
diff --git a/js/codeq/translation.js b/js/codeq/translation.js
index d4278ac..67f49eb 100644
--- a/js/codeq/translation.js
+++ b/js/codeq/translation.js
@@ -2,7 +2,7 @@
var dicts = {},
translateElement = function (jqElt, lang) {
- var dictionaryKey = jqElt.data('dict'),
+ var dictionaryKey = jqElt.data('dict') || 'gui',
translationKey = jqElt.data('tkey'),
dict = dicts[dictionaryKey],
translations, html, key;
@@ -43,7 +43,7 @@
jqElt.html(html);
},
translateDocument = function (lang) {
- $('.translatable').each(function () {
+ $('[data-tkey]').each(function () {
translateElement($(this), lang);
});
};
@@ -70,7 +70,10 @@
'translateDom': function (jqTopElt) {
var lang = codeq.getLang();
- jqTopElt.find('.translatable').each(function () {
+ jqTopElt.filter('[data-tkey]').each(function () {
+ translateElement(jqTopElt, lang)
+ });
+ jqTopElt.find('[data-tkey]').each(function () {
translateElement($(this), lang);
});
}
diff --git a/res/en.json b/res/en.json
new file mode 100644
index 0000000..5330cdc
--- /dev/null
+++ b/res/en.json
@@ -0,0 +1,15 @@
+{
+ "language": "Language",
+ "problem": "Problem",
+ "python": "Python",
+ "prolog": "Prolog",
+ "robot": "Robot",
+ "logout": "Logout",
+ "profile": "Profile",
+ "change_pass": "Change password",
+ "btn_plan": "Plan",
+ "btn_hint": "Hint",
+ "btn_test": "Test",
+ "btn_run": "Run",
+ "btn_stop": "Stop"
+} \ No newline at end of file
diff --git a/res/sl.json b/res/sl.json
new file mode 100644
index 0000000..cdde4f4
--- /dev/null
+++ b/res/sl.json
@@ -0,0 +1,15 @@
+{
+ "language": "Jezik",
+ "problem": "Problem",
+ "python": "Python",
+ "prolog": "Prolog",
+ "robot": "Robot",
+ "logout": "Odjava",
+ "profile": "Profil",
+ "change_pass": "Zamenjaj geslo",
+ "btn_plan": "Plan",
+ "btn_hint": "Namig",
+ "btn_test": "Testiraj",
+ "btn_run": "Zaženi",
+ "btn_stop": "Ustavi"
+} \ No newline at end of file