openct-tasks/_common/modules/integrationAPI.01/official/miniPlatform.js

603 lines
20 KiB
JavaScript

(function() {
'use strict';
/*
* Implementation of a small platform for standalone tasks, mostly for
* development, demo and testing purposes.
*
* Requirements:
* - jQuery
* - a Platform class creating a simple platform (present in the standard
* implementation of the integration API
* - task.getMetaData(), as documented in the PEM
*/
// demo platform key
var demo_key = 'buddy'
var languageStrings = {
ar: {
'task': 'Task',
'submission': 'Submission',
'solution': 'Solution',
'editor': 'Edit',
'hints': 'Hints',
'showSolution': 'Show solution',
'yourScore': "Your score:",
'canReadSolution': "You can now read the solution at the bottom of this page.",
'gradeAnswer': 'Test grader'
},
fr: {
'task': 'Exercice',
'submission': 'Soumission',
'solution': 'Solution',
'editor': 'Résoudre',
'hints': 'Conseils',
'showSolution': 'Voir la solution',
'yourScore': "Votre score :",
'canReadSolution': "Vous pouvez maintenant lire la solution en bas de la page.",
'gradeAnswer': "Tester le grader"
},
en: {
'task': 'Task',
'submission': 'Submission',
'solution': 'Solution',
'editor': 'Edit',
'hints': 'Hints',
'showSolution': 'Show solution',
'yourScore': "Your score:",
'canReadSolution': "You can now read the solution at the bottom of this page.",
'gradeAnswer': 'Test grader'
},
fi: {
'task': 'Task',
'submission': 'Submission',
'solution': 'Solution',
'editor': 'Edit',
'hints': 'Hints',
'showSolution': 'Show solution',
'yourScore': "Your score:",
'canReadSolution': "You can now read the solution at the bottom of this page.",
'gradeAnswer': 'Test grader'
},
sv: {
'task': 'Task',
'submission': 'Submission',
'solution': 'Solution',
'editor': 'Edit',
'hints': 'Hints',
'showSolution': 'Show solution',
'yourScore': "Your score:",
'canReadSolution': "You can now read the solution at the bottom of this page.",
'gradeAnswer': 'Test grader'
},
de: {
'task': 'Aufgabe',
'submission': 'Abgabe',
'solution': 'Lösung',
'editor': 'Bearbeiten',
'hints': 'Hinweise',
'showSolution': 'Lösung anzeigen',
'yourScore': "Dein Punktestand:",
'canReadSolution': "Du kannst dir jetzt die Lösung unten auf der Seite anschauen.",
'gradeAnswer': 'Test grader'
},
es: {
'task': 'Problema',
'submission': 'Sumisión',
'solution': 'Solución',
'editor': 'Editar',
'hints': 'Pistas',
'showSolution': 'Mostrar solución',
'yourScore': 'Su puntuación:',
'canReadSolution': 'Puede leer la solución al final de esta página.',
'gradeAnswer': 'Test grader'
}
};
function getLanguageString(key) {
// Default to english strings
var ls = languageStrings[window.stringsLanguage] ? languageStrings[window.stringsLanguage] : languageStrings['en'];
var str = ls[key];
return str ? str : '';
}
/*
* Create custom elements for platformless implementation
*/
var miniPlatformWrapping = {
beaver: {
'header' : '\
<div id="miniPlatformHeader">\
<table>\
<td><img src="' + (window.modulesPath?window.modulesPath:'../../../_common/modules') + '/img/castor.png" width="60px" style="display:inline-block;margin-right:20px;vertical-align:middle"/></td>\
<td><span class="platform">高阶思维能力测试</span></td>\
<td><a href="http://concours.castor-informatique.fr/" style="display:inline-block;text-align:right;">Le concours Castor</a></td>\
</table>\
</div>'
},
laptop: {
'header' : '\
<div style="width:100%; border-bottom:1px solid #B47238;overflow:hidden">\
<table style="width:770px;margin: 10px auto;">\
<td><img src="' + (window.modulesPath?window.modulesPath:'../../../_common/modules') + '/img/laptop.png" width="60px" style="display:inline-block;margin-right:20px;vertical-align:middle"/></td>\
<td><span class="platform">Concours Alkindi</span></td>\
<td><a href="http://concours-alkindi.fr/home.html#/" style="display:inline-block;text-align:right;">Le concours Alkindi</a></td>\
</table>\
</div>'
},
none: {
'header' : '<span></span>'
}
};
function inIframe() {
try {
return window.self !== window.top;
} catch (e) {
return false;
}
}
if(typeof window.jwt == 'undefined') {
window.jwt = {
isDummy: true,
sign: function() { return null; },
decode: function(token) { return token; }
};
}
function TaskToken(data, key) {
this.data = data
this.data.sHintsRequested = "[]";
this.key = key
var query = document.location.search.replace(/(^\?)/,'').split("&").map(function(n){return n = n.split("="),this[n[0]] = n[1],this}.bind({}))[0];
this.queryToken = query.sToken;
this.addHintRequest = function(hint_params, callback) {
try {
hint_params = jwt.decode(hint_params).askedHint;
} catch(e) {}
var hintsReq = JSON.parse(this.data.sHintsRequested);
var exists = hintsReq.find(function(h) {
return h == hint_params;
});
if(!exists) {
hintsReq.push(hint_params);
this.data.sHintsRequested = JSON.stringify(hintsReq);
}
return this.get(callback);
}
this.update = function(newData, callback) {
for(var key in newData) {
this.data[key] = newData[key];
}
}
this.getToken = function(data, callback) {
var res = jwt.sign(data, this.key)
if(callback) {
// imitate async req
setTimeout(function() {
callback(res)
}, 0);
}
return res;
}
this.get = function(callback) {
if(window.jwt.isDummy && this.queryToken) {
var token = this.queryToken;
if(callback) {
// imitate async req
setTimeout(function() {
callback(token)
}, 0);
}
return token;
}
return this.getToken(this.data, callback);
}
this.getAnswerToken = function(answer, callback) {
var answerData = {};
for(var key in this.data) {
answerData[key] = this.data[key];
}
answerData.sAnswer = answer;
return this.getToken(answerData, callback);
}
}
function AnswerToken(key) {
this.key = key
this.get = function(answer, callback) {
var res = jwt.sign(answer, this.key)
if(callback) {
// imitate async req
setTimeout(function() {
callback(res)
}, 0)
}
return res;
}
}
var taskMetaData;
// important for tracker.js
var compiledTask = true;
window.miniPlatformShowSolution = function() {
$("#showSolutionButton").hide();
task.getAnswer(function(answer) {
task.showViews({"task": true, "solution": true}, function() {
// For tasks with no feedback / older tasks
// miniPlatformPreviewGrade(answer);
platform.trigger('showViews', [{"task": true, "solution": true}]);
});
});
}
function miniPlatformPreviewGrade(answer) {
var minScore = -3;
if (taskMetaData.fullFeedback) {
minScore = 0;
}
var maxScore = 40;
var score;
var showGrade = function(score) {
if ($("#previewScorePopup").length === 0) {
$("<div id='previewScorePopup'><div style=\"background-color:#111;opacity: 0.65;filter:alpha(opacity=65);position:absolute;z-index:10;top:0px;left:0px;width:100%;height:2000px\"></div>" +
"<div style='position:fixed;top:100px;left:100px;width:400px;height:200px;background-color:#E0E0FF;color:black;border: solid black 3px;text-align:center;z-index:1000'>" +
"<div style='padding:50px'><span id='previewScoreMessage'></span><br/><br/><input type='button' onclick='$(\"#previewScorePopup\").remove()' value='OK' /></div></div></div>").insertBefore("#solution");
}
$("#previewScorePopup").show();
$("#previewScoreMessage").html("<b>" + getLanguageString('showSolution') + " " + score + "/" + maxScore + "</b><br/>" + getLanguageString('showSolution'));
};
// acceptedAnswers is not documented, but necessary for old Bebras tasks
if (taskMetaData.acceptedAnswers && taskMetaData.acceptedAnswers[0]) {
if ($.inArray("" + answer, taskMetaData.acceptedAnswers) > -1) {
score = maxScore;
}
else {
score = minScore;
}
showGrade(score);
} else {
score = grader.gradeTask(answer, null, showGrade);
}
}
var alreadyStayed = false;
var miniPlatformValidate = function(task) { return function(mode, success, error) {
//$.post('updateTestToken.php', {action: 'showSolution'}, function(){}, 'json');
if (mode == 'nextImmediate' || mode == 'log') {
return;
}
if (mode == 'stay') {
if (alreadyStayed) {
platform.trigger('validate', [mode]);
if (success) {
success();
}
} else {
alreadyStayed = true;
}
}
if (mode == 'cancel') {
alreadyStayed = false;
}
if(platform.registered_objects && platform.registered_objects.length > 0) {
platform.trigger('validate', [mode]);
} else {
// Try to validate
task.getAnswer(function(answer) {
task.gradeAnswer(answer, task_token.getAnswerToken(answer), function(score, message) {
if(success) { success(); }
})
});
}
if (success) {
success();
}
}};
function getUrlParameter(sParam)
{
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++)
{
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam)
{
return decodeURIComponent(sParameterName[1]);
}
}
}
function getHashParameter(sParam)
{
var sPageURL = window.location.hash.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++)
{
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam)
{
return decodeURIComponent(sParameterName[1]);
}
}
}
var chooseView = (function () {
// Manages the buttons to choose the view
return {
doubleEnabled: false,
isDouble: false,
lastShownViews: {},
init: function(views) {
if (! $("#choose-view").length)
$(document.body).append('<div id="choose-view" style="margin-top:6em"></div>');
$("#choose-view").html("");
// Display buttons to select task view or solution view
/*
for(var viewName in views) {
if (!views[viewName].requires) {
var btn = $('<button id="choose-view-'+viewName+'" class="btn btn-default choose-view-button">' + getLanguageString(viewName) + '</button>')
$("#choose-view").append(btn);
btn.click(this.selectFactory(viewName));
}
}
*/
$("#grade").remove();
var btnGradeAnswer = $('<center id="grade"><button class="btn btn-default">' + getLanguageString('gradeAnswer') + '</button></center>');
// display grader button only if dev mode by adding URL hash 'dev'
if (getHashParameter('dev')) {
$(document.body).append(btnGradeAnswer);
}
btnGradeAnswer.click(function() {
task.getAnswer(function(answer) {
answer_token.get(answer, function(answer_token) {
task.gradeAnswer(answer, answer_token, function(score, message, scoreToken) {
alert("Score : " + score + ", message : " + message);
});
})
}, function() {
alert("error");
});
})
},
reinit: function(views) {
this.init(views);
var newShownViews = {};
for(var viewName in this.lastShownViews) {
if(!this.lastShownViews[viewName]) { continue; }
if(views[viewName] && !views[viewName].requires) {
newShownViews[viewName] = true;
}
}
for(var viewName in views) {
if(views[viewName].includes) {
for(var i=0; i<views[viewName].includes.length; i++) {
if(this.lastShownViews[views[viewName].includes[i]]) {
newShownViews[viewName] = true;
}
}
}
}
this.update(newShownViews);
},
selectFactory: function(viewName) {
var that = this;
return function () {
that.select(viewName);
};
},
select: function(viewName) {
var that = this;
var shownViews = {};
shownViews[viewName] = true;
task.showViews(shownViews, function () {
that.update(shownViews);
});
},
update: function(shownViews) {
this.lastShownViews = shownViews;
$('.choose-view-button').removeClass('btn-info');
for(var viewName in shownViews) {
if(shownViews[viewName]) {
$('#choose-view-'+viewName).addClass('btn-info');
}
};
}
};
})();
window.task_token = new TaskToken({
itemUrl: window.location.href,
randomSeed: Math.floor(Math.random() * 10)
}, demo_key);
$(document).ready(function() {
var hasPlatform = false;
try {
hasPlatform = (inIframe() && (typeof parent.TaskProxyManager !== 'undefined') && (typeof parent.generating == 'undefined' || parent.generating === true));
var testEdge = parent.TaskProxyManager; // generates an exception on edge when in a platform (parent not available)
} catch(ex) {
// iframe from files:// url are considered cross-domain by Chrome
if(location.protocol !== 'file:') {
hasPlatform = true;
}
}
if (!hasPlatform) {
$('head').append('<link rel="stylesheet"type="text/css" href="' + (window.modulesPath?window.modulesPath:'../../../_common/modules') + '/integrationAPI.01/official/miniPlatform.css">');
var platformLoad = function(task) {
window.task_token.update({id: taskMetaData.id});
window.answer_token = new AnswerToken(demo_key)
platform.validate = miniPlatformValidate(task);
platform.updateHeight = function(height,success,error) {if (success) {success();}};
platform.updateDisplay = function(data,success,error) {
if(data.views) {
chooseView.reinit(data.views);
}
if (success) {success();}
};
var taskOptions = {};
try {
var strOptions = getUrlParameter("options");
if (strOptions !== undefined) {
taskOptions = $.parseJSON(strOptions);
}
} catch(exception) {
alert("Error: invalid options");
}
var minScore = -3;
if (taskMetaData.fullFeedback) {
minScore = 0;
}
platform.getTaskParams = function(key, defaultValue, success, error) {
var res = {'minScore': minScore, 'maxScore': 40, 'noScore': 0, 'readOnly': false, 'randomSeed': "0", 'options': taskOptions};
if (key) {
if (key !== 'options' && key in res) {
res = res[key];
} else if (res.options && key in res.options) {
res = res.options[key];
} else {
res = (typeof defaultValue !== 'undefined') ? defaultValue : null;
}
}
if (success) {
success(res);
} else {
return res;
}
};
platform.askHint = function(hint_params, success, error) {
/*
$.post('updateTestToken.php', JSON.stringify({action: 'askHint'}), function(postRes){
if (success) {success();}
}, 'json');
*/
task_token.addHintRequest(hint_params, function(token) {
task.updateToken(token, function() {})
success(token)
})
};
var loadedViews = {'task': true, 'solution': true, 'hints': true, 'editor': true, 'grader': true, 'metadata': true, 'submission': true};
var shownViews = {'task': true};
// TODO: modifs ARTHUR à relire
if (taskOptions.showSolutionOnLoad) {
shownViews.solution = true;
}
if (!taskOptions.hideTitle) {
$("#task h1").show();
if ($("#task h1").length)
document.title = $("#task h1:first").text();
}
if (taskMetaData.fullFeedback) {
loadedViews.grader = true;
}
task.load(
loadedViews,
function() {
platform.trigger('load', [loadedViews]);
task.getViews(function(views) {
chooseView.init(views);
});
task.showViews(shownViews, function() {
chooseView.update(shownViews);
platform.trigger('showViews', [{"task": true}]);
});
if ($("#solution").length) {
$("#task").append("<center id='showSolutionButton'><button type='button' class='btn btn-default' onclick='miniPlatformShowSolution()'>" + getLanguageString('showSolution') + "</button></center>");
}
// add branded header to platformless task depending on avatarType
// defaults to beaver platform branding
if(window.displayHelper) {
if (miniPlatformWrapping[displayHelper.avatarType].header) {
$('body').prepend(miniPlatformWrapping[displayHelper.avatarType].header);
} else {
$('body').prepend(miniPlatformWrapping[beaver].header);
}
}
},
function(error) {
console.error(error)
}
);
task_token.get(function(token) {
task.updateToken(token, function() {})
})
/* For the 'resize' event listener below, we use a cross-browser
* compatible version for "addEventListener" (modern) and "attachEvent" (old).
* Source: https://stackoverflow.com/questions/6927637/addeventlistener-in-internet-explorer
*/
function addEvent(evnt, elem, func) {
if (elem.addEventListener) // W3C DOM
elem.addEventListener(evnt,func,false);
else if (elem.attachEvent) { // IE DOM
elem.attachEvent("on"+evnt, func);
}
else { // No much to do
elem[evnt] = func;
}
}
addEvent('resize', window, function() {
task.getViews(function(views) {
chooseView.reinit(views);
});
});
};
var getMetaDataAndLoad = function(task) {
task.getMetaData(function(metaData) {
taskMetaData = metaData;
platformLoad(task);
});
};
if (window.platform.task || platform.initFailed) {
// case everything went fine with task loading, or task loading failed
// (due to missing jschannel and file:// protocol...
getMetaDataAndLoad(window.task ? window.task : window.platform.task);
} else {
// task is not loaded yet
var oldInit = platform.initWithTask;
platform.initWithTask = function(task) {
oldInit(task);
getMetaDataAndLoad(task);
};
}
}
});
})();