summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleš Smodiš <aless@guru.si>2015-09-24 14:31:13 +0200
committerAleš Smodiš <aless@guru.si>2015-09-24 14:31:13 +0200
commit3184e7f201af05429bedb4fb8a6300e024946c2a (patch)
treee660f5b50fd56cb7ac51deb32d8ab4a8f98552e3
parent652164680ac0d1b4ace0ae9ec55f69b28740d11a (diff)
Implemented: CodeQ event queue, basic translation infrastructure, reimplemented problem index from JSON data.
-rw-r--r--css/codeq.css14
-rw-r--r--index.html31
-rw-r--r--js/codeq/comms.js3
-rw-r--r--js/codeq/core.js111
-rw-r--r--js/codeq/problem.js138
-rw-r--r--js/codeq/statusbar.js26
6 files changed, 271 insertions, 52 deletions
diff --git a/css/codeq.css b/css/codeq.css
index 8604b9c..d293787 100644
--- a/css/codeq.css
+++ b/css/codeq.css
@@ -176,7 +176,21 @@ body {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
}
+/* status bar */
+ul.dropdown-menu a {
+ cursor: pointer;
+}
+
+.lang-selection ul.dropdown-menu {
+ min-width: 4em;
+}
+
/* main screen */
#screen_language a {
cursor: pointer;
}
+
+/* problem index screen */
+#screen_problem .language-problems a {
+ cursor: pointer;
+} \ No newline at end of file
diff --git a/index.html b/index.html
index 31e383b..1dfe0e0 100644
--- a/index.html
+++ b/index.html
@@ -41,6 +41,12 @@
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<p class="navbar-text" id="signed-in-title">Signed in as Franc Jožef</p>
+ <li class="dropdown lang-selection">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true">
+ <span class="lang-choice"></span>
+ </a>
+ <ul class="dropdown-menu"></ul>
+ </li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true">
<span class="glyphicon glyphicon glyphicon-user"></span>
@@ -91,27 +97,11 @@
</div>
<!-- problem selection screen for a specific language: groups of problems, with descriptions -->
- <div id="screen_problem" style="text-align: center; display: none;">
- <h1>CodeQ Select Problem</h1>
+ <div id="screen_problem" style="display: none;">
+ <h1 class="language-title"></h1>
<hr>
- <table style="margin: 0 auto;">
- <tbody>
- <tr>
- <td style="text-align: right;">Problem:</td>
- <td>
- <select name="problem_group" id="problem_group" style="min-width: 10em;">
- </select>
- <select name="problem" id="problems" style="min-width: 10em;">
- </select>
- </td>
- </tr>
- <tr>
- <td colspan="2" style="text-align: center;">
- <button type="button" id="submit_problem">Select</button>
- </td>
- </tr>
- </tbody>
- </table>
+ <div class="language-description"></div>
+ <ul class="language-problems"></ul>
</div>
<!-- problem screen: prolog -->
@@ -198,6 +188,7 @@
<script src="js/codemirror/show-hint.js"></script>
<!-- codeq app -->
<script src="js/codeq/core.js"></script>
+ <script src="js/codeq/statusbar.js"></script>
<script src="js/codeq/navigation.js"></script>
<script src="js/codeq/comms.js"></script>
<script src="js/codeq/console.js"></script>
diff --git a/js/codeq/comms.js b/js/codeq/comms.js
index f9e49ae..2d934bd 100644
--- a/js/codeq/comms.js
+++ b/js/codeq/comms.js
@@ -169,7 +169,7 @@
ajaxGet = function (url) {
return Q.Promise(function (resolve, reject, notify) {
$.ajax({
- dataType: 'application/json',
+ dataType: 'json',
type: 'GET',
url: url,
error: function (jqXHR, textStatus, errorThrown) {
@@ -206,6 +206,7 @@
ajaxPrefix[ajaxPrefix.length - 1] = '';
ajaxPrefix = ajaxPrefix.join('/');
}
+ ajaxPrefix = ajaxPrefix + 'data/';
// ================================================================================
// This module's API methods
diff --git a/js/codeq/core.js b/js/codeq/core.js
index 61ef607..148cdeb 100644
--- a/js/codeq/core.js
+++ b/js/codeq/core.js
@@ -147,7 +147,10 @@
regexpWhiteSpaceBeforeTag = new RegExp('[ \r\n\t]+(?=<)', 'g'),
regexpWhiteSpaceAfterTag = new RegExp('>[ \r\n\t]+', 'g'),
regexpWhiteSpace = new RegExp('[ \\r\\n\\t]+'),
- regexpWhiteSpaceTrim = new RegExp('^[ \\t\\r\\n]*(.*[^ \\t\\r\\n])[ \\t\\r\\n]*$', 'm');
+ regexpWhiteSpaceTrim = new RegExp('^[ \\t\\r\\n]*(.*[^ \\t\\r\\n])[ \\t\\r\\n]*$', 'm'),
+ regexpAmp = new RegExp('&', 'g'),
+ regexpLt = new RegExp('<', 'g'),
+ regexpGt = new RegExp('>', 'g');
// convert a string into its definition (javascript literal)
var stringToDef = function (str) {
@@ -160,11 +163,76 @@
return html.replace(regexpWhiteSpaceBeforeTag, '').split(regexpWhiteSpaceAfterTag).join('>');
};
+ var lang = 'en'; // this is overridden in the boot sequence below, if the browser uses a supported language
+
+ var eventListeners = {}, // keyed by event name, value is an array of listeners
+ asyncTimer = null,
+ queuedEvents = [],
+ fireEvents = function () {
+ if (asyncTimer !== null) return;
+ asyncTimer = setTimeout(function () {
+ var N = queuedEvents.length,
+ i, event, args, listeners, j;
+ asyncTimer = null;
+ for (i = 0; i < N; i++) {
+ event = queuedEvents[i];
+ listeners = eventListeners[event.name];
+ if (!listeners) continue;
+ args = event.args;
+ listeners = listeners.slice(); // make a copy of the list, so we're unaffected of any changes
+ for (j = 0; j < listeners.length; j++) {
+ try {
+ listeners[j](args);
+ }
+ catch (e) {
+ codeq.log.error('Error while invoking an event handler for ' + event.name + ': ' + e, e);
+ }
+ }
+ }
+ queuedEvents.splice(0, N);
+ if (queuedEvents.length > 0) fireEvents();
+ }, 0);
+ };
+
window.codeq = {
'jsonize': jsonize,
'log': log,
+ 'supportedLangs': {
+ 'en': 'English',
+ 'sl': 'Slovenščina'
+ },
+ 'getLang': function () {
+ return lang;
+ },
+
+ 'setLang': function (newLang) {
+ lang = newLang;
+ codeq.fire('langchange', {'lang': newLang});
+ },
+
+ 'chooseTranslation': function (translations, language) {
+ var tr, lang;
+ if ((typeof translations !== 'object') || (translations === null)) return {};
+ tr = translations[language || codeq.lang];
+ if ((typeof tr === 'object') && (tr !== null)) return tr;
+ // default fallback
+ tr = translations['en'];
+ if ((typeof tr === 'object') && (tr !== null)) return tr;
+ // fallback to whatever is available
+ for (lang in translations) {
+ tr = translations[lang];
+ if ((typeof tr === 'object') && (tr !== null)) return tr;
+ }
+ // all options were exhausted, we have nothing
+ return {};
+ },
+
+ 'escapeHtml': function (s) {
+ return ('' + s).replace(regexpAmp, '&amp;').replace(regexpLt, '&lt;').replace(regexpGt, '&gt;');
+ },
+
/**
* Returns the number of Unicode code points in the given string.
*
@@ -262,6 +330,38 @@
if ((typeof args !== 'object') || (args === null)) args = {};
return f.apply(args);
};
+ },
+
+ // codeq event handling
+
+ 'fire': function (eventName, args) {
+ queuedEvents.push({'name': eventName, 'args': args});
+ fireEvents();
+ },
+
+ 'on': function (eventName, callback) {
+ var listeners = eventListeners[eventName],
+ i;
+ if (listeners) {
+ for (i = listeners.length - 1; i >= 0; i--) {
+ if (listeners[i] === callback) return; // already registered
+ }
+ }
+ else {
+ listeners = [];
+ eventListeners[eventName] = listeners;
+ }
+ listeners.push(callback);
+ },
+
+ 'off': function (eventName, callback) {
+ var listeners = eventListeners[eventName],
+ i;
+ if (listeners) {
+ for (i = listeners.length - 1; i >= 0; i--) {
+ if (listeners[i] === callback) listeners.splice(i, 1);
+ }
+ }
}
};
@@ -270,6 +370,15 @@
// ================================================================================
$(document).ready(function () {
+ // set the language
+ var lang = navigator.language || navigator.browserLanguage;
+ if (typeof lang === 'string') {
+ lang = lang.split('-')[0]; // truncate the language variant, in eg. en-US
+ if (lang in codeq.supportedLangs) codeq.lang = lang;
+ }
+ codeq.setLang(lang); // initial language setting
+
+ // go to login
codeq.globalStateMachine.transition('login');
});
})();
diff --git a/js/codeq/problem.js b/js/codeq/problem.js
index f19cbd4..fcb5d8a 100644
--- a/js/codeq/problem.js
+++ b/js/codeq/problem.js
@@ -1,33 +1,112 @@
-/**
- * Created by robert on 9/18/15.
- */
-
-/**
- * Created by robert on 9/18/15.
- */
-
(function(){
- var lastLanguage;
+ var jqScreen = $('#screen_problem'),
+ jqLanguageTitle = jqScreen.find('.language-title'),
+ jqLanguageDescription = jqScreen.find('.language-description'),
+ jqLanguageProblems = jqScreen.find('.language-problems'),
+ languageCache = {}, // keyed by language identifier
+ currentData = null, // currently active data
+ createLanguageData = function (data, languageIdentifier, lang) { // data is the content of language.json
+ var trLanguage = codeq.chooseTranslation(data.translations, lang),
+ groups = data.groups || {},
+ languageHints = trLanguage.hint || {},
+ html = [],
+ problemReferences = [],
+ groupIdentifier, group, trGroup, problems, problemIdentifier, problem, trProblem;
+ for (groupIdentifier in groups) {
+ if (!groups.hasOwnProperty(groupIdentifier)) continue;
+ group = groups[groupIdentifier] || {};
+ trGroup = codeq.chooseTranslation(group.translations, lang);
+ html.push('<li><div class="group-title">', codeq.escapeHtml(trGroup.name || 'Group name not set: ' + groupIdentifier), '</div>');
+ html.push('<div class="group-description">', codeq.escapeHtml(trGroup.description || 'Group description not set: ' + groupIdentifier), '</div>');
+ html.push('<ul class="group-problems">');
+ problems = group.problems || {};
+ for (problemIdentifier in problems) {
+ if (!problems.hasOwnProperty(problemIdentifier)) continue;
+ problem = problems[problemIdentifier];
+ trProblem = codeq.chooseTranslation(problem.translations, lang);
+ html.push('<li><a class="problem-', '' + problemReferences.length, '">', codeq.escapeHtml(trProblem.name || 'Problem name not set: ' + problemIdentifier), '</a></li>');
+ problemReferences.push({'g': groupIdentifier, 'p': problemIdentifier});
+ }
+ html.push('</ul></li>');
+ }
+ return {
+ 'name': trLanguage.name || 'Language name not set: ' + languageIdentifier,
+ 'description': trLanguage.description || 'Description not set: ' + languageIdentifier,
+ 'html': html.join(''),
+ 'refs': problemReferences,
+ 'hints': trLanguage.hint || {}
+ };
+ },
+ onLangChange = function (args) {
+ purgeDom();
+ createDom(currentData);
+ },
+ createDom = function (languageData) {
+ var lang = codeq.getLang(),
+ language = languageData.identifier,
+ data = languageData[lang];
+ if (!data) {
+ data = createLanguageData(languageData.raw, language, lang);
+ languageData[lang] = data;
+ }
+ jqLanguageTitle.html(data.name);
+ jqLanguageDescription.html(data.description);
+ jqLanguageProblems.html(data.html);
+ jqLanguageProblems.find('a').on('click', function () {
+ var index = +$(this).attr('class').split('-')[1],
+ ref = data.refs[index];
+ if (!ref) {
+ 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);
+ })
+ )
+ .fail(function (reason) {
+ codeq.log.error('Failed to obtain the problem definition: ' + reason, reason);
+ alert('Failed to obtain the problem definition: ' + reason);
+ })
+ .done();
+ });
+ codeq.on('langchange', onLangChange);
+ },
+ purgeDom = function () {
+ jqLanguageProblems.find('a').off();
+ jqLanguageProblems.empty();
+ jqLanguageDescription.empty();
+ jqLanguageTitle.empty();
+ codeq.off('langchange', onLangChange);
+ },
+ lastLanguage;
+
codeq.globalStateMachine.register('problem',{
'enter': function(language){
- if(language)lastLanguage = language;
- else language = lastLanguage;//This happens when we hit this with the back button
-
- $('#disabled').css('display', '');
- $('#disabled').css('cursor', 'wait');
+ if (language) lastLanguage = language;
+ else language = lastLanguage; // This happens when we hit this with the back button
$('#navigation-login').css('display', '');
- /*$('#navigation-login').on('click', function(){
- codeq.globalStateMachine.transition('login');
- });*/
$('#navigation-language').css('display', '');
- /*$('#navigation-language').on('click', function(){
- codeq.globalStateMachine.transition('language');
- });*/
- $("#navigation-problem").addClass("active");
- $('#navigation-problem').css('display', '');
+ $("#navigation-problem").addClass("active").css('display', '');
+ jqScreen.css('display', '');
+
+ currentData = languageCache[language];
+ if (currentData) {
+ createDom(currentData);
+ }
+ else {
+ codeq.wait(codeq.comms.getLanguageDef(language).then(function (rawData) {
+ currentData = {'raw': rawData, 'identifier': language};
+ languageCache[language] = currentData;
+ createDom(currentData);
+ })).done();
+ }
- codeq.comms.send({'action': 'list_problems', 'language':language})
+/* codeq.comms.send({'action': 'list_problems', 'language':language})
.then(
function success(data) {
var i, groups, group, problems, problem, first_group,
@@ -107,21 +186,20 @@
$('#disabled').css('cursor', '');
alert('Request to obtain list of problems failed: ' + reason);
}
- ).done();
+ ).done();*/
},
'exit' : function(){
- $('#problem_group').off();
+ purgeDom();
+/* $('#problem_group').off();
$("#submit_problem").off();
$("#problem_group option").remove();//empty the selects
$("#problems option").remove();
- $("#screen_problem").css('display', 'none');
+ $("#screen_problem").css('display', 'none');*/
+ jqScreen.css('display', 'none');
$('#navigation-login').css('display', 'none');
- //$('#navigation-login').off();
$('#navigation-language').css('display', 'none');
- //$('#navigation-language').off();
- $('#navigation-problem').css('display', 'none');
- $("#navigation-problem").removeClass("active");
+ $('#navigation-problem').css('display', 'none').removeClass("active");
}
});
})(); \ No newline at end of file
diff --git a/js/codeq/statusbar.js b/js/codeq/statusbar.js
new file mode 100644
index 0000000..2134b93
--- /dev/null
+++ b/js/codeq/statusbar.js
@@ -0,0 +1,26 @@
+(function () {
+
+ var jqBar = $('#topbar'),
+ jqLang = jqBar.find('.lang-selection'),
+ jqLangChoice = jqLang.find('.lang-choice');
+
+ (function () {
+ var langs = codeq.supportedLangs,
+ jqMenu = jqBar.find('.dropdown-menu'),
+ lang, cssClass;
+ for (lang in langs) {
+ if (!langs.hasOwnProperty(lang)) continue;
+ cssClass = 'lang-' + lang;
+ jqMenu.append('<li><a class="' + cssClass + '">' + lang + '</a></li>');
+ jqMenu.find('.' + cssClass).on('click', (function (l) {return function () {codeq.setLang(l)};})(lang));
+ }
+ })();
+
+ codeq.on('langchange', function (params) {
+ jqLangChoice.text(params.lang);
+ });
+
+ codeq.statusbar = {
+
+ };
+})(); \ No newline at end of file