forked from Open-CT/openct-tasks
3017 lines
122 KiB
JavaScript
3017 lines
122 KiB
JavaScript
var getUrlParameter = function getUrlParameter(sParam) {
|
|
var sPageURL = decodeURIComponent(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 sParameterName[1] === undefined ? true : sParameterName[1];
|
|
}
|
|
}
|
|
};
|
|
|
|
var arrayContains = function(array, needle) {
|
|
for (var index in array) {
|
|
if (needle == array[index]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
var highlightPause = false;
|
|
|
|
function resetFormElement(e) {
|
|
e.wrap('<form>').closest('form').get(0).reset();
|
|
e.unwrap();
|
|
|
|
// Prevent form submission
|
|
//e.stopPropagation();
|
|
//e.preventDefault();
|
|
}
|
|
|
|
var languageStrings = {
|
|
fr: {
|
|
categories: {
|
|
actions: "Actions",
|
|
sensors: "Capteurs",
|
|
debug: "Débuggage",
|
|
colour: "Couleurs",
|
|
dicts: "Dictionnaires",
|
|
input: "Entrées",
|
|
lists: "Listes",
|
|
logic: "Logique",
|
|
loops: "Boucles",
|
|
control: "Contrôles",
|
|
operator: "Opérateurs",
|
|
math: "Maths",
|
|
text: "Texte",
|
|
variables: "Variables",
|
|
functions: "Fonctions",
|
|
read: "Lecture",
|
|
print: "Écriture",
|
|
turtle: "Tortue"
|
|
},
|
|
invalidContent: "Contenu invalide",
|
|
unknownFileType: "Type de fichier non reconnu",
|
|
download: "télécharger",
|
|
smallestOfTwoNumbers: "Plus petit des deux nombres",
|
|
greatestOfTwoNumbers: "Plus grand des deux nombres",
|
|
programOfRobot: "Programme du robot",
|
|
flagClicked: "Quand %1 cliqué",
|
|
tooManyIterations: "Votre programme met trop de temps à se terminer !",
|
|
tooManyIterationsWithoutAction: "Votre programme s'est exécuté trop longtemps sans effectuer d'action !",
|
|
submitProgram: "Valider le programme",
|
|
runProgram: "Exécuter sur ce test",
|
|
speed: "Vitesse :",
|
|
stopProgram: "|<",
|
|
stepProgram: "|>",
|
|
slowSpeed: ">",
|
|
mediumSpeed: ">>",
|
|
fastSpeed: ">>>",
|
|
ludicrousSpeed: ">|",
|
|
stopProgramDesc: "Repartir du début",
|
|
stepProgramDesc: "Exécution pas-à-pas",
|
|
slowSpeedDesc: "Exécuter sur ce test",
|
|
mediumSpeedDesc: "Vitesse moyenne",
|
|
fastSpeedDesc: "Vitesse rapide",
|
|
ludicrousSpeedDesc: "Vitesse très rapide",
|
|
selectLanguage: "Langage :",
|
|
blocklyLanguage: "Blockly",
|
|
javascriptLanguage: "Javascript",
|
|
importFromBlockly: "Repartir de blockly",
|
|
saveOrLoadProgram: "Enregistrer ou recharger votre programme :",
|
|
avoidReloadingOtherTask: "Attention : ne rechargez pas le programme d'un autre sujet !",
|
|
files: "Fichiers",
|
|
reloadProgram: "Recharger :",
|
|
saveProgram: "Enregistrer",
|
|
limitBlocks: "{remainingBlocks} blocs restants sur {maxBlocks} autorisés.",
|
|
limitBlocksOver: "{remainingBlocks} blocs en trop utilisés pour {maxBlocks} autorisés.",
|
|
previousTestcase: "Précédent",
|
|
nextTestcase: "Suivant",
|
|
allTests: "Tous les tests :",
|
|
emptyProgram: "Le programme est vide ! Connectez des blocs.",
|
|
tooManyBlocks: "Vous utilisez trop de blocs !",
|
|
uninitializedVar: "Variable non initialisée :",
|
|
valueTrue: 'vrai',
|
|
valueFalse: 'faux',
|
|
correctAnswer: 'Réponse correcte',
|
|
partialAnswer: 'Réponse améliorable',
|
|
wrongAnswer: 'Réponse incorrecte',
|
|
resultsNoSuccess: "Vous n'avez validé aucun test.",
|
|
resultsPartialSuccess: "Vous avez validé seulement {nbSuccess} test(s) sur {nbTests}.",
|
|
gradingInProgress: "Évaluation en cours"
|
|
},
|
|
en: {
|
|
categories: {
|
|
actions: "Actions",
|
|
sensors: "Sensors",
|
|
debug: "Debug",
|
|
colour: "Colors",
|
|
dicts: "Dictionnaries",
|
|
input: "Input",
|
|
lists: "Lists",
|
|
logic: "Logic",
|
|
loops: "Loops",
|
|
math: "Math",
|
|
text: "Text",
|
|
variables: "Variables",
|
|
functions: "Functions",
|
|
read: "Reading",
|
|
print: "Writing",
|
|
turtle: "Turtle"
|
|
},
|
|
invalidContent: "Invalid content",
|
|
unknownFileType: "Unrecognized file type",
|
|
download: "download",
|
|
smallestOfTwoNumbers: "Smallest of the two numbers",
|
|
greatestOfTwoNumbers: "Greatest of the two numbers",
|
|
programOfRobot: "Robot's program",
|
|
flagClicked: "When %1 clicked",
|
|
tooManyIterations: "Too many iterations!",
|
|
tooManyIterationsWithoutAction: "Too many iterations without action!",
|
|
submitProgram: "Validate this program",
|
|
runProgram: "Run this program",
|
|
speed: "Speed:",
|
|
stopProgram: "|<",
|
|
stepProgram: "|>",
|
|
slowSpeed: ">",
|
|
mediumSpeed: ">>",
|
|
fastSpeed: ">>>",
|
|
ludicrousSpeed: ">|",
|
|
stopProgramDesc: "Restart from the beginning",
|
|
stepProgramDesc: "Step-by-step execution",
|
|
slowSpeedDesc: "Execute on this test",
|
|
mediumSpeedDesc: "Average speed",
|
|
fastSpeedDesc: "Fast speed",
|
|
ludicrousSpeedDesc: "Ludicrous speed",
|
|
selectLanguage: "Language :",
|
|
blocklyLanguage: "Blockly",
|
|
javascriptLanguage: "Javascript",
|
|
importFromBlockly: "Generate from blockly",
|
|
saveOrLoadProgram: "Save or reload your code:",
|
|
avoidReloadingOtherTask: "Warning: do not reload code for another task!",
|
|
reloadProgram: "Reload:",
|
|
saveProgram: "Save",
|
|
limitBlocks: "{remainingBlocks} blocks remaining out of {maxBlocks} available.",
|
|
limitBlocksOver: "{remainingBlocks} blocks over the limit of {maxBlocks} available.",
|
|
previousTestcase: "Previous",
|
|
nextTestcase: "Next",
|
|
allTests: "All tests:",
|
|
emptyProgram: "The program is empty! Connect some blocks.",
|
|
tooManyBlocks: "You use too many blocks!",
|
|
uninitializedVar: "Uninitialized variable:",
|
|
valueTrue: 'true',
|
|
valueFalse: 'false',
|
|
correctAnswer: 'Correct answer',
|
|
partialAnswer: 'Partial answer',
|
|
wrongAnswer: 'Wrong answer',
|
|
resultsNoSuccess: "You passed none of the tests.",
|
|
resultsPartialSuccess: "You passed only {nbSuccess} test(s) of {nbTests}.",
|
|
gradingInProgress: "Grading in process"
|
|
},
|
|
de: {
|
|
categories: {
|
|
actions: "Aktionen",
|
|
sensors: "Sensoren",
|
|
debug: "Debug",
|
|
colour: "Farben",
|
|
dicts: "Hash-Map",
|
|
input: "Eingabe",
|
|
lists: "Listen",
|
|
logic: "Logik",
|
|
loops: "Schleifen",
|
|
math: "Mathe",
|
|
text: "Text",
|
|
variables: "Variablen",
|
|
functions: "Funktionen",
|
|
read: "Einlesen",
|
|
print: "Ausgeben",
|
|
turtle: "Turtle"
|
|
},
|
|
invalidContent: "Ungültiger Inhalt",
|
|
unknownFileType: "Ungültiger Datentyp",
|
|
download: "Herunterladen",
|
|
smallestOfTwoNumbers: "Kleinste von zwei Zahlen",
|
|
greatestOfTwoNumbers: "Größte von zwei Zahlen",
|
|
programOfRobot: "Programm des Roboters",
|
|
flagClicked: "When %1 clicked", // TODO :: translate (scratch start flag, %1 is the flag icon)
|
|
tooManyIterations: "Zu viele Iterationen!",
|
|
tooManyIterationsWithoutAction: "Zu viele Iterationen vor einer Aktion!",
|
|
submitProgram: "Programm überprüfen lassen",
|
|
runProgram: "Programm ausführen",
|
|
speed: "Ablaufgeschwindigkeit:",
|
|
stopProgram: "|<",
|
|
stepProgram: "|>",
|
|
slowSpeed: ">",
|
|
mediumSpeed: ">>",
|
|
fastSpeed: ">>>",
|
|
ludicrousSpeed: ">|",
|
|
stopProgramDesc: "Restart from the beginning", // TODO :: translate and next 5
|
|
stepProgramDesc: "Step-by-step execution",
|
|
slowSpeedDesc: "Execute on this test",
|
|
mediumSpeedDesc: "Average speed",
|
|
fastSpeedDesc: "Fast speed",
|
|
ludicrousSpeedDesc: "Ludicrous speed",
|
|
selectLanguage: "Sprache:",
|
|
blocklyLanguage: "Blockly",
|
|
javascriptLanguage: "Javascript",
|
|
importFromBlockly: "Generiere von Blockly-Blöcken",
|
|
saveOrLoadProgram: "Speicher oder lade deinen Quelltext:",
|
|
avoidReloadingOtherTask: "Warnung: Lade keinen Quelltext von einer anderen Aufgabe!",
|
|
reloadProgram: "Laden:",
|
|
saveProgram: "Speichern",
|
|
limitBlocks: "Noch {remainingBlocks} von {maxBlocks} Blöcken verfügbar.",
|
|
limitBlocksOver: "{remainingBlocks} blocks over the limit of {maxBlocks} available.", // TODO :: translate
|
|
previousTestcase: "Vorheriger",
|
|
nextTestcase: "Nächster",
|
|
allTests: "Tous le tests :",
|
|
emptyProgram: "Le programme est vide ! Connectez des blocs.", // TODO :: translate this one and next 9
|
|
tooManyBlocks: "You use too many blocks!",
|
|
uninitializedVar: "Uninitialized variable:",
|
|
valueTrue: 'true',
|
|
valueFalse: 'false',
|
|
correctAnswer: 'Correct answer',
|
|
partialAnswer: 'Partial answer',
|
|
wrongAnswer: 'Wrong answer',
|
|
resultsNoSuccess: "You passed none of the tests.",
|
|
resultsPartialSuccess: "You passed only {nbSuccess} test(s) of {nbTests}.",
|
|
gradingInProgress: "Das Ergebnis wird ausgewertet …"
|
|
},
|
|
es: {
|
|
categories: {
|
|
actions: "Acciones",
|
|
sensors: "Sensores",
|
|
debug: "Depurar",
|
|
colour: "Colores",
|
|
dicts: "Diccionarios",
|
|
input: "Entradas",
|
|
lists: "Listas",
|
|
logic: "Lógica",
|
|
loops: "Bucles",
|
|
control: "Control",
|
|
operator: "Operadores",
|
|
math: "Mate",
|
|
text: "Texto",
|
|
variables: "Variables",
|
|
functions: "Funciones"
|
|
},
|
|
invalidContent: "Contenido inválido",
|
|
unknownFileType: "Tipo de archivo no reconocido",
|
|
download: "descargar",
|
|
smallestOfTwoNumbers: "El menor de dos números",
|
|
greatestOfTwoNumbers: "El mayor de dos números",
|
|
programOfRobot: "Programa de robot",
|
|
flagClicked: "Cuando se hace click en %1",
|
|
tooManyIterations: "¡Su programa se tomó demasiado tiempo para terminar!",
|
|
tooManyIterationsWithoutAction: "¡Su programa se tomó demasiado tiempo para terminar!", // TODO :: change translation
|
|
submitProgram: "Validar el programa",
|
|
runProgram: "Ejecutar el programa",
|
|
speed: "Velocidad:",
|
|
stopProgram: "|<",
|
|
stepProgram: "|>",
|
|
slowSpeed: ">",
|
|
mediumSpeed: ">>",
|
|
fastSpeed: ">>>",
|
|
ludicrousSpeed: ">|",
|
|
stopProgramDesc: "Restart from the beginning", // TODO :: translate and next 5
|
|
stepProgramDesc: "Step-by-step execution",
|
|
slowSpeedDesc: "Execute on this test",
|
|
mediumSpeedDesc: "Average speed",
|
|
fastSpeedDesc: "Fast speed",
|
|
ludicrousSpeedDesc: "Ludicrous speed",
|
|
selectLanguage: "Lenguaje:",
|
|
blocklyLanguage: "Blockly",
|
|
javascriptLanguage: "Javascript",
|
|
importFromBlockly: "Generar desde blockly",
|
|
saveOrLoadProgram: "Guardar o cargar su programa:",
|
|
avoidReloadingOtherTask: "Atención: ¡no recargue el programa de otro problema!",
|
|
files: "Archivos",
|
|
reloadProgram: "Recargar:",
|
|
saveProgram: "Guardar",
|
|
limitBlocks: "{remainingBlocks} bloques disponibles de {maxBlocks} autorizados.",
|
|
limitBlocksOver: "{remainingBlocks} bloques sobre el límite de {maxBlocks} autorizados.",
|
|
previousTestcase: "Anterior",
|
|
nextTestcase: "Siguiente",
|
|
allTests: "Todas las pruebas:",
|
|
emptyProgram: "¡El programa está vacio! Conecta algunos bloques",
|
|
emptyProgram: "Le programme est vide ! Connectez des blocs.", // TODO :: translate this one and next 6
|
|
uninitializedVar: "Uninitialized variable:",
|
|
valueTrue: 'true',
|
|
valueFalse: 'false',
|
|
correctAnswer: 'Correct answer',
|
|
partialAnswer: 'Partial answer',
|
|
wrongAnswer: 'Wrong answer',
|
|
resultsNoSuccess: "You passed none of the tests.",
|
|
resultsPartialSuccess: "You passed only {nbSuccess} test(s) of {nbTests}.",
|
|
gradingInProgress: "Evaluación en curso"
|
|
}
|
|
};
|
|
|
|
// Blockly to Scratch translations
|
|
var blocklyToScratch = {
|
|
singleBlocks: {
|
|
'controls_if': ['control_if'],
|
|
'controls_if_else': ['control_if_else'],
|
|
'controls_repeat': ['control_repeat'],
|
|
'controls_repeat_ext': ['control_repeat'],
|
|
'controls_whileUntil': ['control_repeat_until'],
|
|
'controls_untilWhile': ['control_repeat_until'],
|
|
'logic_negate': ['operator_not'],
|
|
'logic_boolean': [],
|
|
'logic_compare': ['operator_equals', 'operator_gt', 'operator_lt'],
|
|
'logic_operation': ['operator_and', 'operator_or'],
|
|
'text_join': ['operator_join'],
|
|
'math_arithmetic': ['operator_add', 'operator_subtract', 'operator_multiply', 'operator_divide'],
|
|
'math_number': []
|
|
}
|
|
}
|
|
|
|
|
|
// from http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format
|
|
// where they got it from the stackoverflow-code itself ("formatUnicorn")
|
|
if (!String.prototype.format) {
|
|
String.prototype.format = function() {
|
|
var str = this.toString();
|
|
if (!arguments.length)
|
|
return str;
|
|
var args = typeof arguments[0],
|
|
args = (("string" == args || "number" == args) ? arguments : arguments[0]);
|
|
for (var arg in args) {
|
|
str = str.replace(RegExp("\\{" + arg + "\\}", "gi"), args[arg]);
|
|
}
|
|
return str;
|
|
}
|
|
}
|
|
|
|
function showModal(id) {
|
|
var el = '#' + id
|
|
$(el).show();
|
|
}
|
|
function closeModal(id) {
|
|
var el = '#' + id;
|
|
$(el).hide();
|
|
}
|
|
function getBlocklyHelper(maxBlocks, nbTestCases) {
|
|
return {
|
|
scratchMode: (typeof Blockly.Blocks['control_if'] !== 'undefined'),
|
|
textFile: null,
|
|
extended: false,
|
|
programs: [],
|
|
languages: [],
|
|
definitions: {},
|
|
simpleGenerators: {},
|
|
player: 0,
|
|
workspace: null,
|
|
prevWidth: 0,
|
|
trashInToolbox: false,
|
|
languageStrings: languageStrings,
|
|
startingBlock: true,
|
|
mediaUrl: (window.location.protocol == 'file:' && modulesPath) ? modulesPath+'/img/blockly/' : "http://static3.castor-informatique.fr/contestAssets/blockly/",
|
|
|
|
// Scratch specific
|
|
glowingBlock: null,
|
|
|
|
includeBlocks: {
|
|
groupByCategory: true,
|
|
generatedBlocks: {},
|
|
standardBlocks: {
|
|
includeAll: true,
|
|
wholeCategories: [],
|
|
singleBlocks: []
|
|
}
|
|
},
|
|
|
|
loadHtml: function(nbTestCases) {
|
|
$("#blocklyLibContent").html("<xml id='toolbox' style='display: none'></xml>" +
|
|
" <div style='height: 40px;display:none' id='lang'>" +
|
|
" <p>" + this.strings.selectLanguage +
|
|
" <select id='selectLanguage' onchange='task.displayedSubTask.blocklyHelper.changeLanguage()'>" +
|
|
" <option value='blockly'>" + this.strings.blocklyLanguage + "</option>" +
|
|
" <option value='javascript'>" + this.strings.javascriptLanguage + "</option>" +
|
|
" </select>" +
|
|
" <input type='button' class='language_javascript' value='" + this.strings.importFromBlockly +
|
|
"' style='display:none' onclick='task.displayedSubTask.blocklyHelper.importFromBlockly()' />" +
|
|
" </p>" +
|
|
" </div>" +
|
|
" <div id='blocklyCapacity' style='clear:both;'></div>" +
|
|
" <div id='blocklyContainer'>" +
|
|
" <div id='blocklyDiv' class='language_blockly'></div>" +
|
|
" <textarea id='program' class='language_javascript' style='width:100%;height:100%;display:none'></textarea>" +
|
|
" </div>" +
|
|
" <div id='saveOrLoadModal' class='modalWrapper'></div>\n"
|
|
);
|
|
var saveOrLoadModal = "<div class='modal'>" +
|
|
" <p><b>" + this.strings.saveOrLoadProgram + "</b></p>\n" +
|
|
" <button type='button' class='btn' onclick='task.displayedSubTask.blocklyHelper.saveProgram()' >" + this.strings.saveProgram +
|
|
"</button><span id='saveUrl'></span>\n" +
|
|
" <p>" + this.strings.avoidReloadingOtherTask + "</p>\n" +
|
|
" <p>" + this.strings.reloadProgram + " <input type='file' id='input' " +
|
|
"onchange='task.displayedSubTask.blocklyHelper.handleFiles(this.files);resetFormElement($(\"#input\"))'></p>\n" +
|
|
" <button type='button' class='btn close' onclick='closeModal(`saveOrLoadModal`)' >x</button>"
|
|
"</div>"
|
|
;
|
|
$("#saveOrLoadModal").html(saveOrLoadModal);
|
|
|
|
// Buttons from buttonsAndMessages
|
|
var addTaskHTML = '<div id="displayHelperAnswering" class="contentCentered" style="width: 438px; padding: 1px;">';
|
|
var placementNames = ['graderMessage', 'validate', 'saved'];
|
|
for (var iPlacement = 0; iPlacement < placementNames.length; iPlacement++) {
|
|
var placement = 'displayHelper_' + placementNames[iPlacement];
|
|
if ($('#' + placement).length === 0) {
|
|
addTaskHTML += '<div id="' + placement + '"></div>';
|
|
}
|
|
}
|
|
addTaskHTML += '</div>';
|
|
if(!$('#displayHelper_cancel').length) {
|
|
$('body').append($('<div class="contentCentered" style="margin-top: 15px;"><div id="displayHelper_cancel"></div></div>'));
|
|
}
|
|
|
|
var gridButtonsAfter = ''
|
|
gridButtonsAfter += "<div id='testSelector' style='width: 420px;'></div>"
|
|
+ "<button type='button' class='btn btn-primary' onclick='task.displayedSubTask.submit()'>"
|
|
+ (this.scratchMode ? "<img src='" + this.mediaUrl + "icons/event_whenflagclicked.svg' height='32px' width='32px' style='vertical-align: middle;'>" : '')
|
|
+ this.strings.submitProgram
|
|
+ "</button><br/>"
|
|
+ "<div id='errors' style='width: 400px;'></div>"
|
|
+ addTaskHTML;
|
|
$("#gridButtonsAfter").html(gridButtonsAfter);
|
|
},
|
|
|
|
load: function(language, display, nbTestCases, options) {
|
|
if(this.scratchMode) {
|
|
this.fixScratch();
|
|
}
|
|
if (language == undefined) {
|
|
language = "fr";
|
|
}
|
|
if (options == undefined) options = {};
|
|
if (!options.divId) options.divId = 'blocklyDiv';
|
|
this.strings = this.languageStrings[language];
|
|
if (display) {
|
|
this.loadHtml(nbTestCases);
|
|
this.addExtraBlocks();
|
|
this.createSimpleGeneratorsAndBlocks();
|
|
var xml = this.getToolboxXml();
|
|
var wsConfig = {
|
|
toolbox: "<xml>"+xml+"</xml>",
|
|
sounds: false,
|
|
media: this.mediaUrl
|
|
};
|
|
wsConfig.comments = true;
|
|
wsConfig.scrollbars = true;
|
|
wsConfig.trashcan = true;
|
|
if (maxBlocks != undefined) {
|
|
wsConfig.maxBlocks = maxBlocks;
|
|
}
|
|
if (options.readOnly) {
|
|
wsConfig.readOnly = true;
|
|
}
|
|
if (this.scratchMode) {
|
|
wsConfig.zoom = { startScale: 0.75 };
|
|
}
|
|
if(this.trashInToolbox) {
|
|
Blockly.Trashcan.prototype.MARGIN_SIDE_ = $('#blocklyDiv').width() - 110;
|
|
}
|
|
this.workspace = Blockly.inject(options.divId, wsConfig);
|
|
|
|
var toolboxNode = $('#toolboxXml');
|
|
if (toolboxNode.length != 0) {
|
|
toolboxNode.html(xml);
|
|
}
|
|
|
|
$(".blocklyToolboxDiv").css("background-color", "rgba(168, 168, 168, 0.5)");
|
|
var that = this;
|
|
function onchange(event) {
|
|
Blockly.svgResize(that.workspace);
|
|
|
|
var remaining = that.workspace.remainingCapacity();
|
|
var optLimitBlocks = {
|
|
maxBlocks: maxBlocks,
|
|
remainingBlocks: Math.abs(remaining)
|
|
};
|
|
var strLimitBlocks = remaining < 0 ? that.strings.limitBlocksOver : that.strings.limitBlocks;
|
|
$('#blocklyCapacity').css('color', remaining < 0 ? 'red' : '');
|
|
$('#blocklyCapacity').html(strLimitBlocks.format(optLimitBlocks));
|
|
|
|
// TODO :: put into a resetDisplay function, find other elements to reset
|
|
var newBlockly = Blockly.Xml.domToText(Blockly.Xml.workspaceToDom(that.workspace));
|
|
if(that.programs[that.player] && newBlockly != that.programs[that.player].blockly) {
|
|
// only reset when program changed
|
|
if(that.scratchMode) {
|
|
that.glowBlock(null);
|
|
}
|
|
|
|
that.resetTestScores(nbTestCases);
|
|
}
|
|
}
|
|
this.workspace.addChangeListener(onchange);
|
|
onchange();
|
|
} else {
|
|
this.workspace = new Blockly.Workspace();
|
|
}
|
|
|
|
this.programs = [];
|
|
for (var iPlayer = this.mainContext.nbRobots - 1; iPlayer >= 0; iPlayer--) {
|
|
this.programs[iPlayer] = {blockly: null, blocklyJS: "", blocklyPython: "", javascript: ""};
|
|
this.languages[iPlayer] = "blockly";
|
|
this.setPlayer(iPlayer);
|
|
if(this.startingBlock) {
|
|
var xml = this.getDefaultBlocklyContent();
|
|
|
|
Blockly.Events.recordUndo = false;
|
|
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), this.workspace);
|
|
Blockly.Events.recordUndo = true;
|
|
}
|
|
this.savePrograms();
|
|
}
|
|
},
|
|
|
|
initTestSelector: function (nbTestCases) {
|
|
var buttons = [
|
|
{cls: 'speedStop', label: this.strings.stopProgram, tooltip: this.strings.stopProgramDesc, onclick: 'task.displayedSubTask.stop()'},
|
|
{cls: 'speedStep', label: this.strings.stepProgram, tooltip: this.strings.stepProgramDesc, onclick: 'task.displayedSubTask.step()'},
|
|
{cls: 'speedSlow', label: this.strings.slowSpeed, tooltip: this.strings.slowSpeedDesc, onclick: 'task.displayedSubTask.changeSpeed(200)'},
|
|
{cls: 'speedMedium', label: this.strings.mediumSpeed, tooltip: this.strings.mediumSpeedDesc, onclick: 'task.displayedSubTask.changeSpeed(50)'},
|
|
{cls: 'speedFast', label: this.strings.fastSpeed, tooltip: this.strings.fastSpeedDesc, onclick: 'task.displayedSubTask.changeSpeed(5)'},
|
|
{cls: 'speedLudicrous', label: this.strings.ludicrousSpeed, tooltip: this.strings.ludicrousSpeedDesc, onclick: 'task.displayedSubTask.changeSpeed(0)'}
|
|
];
|
|
|
|
var selectSpeed = "<div class='selectSpeed'>" +
|
|
" <div class='btn-group'>\n";
|
|
for(var btnIdx = 0; btnIdx < buttons.length; btnIdx++) {
|
|
var btn = buttons[btnIdx];
|
|
selectSpeed += " <button type='button' class='"+btn.cls+" btn btn-default btn-icon'>"+btn.label+" </button>\n";
|
|
}
|
|
selectSpeed += " </div></div>";
|
|
|
|
var html = '<div class="panel-group">';
|
|
for(var iTest=0; iTest<nbTestCases; iTest++) {
|
|
html += '<div id="testPanel'+iTest+'" class="panel panel-default">'
|
|
+ ' <div class="panel-heading" onclick="task.displayedSubTask.changeTestTo('+iTest+')"><h4 class="panel-title"></h4></div>'
|
|
+ ' <div class="panel-body">'
|
|
+ selectSpeed
|
|
+ ' </div>'
|
|
+ '</div>';
|
|
}
|
|
$('#testSelector').html(html);
|
|
|
|
var selectSpeedClickHandler = function () {
|
|
var thisBtn = $(this);
|
|
for(var btnIdx = 0; btnIdx < buttons.length; btnIdx++) {
|
|
var btnInfo = buttons[btnIdx];
|
|
if(thisBtn.hasClass(btnInfo.cls)) {
|
|
$('#errors').html(btnInfo.tooltip);
|
|
eval(btnInfo.onclick);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
var selectSpeedHoverHandler = function () {
|
|
var thisBtn = $(this);
|
|
for(var btnIdx = 0; btnIdx < buttons.length; btnIdx++) {
|
|
var btnInfo = buttons[btnIdx];
|
|
if(thisBtn.hasClass(btnInfo.cls)) {
|
|
$('#errors').html(btnInfo.tooltip);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
var selectSpeedHoverClear = function () {
|
|
// Only clear #errors if the tooltip was for this button
|
|
var thisBtn = $(this);
|
|
for(var btnIdx = 0; btnIdx < buttons.length; btnIdx++) {
|
|
var btnInfo = buttons[btnIdx];
|
|
if(thisBtn.hasClass(btnInfo.cls)) {
|
|
if($('#errors').html() == btnInfo.tooltip) {
|
|
$('#errors').html('');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
// TODO :: better display functions for #errors
|
|
$('.selectSpeed button').click(selectSpeedClickHandler);
|
|
$('.selectSpeed button').hover(selectSpeedHoverHandler, selectSpeedHoverClear);
|
|
|
|
|
|
this.updateTestSelector(0);
|
|
this.resetTestScores(nbTestCases);
|
|
},
|
|
|
|
updateTestScores: function (testScores) {
|
|
// Display test results
|
|
for(var iTest=0; iTest<testScores.length; iTest++) {
|
|
if(testScores[iTest].successRate >= 1) {
|
|
var icon = '<span class="testResultIcon" style="color: green">✔</span>';
|
|
var label = '<span class="testResult testSuccess">'+this.strings.correctAnswer+'</span>';
|
|
} else if(testScores[iTest].successRate > 0) {
|
|
var icon = '<span class="testResultIcon" style="color: orange">✖</span>';
|
|
var label = '<span class="testResult testPartial">'+this.strings.partialAnswer+'</span>';
|
|
} else {
|
|
var icon = '<span class="testResultIcon" style="color: red">✖</span>';
|
|
var label = '<span class="testResult testFailure">'+this.strings.wrongAnswer+'</span>';
|
|
}
|
|
$('#testPanel'+iTest+' .panel-title').html(icon+' Test '+(iTest+1)+' '+label);
|
|
}
|
|
},
|
|
|
|
resetTestScores: function (nbTestCases) {
|
|
// Reset test results display
|
|
for(var iTest=0; iTest<nbTestCases; iTest++) {
|
|
$('#testPanel'+iTest+' .panel-title').html('<span class="testResultIcon"> </span> Test '+(iTest+1));
|
|
}
|
|
},
|
|
|
|
updateTestSelector: function (newCurTest) {
|
|
$("#testSelector .panel-body").hide();
|
|
$("#testPanel"+newCurTest+" .panel-body").prepend($('#grid')).append($('#errors')).show();
|
|
},
|
|
|
|
unload: function() {
|
|
//var ws = Blockly.getMainWorkspace('blocklyDiv');
|
|
var ws = this.workspace;
|
|
if (ws != null) {
|
|
ws.dispose();
|
|
// TODO: this should be in a global unload function
|
|
if (false) {
|
|
$(".blocklyWidgetDiv").remove();
|
|
$(".blocklyTooltipDiv").remove();
|
|
document.removeEventListener("keydown", Blockly.onKeyDown_); // TODO: find correct way to remove all event listeners
|
|
//delete Blockly;
|
|
}
|
|
}
|
|
},
|
|
|
|
getDefaultBlocklyContent: function () {
|
|
if (this.startingBlock) {
|
|
if(this.scratchMode) {
|
|
return '<xml><block type="robot_start" deletable="false" movable="false" x="10" y="20"></block></xml>';
|
|
} else {
|
|
return '<xml><block type="robot_start" deletable="false" movable="false"></block></xml>';
|
|
}
|
|
}
|
|
else {
|
|
return '<xml></xml>';
|
|
}
|
|
},
|
|
|
|
checkRobotStart: function () {
|
|
if(!this.startingBlock || !this.workspace) { return; }
|
|
var blocks = this.workspace.getTopBlocks(true);
|
|
for(var b=0; b<blocks.length; b++) {
|
|
if(blocks[b].type == 'robot_start') { return;}
|
|
}
|
|
|
|
var xml = Blockly.Xml.textToDom(this.getDefaultBlocklyContent())
|
|
Blockly.Xml.domToWorkspace(xml, this.workspace);
|
|
},
|
|
|
|
setPlayer: function(newPlayer) {
|
|
this.player = newPlayer;
|
|
$("#selectPlayer").val(this.player);
|
|
$(".robot0, .robot1").hide();
|
|
$(".robot" + this.player).show();
|
|
},
|
|
|
|
changePlayer: function() {
|
|
this.loadPlayer($("#selectPlayer").val());
|
|
},
|
|
|
|
loadPlayer: function(player) {
|
|
this.savePrograms();
|
|
this.player = player;
|
|
for (var iRobot = 0; iRobot < this.mainContext.nbRobots; iRobot++) {
|
|
$(".robot" + iRobot).hide();
|
|
}
|
|
$(".robot" + this.player).show();
|
|
|
|
$(".language_blockly, .language_javascript").hide();
|
|
$(".language_" + this.languages[this.player]).show();
|
|
|
|
var blocklyElems = $(".blocklyToolboxDiv, .blocklyWidgetDiv");
|
|
$("#selectLanguage").val(this.languages[this.player]);
|
|
if (this.languages[this.player] == "blockly") {
|
|
blocklyElems.show();
|
|
} else {
|
|
blocklyElems.hide();
|
|
$(".blocklyTooltipDiv").hide();
|
|
}
|
|
this.loadPrograms();
|
|
},
|
|
|
|
getCodeFromXml: function(xmlText, language) {
|
|
try {
|
|
var xml = Blockly.Xml.textToDom(xmlText)
|
|
} catch (e) {
|
|
alert(e);
|
|
return;
|
|
}
|
|
var tmpWorkspace = new Blockly.Workspace();
|
|
if(this.scratchMode) {
|
|
// Make sure it has the right information from this blocklyHelper
|
|
tmpWorkspace.maxBlocks = function () { return maxBlocks; };
|
|
}
|
|
Blockly.Xml.domToWorkspace(xml, tmpWorkspace);
|
|
return this.getCode(language, tmpWorkspace);
|
|
},
|
|
|
|
|
|
getCode: function(language, codeWorkspace) {
|
|
if (codeWorkspace == undefined) {
|
|
codeWorkspace = this.workspace;
|
|
}
|
|
if(codeWorkspace.remainingCapacity() < 0) {
|
|
// Safeguard: avoid generating code when we use too many blocks
|
|
return '';
|
|
}
|
|
var blocks = codeWorkspace.getTopBlocks(true);
|
|
var languageObj = null;
|
|
if (language == "javascript") {
|
|
languageObj = Blockly.JavaScript;
|
|
}
|
|
if (language == "python") {
|
|
languageObj = Blockly.Python;
|
|
}
|
|
languageObj.init(codeWorkspace);
|
|
|
|
var code = [];
|
|
var comments = [];
|
|
for (var b = 0; b < blocks.length; b++) {
|
|
var block = blocks[b];
|
|
var blockCode = languageObj.blockToCode(block);
|
|
if (arrayContains(["procedures_defnoreturn", "procedures_defreturn"], block.type)) {
|
|
// For function blocks, the code is stored in languageObj.definitions_
|
|
} else {
|
|
if (block.type == "robot_start" || !this.startingBlock) {
|
|
comments.push(blockCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var def in languageObj.definitions_) {
|
|
code.push(languageObj.definitions_[def]);
|
|
}
|
|
|
|
var code = code.join("\n");
|
|
code += comments.join("\n");
|
|
return code;
|
|
},
|
|
|
|
savePrograms: function() {
|
|
this.checkRobotStart();
|
|
|
|
this.programs[this.player].javascript = $("#program").val();
|
|
if (this.workspace != null) {
|
|
var xml = Blockly.Xml.workspaceToDom(this.workspace);
|
|
this.programs[this.player].blockly = Blockly.Xml.domToText(xml);
|
|
this.programs[this.player].blocklyJS = this.getCode("javascript");
|
|
//this.programs[this.player].blocklyPython = this.getCode("python");
|
|
}
|
|
},
|
|
|
|
loadPrograms: function() {
|
|
if (this.workspace != null) {
|
|
var xml = Blockly.Xml.textToDom(this.programs[this.player].blockly);
|
|
this.workspace.clear();
|
|
Blockly.Xml.domToWorkspace(xml, this.workspace);
|
|
}
|
|
$("#program").val(this.programs[this.player].javascript);
|
|
},
|
|
|
|
changeLanguage: function() {
|
|
this.languages[this.player] = $("#selectLanguage").val();
|
|
this.loadPlayer(this.player);
|
|
},
|
|
|
|
importFromBlockly: function() {
|
|
//var player = $("#selectPlayer").val();
|
|
var player = 0;
|
|
this.programs[player].javascript = this.getCode("javascript");
|
|
$("#program").val(this.programs[player].javascript);
|
|
},
|
|
|
|
handleFiles: function(files) {
|
|
var that = this;
|
|
if (files.length < 0) {
|
|
return;
|
|
}
|
|
var file = files[0];
|
|
var textType = /text.*/;
|
|
if (file.type.match(textType)) {
|
|
var reader = new FileReader();
|
|
|
|
reader.onload = function(e) {
|
|
var code = reader.result;
|
|
if (code[0] == "<") {
|
|
try {
|
|
var xml = Blockly.Xml.textToDom(code);
|
|
that.programs[that.player].blockly = code;
|
|
} catch(e) {
|
|
$("#errors").html('<span class="testError">'+that.strings.invalidContent+'</span>');
|
|
}
|
|
that.languages[that.player] = "blockly";
|
|
} else {
|
|
that.programs[that.player].javascript = code;
|
|
that.languages[that.player] = "javascript";
|
|
}
|
|
that.loadPrograms();
|
|
that.loadPlayer(that.player);
|
|
}
|
|
|
|
reader.readAsText(file);
|
|
} else {
|
|
$("#errors").html('<span class="testError">'+this.strings.unknownFileType+'</span>');
|
|
}
|
|
},
|
|
|
|
saveProgram: function() {
|
|
this.savePrograms();
|
|
var code = this.programs[this.player][this.languages[this.player]];
|
|
var data = new Blob([code], {type: 'text/plain'});
|
|
|
|
// If we are replacing a previously generated file we need to
|
|
// manually revoke the object URL to avoid memory leaks.
|
|
if (this.textFile !== null) {
|
|
window.URL.revokeObjectURL(this.textFile);
|
|
}
|
|
|
|
this.textFile = window.URL.createObjectURL(data);
|
|
|
|
// returns a URL you can use as a href
|
|
$("#saveUrl").html(" <a href='" + this.textFile + "' download='robot_" + this.languages[this.player] + "_program.txt'>" + this.strings.download + "</a>");
|
|
return this.textFile;
|
|
},
|
|
|
|
toggleSize: function() {
|
|
if (!this.extended) {
|
|
this.extended = true;
|
|
$("#blocklyContainer").css("width", "800px");
|
|
$("#extendButton").val("<<");
|
|
} else {
|
|
this.extended = false;
|
|
$("#blocklyContainer").css("width", "500px");
|
|
$("#extendButton").val(">>");
|
|
}
|
|
this.updateSize();
|
|
},
|
|
|
|
updateSize: function() {
|
|
var panelWidth = 500;
|
|
if (this.languages[this.player] == "blockly") {
|
|
panelWidth = $("#blocklyDiv").width() - 10;
|
|
} else {
|
|
panelWidth = $("#program").width() + 20;
|
|
}
|
|
if (panelWidth != this.prevWidth) {
|
|
if (this.languages[this.player] == "blockly") {
|
|
if (this.trashInToolbox) {
|
|
Blockly.Trashcan.prototype.MARGIN_SIDE_ = panelWidth - 90;
|
|
}
|
|
Blockly.svgResize(this.workspace);
|
|
}
|
|
}
|
|
this.prevWidth = panelWidth;
|
|
},
|
|
|
|
completeBlockHandler: function(block, objectName, context) {
|
|
if (typeof block.handler == "undefined") {
|
|
block.handler = context[objectName][block.name];
|
|
}
|
|
|
|
|
|
if (typeof block.handler == "undefined") {
|
|
block.handler = (function(oName, bName) {
|
|
return function() { console.error("Error: No handler given. No function context." + oName + "." + bName + "() found!" ); }
|
|
})(objectName, block.name);
|
|
}
|
|
},
|
|
completeBlockJson: function(block, objectName, categoryName, context) {
|
|
// Needs context object solely for the language strings. Maybe change that …
|
|
|
|
if (typeof block.blocklyJson == "undefined") {
|
|
block.blocklyJson = {};
|
|
}
|
|
|
|
// Set block name
|
|
if (typeof block.blocklyJson.type == "undefined") {
|
|
block.blocklyJson.type = block.name;
|
|
}
|
|
|
|
// Add connectors (top-bottom or left)
|
|
if (typeof block.blocklyJson.output == "undefined" &&
|
|
typeof block.blocklyJson.previousStatement == "undefined" &&
|
|
typeof block.blocklyJson.nextStatement == "undefined" &&
|
|
!(block.noConnectors)) {
|
|
if (block.yieldsValue) {
|
|
block.blocklyJson.output = null;
|
|
if(this.scratchMode) {
|
|
block.blocklyJson.outputShape = Blockly.OUTPUT_SHAPE_HEXAGONAL;
|
|
block.blocklyJson.colour = Blockly.Colours.sensing.primary;
|
|
block.blocklyJson.colourSecondary = Blockly.Colours.sensing.secondary;
|
|
block.blocklyJson.colourTertiary = Blockly.Colours.sensing.tertiary;
|
|
}
|
|
}
|
|
else {
|
|
block.blocklyJson.previousStatement = null;
|
|
block.blocklyJson.nextStatement = null;
|
|
if(this.scratchMode) {
|
|
block.blocklyJson.colour = Blockly.Colours.motion.primary;
|
|
block.blocklyJson.colourSecondary = Blockly.Colours.motion.secondary;
|
|
block.blocklyJson.colourTertiary = Blockly.Colours.motion.tertiary;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add parameters
|
|
if (typeof block.blocklyJson.args0 == "undefined" &&
|
|
typeof block.params != "undefined" &&
|
|
block.params.length > 0) {
|
|
block.blocklyJson.args0 = [];
|
|
for (var iParam = 0; iParam < block.params.length; iParam++) {
|
|
var param = {
|
|
type: "input_value",
|
|
name: "PARAM_" + iParam
|
|
}
|
|
|
|
if (block.params[iParam] != null) {
|
|
param.check = block.params[iParam]; // Should be a string!
|
|
}
|
|
block.blocklyJson.args0.push(param);
|
|
}
|
|
}
|
|
|
|
// Add message string
|
|
if (typeof block.blocklyJson.message0 == "undefined") {
|
|
block.blocklyJson.message0 = context.strings.label[block.name];
|
|
|
|
if (typeof block.blocklyJson.message0 == "undefined") {
|
|
block.blocklyJson.message0 = "<translation missing: " + block.name + ">";
|
|
}
|
|
|
|
if (typeof block.blocklyJson.args0 != "undefined") {
|
|
var iParam = 0;
|
|
for (var iArgs0 in block.blocklyJson.args0) {
|
|
if (block.blocklyJson.args0[iArgs0].type == "input_value") {
|
|
iParam += 1;
|
|
block.blocklyJson.message0 += " %" + iParam;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tooltip & HelpUrl should always exist, so lets just add empty ones in case they don't exist
|
|
if (typeof block.blocklyJson.tooltip == "undefined") { block.blocklyJson.tooltip = ""; }
|
|
if (typeof block.blocklyJson.helpUrl == "undefined") { block.blocklyJson.helpUrl = ""; } // TODO: Or maybe not?
|
|
|
|
// TODO: Load default colours + custom styles
|
|
if (typeof block.blocklyJson.colour == "undefined") {
|
|
if(this.scratchMode) {
|
|
block.blocklyJson.colour = Blockly.Colours.motion.primary;
|
|
block.blocklyJson.colourSecondary = Blockly.Colours.motion.secondary;
|
|
block.blocklyJson.colourTertiary = Blockly.Colours.motion.tertiary;
|
|
} else {
|
|
var colours = this.getDefaultColours();
|
|
block.blocklyJson.colour = 210; // default: blue
|
|
//console.log(block.name);
|
|
//console.log(categoryName);
|
|
//console.log(colours);
|
|
if ("blocks" in colours && block.name in colours.blocks) {
|
|
block.blocklyJson.colour = colours.blocks[block.name];
|
|
}
|
|
else if ("categories" in colours) {
|
|
if (categoryName in colours.categories) {
|
|
block.blocklyJson.colour = colours.categories[categoryName];
|
|
}
|
|
else if ("_default" in colours.categories) {
|
|
block.blocklyJson.colour = colours.categories["_default"];
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
},
|
|
completeBlockXml: function(block) {
|
|
if (typeof block.blocklyXml == "undefined" || block.blocklyXml == "") {
|
|
block.blocklyXml = "<block type='" + block.name + "'></block>";
|
|
}
|
|
},
|
|
completeCodeGenerators: function(blockInfo, objectName) {
|
|
if (typeof blockInfo.codeGenerators == "undefined") {
|
|
blockInfo.codeGenerators = {};
|
|
}
|
|
|
|
// for closure:
|
|
var args0 = blockInfo.blocklyJson.args0;
|
|
var code = this.mainContext.strings.code[blockInfo.name];
|
|
var output = blockInfo.blocklyJson.output;
|
|
|
|
for (var language in {JavaScript: null, Python: null}) {
|
|
if (typeof blockInfo.codeGenerators[language] == "undefined") {
|
|
// Prevent the function name to be used as a variable
|
|
Blockly[language].addReservedWords(code);
|
|
|
|
function setCodeGeneratorForLanguage(language) {
|
|
blockInfo.codeGenerators[language] = function(block) {
|
|
var params = "";
|
|
|
|
/* There are three kinds of input: value_input, statement_input and dummy_input,
|
|
We should definitely consider value_input here and not consider dummy_input here.
|
|
|
|
I don't know how statement_input is handled best, so I'll ignore it first -- Robert
|
|
*/
|
|
var iParam = 0;
|
|
for (var iArgs0 in args0) {
|
|
if (args0[iArgs0].type == "input_value") {
|
|
if (iParam) {
|
|
params += ", ";
|
|
}
|
|
params += Blockly[language].valueToCode(block, 'PARAM_' + iParam, Blockly[language].ORDER_ATOMIC);
|
|
iParam += 1;
|
|
}
|
|
}
|
|
|
|
var callCode = code + '(' + params + ')';
|
|
// Add reportValue to show the value in step-by-step mode
|
|
var reportedCode = "reportBlockValue('" + block.id + "', " + callCode + ")";
|
|
|
|
if (typeof output == "undefined") {
|
|
return callCode + ";\n";
|
|
}
|
|
else {
|
|
return [reportedCode, Blockly[language].ORDER_NONE];
|
|
}
|
|
}
|
|
};
|
|
setCodeGeneratorForLanguage(language);
|
|
}
|
|
}
|
|
},
|
|
applyCodeGenerators: function(block) {
|
|
for (var language in block.codeGenerators) {
|
|
Blockly[language][block.name] = block.codeGenerators[language];
|
|
}
|
|
},
|
|
|
|
createBlock: function(block) {
|
|
if (typeof block.blocklyInit == "undefined") {
|
|
var blocklyjson = block.blocklyJson;
|
|
Blockly.Blocks[block.name] = {
|
|
init: function() {
|
|
this.jsonInit(blocklyjson);
|
|
}
|
|
};
|
|
}
|
|
else if (typeof block.blocklyInit == "function") {
|
|
Blockly.Blocks[block.name] = {
|
|
init: block.blocklyInit()
|
|
};
|
|
}
|
|
else {
|
|
console.err(block.name + ".blocklyInit is defined but not a function");
|
|
}
|
|
},
|
|
|
|
createSimpleGenerator: function(label, code, type, nbParams) {
|
|
var jsDefinitions = this.definitions['javascript'] ? this.definitions['javascript'] : [];
|
|
var pyDefinitions = this.definitions['python'] ? this.definitions['python'] : [];
|
|
|
|
// Prevent the function name to be used as a variable
|
|
Blockly.JavaScript.addReservedWords(code);
|
|
Blockly.Python.addReservedWords(code);
|
|
|
|
Blockly.JavaScript[label] = function(block) {
|
|
for (var iDef=0; iDef < jsDefinitions.length; iDef++) {
|
|
var def = jsDefinitions[iDef];
|
|
Blockly.Javascript.definitions_[def.label] = def.code;
|
|
}
|
|
var params = "";
|
|
for (var iParam = 0; iParam < nbParams; iParam++) {
|
|
if (iParam != 0) {
|
|
params += ", ";
|
|
}
|
|
params += Blockly.JavaScript.valueToCode(block, 'NAME_' + (iParam + 1), Blockly.JavaScript.ORDER_ATOMIC);
|
|
}
|
|
if (type == 0) {
|
|
return code + "(" + params + ");\n";
|
|
} else if (type == 1){
|
|
return [code + "(" + params + ")", Blockly.JavaScript.ORDER_NONE];
|
|
}
|
|
};
|
|
Blockly.Python[label] = function(block) {
|
|
for (var iDef=0; iDef < pyDefinitions.length; iDef++) {
|
|
var def = pyDefinitions[iDef];
|
|
Blockly.Python.definitions_[def.label] = def.code;
|
|
}
|
|
var params = "";
|
|
for (var iParam = 0; iParam < nbParams; iParam++) {
|
|
if (iParam != 0) {
|
|
params += ", ";
|
|
}
|
|
params += Blockly.Python.valueToCode(block, 'NAME_' + (iParam + 1), Blockly.Python.ORDER_ATOMIC);
|
|
}
|
|
if (type == 0) {
|
|
return code + "(" + params + ")\n";
|
|
} else if (type == 1) {
|
|
return [code + "(" + params + ")", Blockly.Python.ORDER_NONE];
|
|
}
|
|
};
|
|
},
|
|
|
|
createSimpleBlock: function(label, code, type, nbParams) {
|
|
Blockly.Blocks[label] = {
|
|
init: function() {
|
|
this.appendDummyInput()
|
|
.appendField(code);
|
|
if (type == 0) {
|
|
this.setPreviousStatement(true);
|
|
this.setNextStatement(true);
|
|
}
|
|
if (type == 1) {
|
|
this.setOutput(true);
|
|
}
|
|
this.setInputsInline(true);
|
|
for (var iParam = 0; iParam < nbParams; iParam++) {
|
|
this.appendValueInput("NAME_" + (iParam + 1)).setCheck(null);
|
|
}
|
|
this.setColour(210);
|
|
this.setTooltip('');
|
|
this.setHelpUrl('');
|
|
}
|
|
};
|
|
},
|
|
|
|
createSimpleGeneratorsAndBlocks: function() {
|
|
for (var genName in this.simpleGenerators) {
|
|
for (var iGen = 0; iGen < this.simpleGenerators[genName].length; iGen++) {
|
|
var generator = this.simpleGenerators[genName][iGen];
|
|
if(genName == '.') {
|
|
var label = generator.label + "__";
|
|
var code = generator.code;
|
|
} else {
|
|
var label = genName + "_" + generator.label + "__";
|
|
var code = genName + "." + generator.code;
|
|
}
|
|
this.createSimpleGenerator(label, code, generator.type, generator.nbParams);
|
|
// TODO :: merge createSimpleBlock with completeBlock*
|
|
this.createSimpleBlock(label, generator.label, generator.type, generator.nbParams);
|
|
}
|
|
}
|
|
},
|
|
|
|
createGeneratorsAndBlocks: function() {
|
|
var customGenerators = this.mainContext.customBlocks;
|
|
for (var objectName in customGenerators) {
|
|
for (var categoryName in customGenerators[objectName]) {
|
|
var category = customGenerators[objectName][categoryName];
|
|
for (var iBlock = 0; iBlock < category.blocks.length; iBlock++) {
|
|
var block = category.blocks[iBlock];
|
|
|
|
/* TODO: Allow library writers to provide their own JS/Python code instead of just a handler */
|
|
this.completeBlockHandler(block, objectName, this.mainContext);
|
|
this.completeBlockJson(block, objectName, categoryName, this.mainContext); /* category.category is category name */
|
|
this.completeBlockXml(block);
|
|
this.completeCodeGenerators(block, objectName);
|
|
this.applyCodeGenerators(block);
|
|
this.createBlock(block);
|
|
}
|
|
// TODO: Anything of this still needs to be done?
|
|
//this.createGenerator(label, objectName + "." + code, generator.type, generator.nbParams);
|
|
//this.createBlock(label, generator.labelFr, generator.type, generator.nbParams);
|
|
}
|
|
}
|
|
},
|
|
|
|
getBlocklyLibCode: function(generators) {
|
|
var strCode = "";
|
|
for (var objectName in generators) {
|
|
strCode += "var " + objectName + " = {\n";
|
|
for (var iGen = 0; iGen < generators[objectName].length; iGen++) {
|
|
var generator = generators[objectName][iGen];
|
|
|
|
if (generator.nbParams == 0) {
|
|
strCode += generator.codeFr + ": function() { ";
|
|
} else {
|
|
strCode += generator.codeFr + ": function(param1) { ";
|
|
}
|
|
if (generator.type == 1) {
|
|
strCode += "return ";
|
|
}
|
|
if (generator.nbParams == 0) {
|
|
strCode += objectName + "_" + generator.labelEn + "(); }";
|
|
} else {
|
|
strCode += objectName + "_" + generator.labelEn + "(param1); }";
|
|
}
|
|
if (iGen < generators[objectName].length - 1) {
|
|
strCode += ",";
|
|
}
|
|
strCode += "\n";
|
|
}
|
|
strCode += "};\n\n";
|
|
}
|
|
strCode += "Math['max'] = function(a, b) { if (a > b) return a; return b; };\n";
|
|
strCode += "Math['min'] = function(a, b) { if (a > b) return b; return a; };\n";
|
|
return strCode;
|
|
},
|
|
|
|
|
|
getDefaultColours: function() {
|
|
var colours = {
|
|
categories: {
|
|
logic: 210,
|
|
loops: 120,
|
|
control: 120,
|
|
math: 230,
|
|
operator: 230,
|
|
texts: 160,
|
|
lists: 260,
|
|
colour: 20,
|
|
variables: 330,
|
|
functions: 290,
|
|
_default: 65
|
|
},
|
|
blocks: {}
|
|
};
|
|
|
|
if (typeof provideBlocklyColours == "function") {
|
|
var providedColours = provideBlocklyColours();
|
|
|
|
for (var group in providedColours) {
|
|
if (!(group in colours)) {
|
|
colours[group] = {};
|
|
}
|
|
for (name in providedColours[group]) {
|
|
colours[group][name] = providedColours[group][name];
|
|
}
|
|
}
|
|
}
|
|
|
|
return colours;
|
|
},
|
|
|
|
|
|
getStdBlocks: function() {
|
|
var stdBlocks = this.scratchMode ? this.getStdScratchBlocks() : this.getStdBlocklyBlocks();
|
|
|
|
// Check each block is actually loaded in current context
|
|
for(var category in stdBlocks) {
|
|
var verifiedBlocks = [];
|
|
var blockList = stdBlocks[category].blocks;
|
|
for(var b=0; b<blockList.length; b++) {
|
|
var blocklyName = blockList[b].depends ? blockList[b].depends : blockList[b].name;
|
|
if(Blockly.Blocks[blocklyName]) {
|
|
verifiedBlocks.push(blockList[b]);
|
|
}
|
|
}
|
|
stdBlocks[category].blocks = verifiedBlocks;
|
|
}
|
|
|
|
return stdBlocks;
|
|
},
|
|
|
|
getStdBlocklyBlocks: function() {
|
|
return {
|
|
input: {
|
|
blocks: [
|
|
{
|
|
name: "input_num",
|
|
blocklyXml: "<block type='input_num'></block>"
|
|
},
|
|
{
|
|
name: "input_char",
|
|
blocklyXml: "<block type='input_char'></block>"
|
|
},
|
|
{
|
|
name: "input_word",
|
|
blocklyXml: "<block type='input_word'></block>"
|
|
},
|
|
{
|
|
name: "input_line",
|
|
blocklyXml: "<block type='input_line'></block>"
|
|
}
|
|
]
|
|
},
|
|
logic: {
|
|
blocks: [
|
|
{
|
|
name: "controls_if",
|
|
blocklyXml: "<block type='controls_if'></block>"
|
|
},
|
|
{
|
|
name: "controls_if_else",
|
|
depends: "controls_if",
|
|
blocklyXml: "<block type='controls_if'><mutation else='1'></mutation></block>"
|
|
},
|
|
{
|
|
name: "logic_compare",
|
|
blocklyXml: "<block type='logic_compare'></block>"
|
|
},
|
|
{
|
|
name: "logic_operation",
|
|
blocklyXml: "<block type='logic_operation'></block>"
|
|
},
|
|
{
|
|
name: "logic_negate",
|
|
blocklyXml: "<block type='logic_negate'></block>"
|
|
},
|
|
{
|
|
name: "logic_boolean",
|
|
blocklyXml: "<block type='logic_boolean'></block>"
|
|
}
|
|
]
|
|
},
|
|
loops: {
|
|
blocks: [
|
|
{
|
|
name: "controls_repeat",
|
|
blocklyXml: "<block type='controls_repeat'></block>",
|
|
excludedByDefault: true
|
|
},
|
|
{
|
|
name: "controls_repeat_ext",
|
|
blocklyXml: "<block type='controls_repeat_ext'>" +
|
|
" <value name='TIMES'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>10</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "controls_whileUntil",
|
|
blocklyXml: "<block type='controls_whileUntil'></block>"
|
|
},
|
|
{
|
|
name: "controls_untilWhile",
|
|
blocklyXml: "<block type='controls_whileUntil'><field name='MODE'>UNTIL</field></block>",
|
|
excludedByDefault: true
|
|
},
|
|
{
|
|
name: "controls_for",
|
|
blocklyXml: "<block type='controls_for'>" +
|
|
" <value name='FROM'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>1</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
" <value name='TO'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>10</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
" <value name='BY'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>1</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "controls_forEach",
|
|
excludedByDefault: true,
|
|
blocklyXml: "<block type='controls_forEach'></block>"
|
|
},
|
|
{
|
|
name: "controls_flow_statements",
|
|
blocklyXml: "<block type='controls_flow_statements'></block>"
|
|
}
|
|
]
|
|
},
|
|
math: {
|
|
blocks: [
|
|
{
|
|
name: "math_number",
|
|
blocklyXml: "<block type='math_number' gap='32'></block>"
|
|
},
|
|
{
|
|
name: "math_arithmetic",
|
|
blocklyXml: "<block type='math_arithmetic'>" +
|
|
" <value name='A'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>1</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
" <value name='B'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>1</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "math_number_property",
|
|
blocklyXml: "<block type='math_number_property'></block>"
|
|
},
|
|
{
|
|
name: "math_change",
|
|
blocklyXml: "<block type='math_change'></block>"
|
|
},
|
|
{
|
|
name: "math_round",
|
|
blocklyXml: "<block type='math_round'></block>"
|
|
},
|
|
{
|
|
name: "math_extra_single",
|
|
blocklyXml: "<block type='math_extra_single'></block>"
|
|
},
|
|
{
|
|
name: "math_extra_double",
|
|
blocklyXml: "<block type='math_extra_double'></block>"
|
|
},
|
|
{
|
|
name: "math_modulo",
|
|
blocklyXml: "<block type='math_modulo'></block>"
|
|
}
|
|
]
|
|
},
|
|
text: {
|
|
blocks: [
|
|
{
|
|
name: "text",
|
|
blocklyXml: "<block type='text'></block>"
|
|
},
|
|
{
|
|
name: "text_print",
|
|
blocklyXml: "<block type='text_print'>" +
|
|
" <value name='TEXT'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>abc</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_print_noend",
|
|
blocklyXml: "<block type='text_print_noend'>" +
|
|
" <value name='TEXT'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>abc</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_join",
|
|
blocklyXml: "<block type='text_join'></block>"
|
|
},
|
|
{
|
|
name: "text_append",
|
|
blocklyXml: "<block type='text_append'></block>"
|
|
},
|
|
{
|
|
name: "text_length",
|
|
blocklyXml: "<block type='text_length'>" +
|
|
" <value name='VALUE'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>abc</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_isEmpty",
|
|
blocklyXml: "<block type='text_isEmpty'>" +
|
|
" <value name='VALUE'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'></field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_indexOf",
|
|
blocklyXml: "<block type='text_indexOf'>" +
|
|
" <value name='VALUE'>" +
|
|
" <block type='variables_get'>" +
|
|
" <field name='VAR'>{textVariable}</field>" +
|
|
" </block>" +
|
|
" </value>" +
|
|
" <value name='FIND'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>abc</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_charAt",
|
|
blocklyXml: "<block type='text_charAt'>" +
|
|
" <value name='VALUE'>" +
|
|
" <block type='variables_get'>" +
|
|
" <field name='VAR'>{textVariable}</field>" +
|
|
" </block>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_getSubstring",
|
|
blocklyXml: "<block type='text_getSubstring'>" +
|
|
" <value name='STRING'>" +
|
|
" <block type='variables_get'>" +
|
|
" <field name='VAR'>{textVariable}</field>" +
|
|
" </block>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_changeCase",
|
|
blocklyXml: "<block type='text_changeCase'>" +
|
|
" <value name='TEXT'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>abc</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_trim",
|
|
blocklyXml: "<block type='text_trim'>" +
|
|
" <value name='TEXT'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>abc</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "text_prompt_ext",
|
|
blocklyXml: "<block type='text_prompt_ext'>" +
|
|
" <value name='TEXT'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>abc</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
}
|
|
]
|
|
},
|
|
lists: {
|
|
blocks: [
|
|
{
|
|
name: "lists_create_with_empty",
|
|
blocklyXml: "<block type='lists_create_with'>" +
|
|
" <mutation items='0'></mutation>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "lists_create_with",
|
|
blocklyXml: "<block type='lists_create_with'></block>"
|
|
},
|
|
{
|
|
name: "lists_repeat",
|
|
blocklyXml: "<block type='lists_repeat'>" +
|
|
" <value name='NUM'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>5</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "lists_length",
|
|
blocklyXml: "<block type='lists_length'></block>"
|
|
},
|
|
{
|
|
name: "lists_isEmpty",
|
|
blocklyXml: "<block type='lists_isEmpty'></block>"
|
|
},
|
|
{
|
|
name: "lists_indexOf",
|
|
blocklyXml: "<block type='lists_indexOf'>" +
|
|
" <value name='VALUE'>" +
|
|
" <block type='variables_get'>" +
|
|
" <field name='VAR'>{listVariable}</field>" +
|
|
" </block>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "lists_getIndex",
|
|
blocklyXml: "<block type='lists_getIndex'>" +
|
|
" <value name='VALUE'>" +
|
|
" <block type='variables_get'>" +
|
|
" <field name='VAR'>{listVariable}</field>" +
|
|
" </block>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "lists_setIndex",
|
|
blocklyXml: "<block type='lists_setIndex'>" +
|
|
" <value name='LIST'>" +
|
|
" <block type='variables_get'>" +
|
|
" <field name='VAR'>{listVariable}</field>" +
|
|
" </block>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "lists_getSublist",
|
|
blocklyXml: "<block type='lists_getSublist'>" +
|
|
" <value name='LIST'>" +
|
|
" <block type='variables_get'>" +
|
|
" <field name='VAR'>{listVariable}</field>" +
|
|
" </block>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "lists_sort",
|
|
blocklyXml: "<block type='lists_sort'></block>"
|
|
},
|
|
{
|
|
name: "lists_split",
|
|
blocklyXml: "<block type='lists_split'>" +
|
|
" <value name='DELIM'>" +
|
|
" <shadow type='text'>" +
|
|
" <field name='TEXT'>,</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "lists_append",
|
|
blocklyXml: "<block type='lists_append'></block>"
|
|
}
|
|
]
|
|
},
|
|
colour: {
|
|
blocks: [
|
|
{
|
|
name: "colour_picker",
|
|
blocklyXml: "<block type='colour_picker'></block>"
|
|
},
|
|
{
|
|
name: "colour_random",
|
|
blocklyXml: "<block type='colour_random'></block>"
|
|
},
|
|
{
|
|
name: "colour_rgb",
|
|
blocklyXml: "<block type='colour_rgb'>" +
|
|
" <value name='RED'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>100</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
" <value name='GREEN'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>50</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
" <value name='BLUE'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>0</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "colour_blend",
|
|
blocklyXml: "<block type='colour_blend'>" +
|
|
" <value name='COLOUR1'>" +
|
|
" <shadow type='colour_picker'>" +
|
|
" <field name='COLOUR'>#ff0000</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
" <value name='COLOUR2'>" +
|
|
" <shadow type='colour_picker'>" +
|
|
" <field name='COLOUR'>#3333ff</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
" <value name='RATIO'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>0.5</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
}
|
|
]
|
|
},
|
|
dicts: {
|
|
blocks: [
|
|
{
|
|
name: "dict_get_literal",
|
|
blocklyXml: "<block type='dict_get_literal'></block>"
|
|
},
|
|
{
|
|
name: "dict_keys",
|
|
blocklyXml: "<block type='dict_keys'></block>"
|
|
},
|
|
{
|
|
name: "dicts_create_with",
|
|
blocklyXml: "<block type='dicts_create_with'></block>"
|
|
}
|
|
]
|
|
},
|
|
variables: {
|
|
custom: "VARIABLE",
|
|
blocks: []
|
|
},
|
|
functions: {
|
|
custom: "PROCEDURE",
|
|
blocks: []
|
|
}
|
|
};
|
|
},
|
|
|
|
getStdScratchBlocks: function() {
|
|
// TODO :: make the list of standard scratch blocks
|
|
return {
|
|
control: {
|
|
blocks: [
|
|
{
|
|
name: "control_if",
|
|
blocklyXml: "<block type='control_if'></block>"
|
|
},
|
|
{
|
|
name: "control_if_else",
|
|
blocklyXml: "<block type='control_if_else'></block>"
|
|
},
|
|
{
|
|
name: "control_repeat",
|
|
blocklyXml: "<block type='control_repeat'>" +
|
|
" <value name='TIMES'>" +
|
|
" <shadow type='math_number'>" +
|
|
" <field name='NUM'>10</field>" +
|
|
" </shadow>" +
|
|
" </value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "control_repeat_until",
|
|
blocklyXml: "<block type='control_repeat_until'></block>"
|
|
}
|
|
]
|
|
},
|
|
operator: {
|
|
blocks: [
|
|
{
|
|
name: "operator_add",
|
|
blocklyXml: "<block type='operator_add'>" +
|
|
" <value name='NUM1'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
" <value name='NUM2'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "operator_subtract",
|
|
blocklyXml: "<block type='operator_subtract'>" +
|
|
" <value name='NUM1'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
" <value name='NUM2'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "operator_multiply",
|
|
blocklyXml: "<block type='operator_multiply'>" +
|
|
" <value name='NUM1'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
" <value name='NUM2'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "operator_divide",
|
|
blocklyXml: "<block type='operator_divide'>" +
|
|
" <value name='NUM1'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
" <value name='NUM2'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "operator_equals",
|
|
blocklyXml: "<block type='operator_equals'>" +
|
|
" <value name='OPERAND1'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
" <value name='OPERAND2'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "operator_gt",
|
|
blocklyXml: "<block type='operator_gt'>" +
|
|
" <value name='OPERAND1'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
" <value name='OPERAND2'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "operator_lt",
|
|
blocklyXml: "<block type='operator_lt'>" +
|
|
" <value name='OPERAND1'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
" <value name='OPERAND2'><shadow type='math_number'><field name='NUM'></field></shadow></value>" +
|
|
"</block>"
|
|
},
|
|
{
|
|
name: "operator_and",
|
|
blocklyXml: "<block type='operator_and'></block>"
|
|
},
|
|
{
|
|
name: "operator_or",
|
|
blocklyXml: "<block type='operator_or'></block>"
|
|
},
|
|
{
|
|
name: "operator_not",
|
|
blocklyXml: "<block type='operator_not'></block>"
|
|
},
|
|
{
|
|
name: "operator_join",
|
|
blocklyXml: "<block type='operator_join'>" +
|
|
" <value name='STRING1'><shadow type='text'><field name='TEXT'></field></shadow></value>" +
|
|
" <value name='STRING2'><shadow type='text'><field name='TEXT'></field></shadow></value>" +
|
|
"</block>"
|
|
}
|
|
]
|
|
},
|
|
variables: {
|
|
custom: "VARIABLE",
|
|
blocks: []
|
|
}
|
|
};
|
|
},
|
|
|
|
getBlockXmlInfo: function(generatorStruct, blockName) {
|
|
for (var categoryName in generatorStruct) {
|
|
var blocks = generatorStruct[categoryName].blocks;
|
|
for (var iBlock = 0; iBlock < blocks.length; iBlock++) {
|
|
var block = blocks[iBlock];
|
|
if (block.name == blockName) {
|
|
return {
|
|
category: categoryName,
|
|
xml: block.blocklyXml
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
console.error("Block not found: " + blockName);
|
|
return null;
|
|
},
|
|
|
|
addBlocksAndCategories: function(blockNames, blocksDefinition, categoriesInfos) {
|
|
var colours = this.getDefaultColours();
|
|
for (var iBlock = 0; iBlock < blockNames.length; iBlock++) {
|
|
var blockName = blockNames[iBlock];
|
|
var blockXmlInfo = this.getBlockXmlInfo(blocksDefinition, blockName);
|
|
var categoryName = blockXmlInfo.category;
|
|
|
|
if (!(categoryName in categoriesInfos)) {
|
|
categoriesInfos[categoryName] = {
|
|
blocksXml: [],
|
|
colour: colours.blocks[blockName]
|
|
};
|
|
}
|
|
categoriesInfos[categoryName].blocksXml.push(blockXmlInfo.xml);
|
|
}
|
|
|
|
// by the way, just change the defaul colours of the blockly blocks:
|
|
if(!this.scratchMode) {
|
|
var defCat = ["logic", "loops", "math", "texts", "lists", "colour"]
|
|
for (var iCat in defCat) {
|
|
Blockly.Blocks[defCat[iCat]].HUE = colours.categories[defCat[iCat]];
|
|
}
|
|
}
|
|
},
|
|
|
|
getToolboxXml: function() {
|
|
var categoriesInfos = {};
|
|
var colours = this.getDefaultColours();
|
|
|
|
// Reset the flyoutOptions
|
|
Blockly.Variables.flyoutOptions = {
|
|
any: false,
|
|
anyButton: !!this.includeBlocks.groupByCategory,
|
|
fixed: [],
|
|
includedBlocks: {get: true, set: true, incr: true},
|
|
shortList: true
|
|
};
|
|
|
|
for (var blockType in this.includeBlocks.generatedBlocks) {
|
|
this.addBlocksAndCategories(this.includeBlocks.generatedBlocks[blockType], this.mainContext.customBlocks[blockType], categoriesInfos);
|
|
}
|
|
|
|
for (var genName in this.simpleGenerators) {
|
|
for (var iGen = 0; iGen < this.simpleGenerators[genName].length; iGen++) {
|
|
var generator = this.simpleGenerators[genName][iGen];
|
|
if (categoriesInfos[generator.category] == undefined) {
|
|
categoriesInfos[generator.category] = {
|
|
blocksXml: [],
|
|
colour: 210
|
|
};
|
|
}
|
|
var blockName = (genName == '.') ? generator.label + "__" : genName + "_" + generator.label + "__";
|
|
categoriesInfos[generator.category].blocksXml.push("<block type='"+blockName+"'></block>");
|
|
}
|
|
}
|
|
|
|
var stdBlocks = this.getStdBlocks();
|
|
|
|
if (this.includeBlocks.standardBlocks.includeAll) {
|
|
this.includeBlocks.standardBlocks.wholeCategories = ["input", "logic", "loops", "math", "text", "lists", "colour", "dicts", "variables", "functions"];
|
|
}
|
|
var wholeCategories = this.includeBlocks.standardBlocks.wholeCategories || [];
|
|
for (var iCategory = 0; iCategory < wholeCategories.length; iCategory++) {
|
|
var categoryName = wholeCategories[iCategory];
|
|
if (!(categoryName in categoriesInfos)) {
|
|
categoriesInfos[categoryName] = {
|
|
blocksXml: []
|
|
};
|
|
}
|
|
if (categoryName == 'variables') {
|
|
Blockly.Variables.flyoutOptions.any = true;
|
|
continue;
|
|
}
|
|
var blocks = stdBlocks[categoryName].blocks;
|
|
for (var iBlock = 0; iBlock < blocks.length; iBlock++) {
|
|
categoriesInfos[categoryName].blocksXml.push(blocks[iBlock].blocklyXml);
|
|
}
|
|
}
|
|
|
|
this.addBlocksAndCategories(this.includeBlocks.standardBlocks.singleBlocks || [], stdBlocks, categoriesInfos);
|
|
|
|
// Handle variable blocks, which are normally automatically added with
|
|
// the VARIABLES category but can be customized here
|
|
if (typeof this.includeBlocks.variables !== 'undefined') {
|
|
Blockly.Variables.flyoutOptions.fixed = (this.includeBlocks.variables.length > 0) ? this.includeBlocks.variables : [];
|
|
if (typeof this.includeBlocks.variablesOnlyBlocks !== 'undefined') {
|
|
Blockly.Variables.flyoutOptions.includedBlocks = {get: false, set: false, incr: false};
|
|
for (var iBlock=0; iBlock < this.includeBlocks.variablesOnlyBlocks.length; iBlock++) {
|
|
Blockly.Variables.flyoutOptions.includedBlocks[this.includeBlocks.variablesOnlyBlocks[iBlock]] = true;
|
|
}
|
|
}
|
|
|
|
var varAnyIdx = Blockly.Variables.flyoutOptions.fixed.indexOf('*');
|
|
if(varAnyIdx > -1) {
|
|
Blockly.Variables.flyoutOptions.fixed.splice(varAnyIdx, 1);
|
|
Blockly.Variables.flyoutOptions.any = true;
|
|
}
|
|
|
|
var blocksXml = Blockly.Variables.flyoutCategory();
|
|
var xmlSer = new XMLSerializer();
|
|
for(var i=0; i<blocksXml.length; i++) {
|
|
blocksXml[i] = xmlSer.serializeToString(blocksXml[i]);
|
|
}
|
|
categoriesInfos["variables"] = {
|
|
blocksXml: blocksXml,
|
|
colour: 330
|
|
}
|
|
}
|
|
|
|
var xmlString = "";
|
|
for (var categoryName in categoriesInfos) {
|
|
var categoryInfo = categoriesInfos[categoryName];
|
|
if (this.includeBlocks.groupByCategory) {
|
|
var colour = categoryInfo.colour;
|
|
if (typeof(colour) == "undefined") {
|
|
colour = colours.categories[categoryName]
|
|
if (typeof(colour) == "undefined") {
|
|
colour = colours.categories._default;
|
|
}
|
|
}
|
|
xmlString += "<category "
|
|
+ " name='" + this.strings.categories[categoryName] + "'"
|
|
+ " colour='" + colour + "'"
|
|
+ (this.scratchMode ? " secondaryColour='" + colour + "'" : '')
|
|
+ (categoryName == 'variables' ? ' custom="VARIABLE"' : '')
|
|
+ (categoryName == 'functions' ? ' custom="PROCEDURE"' : '')
|
|
+ ">";
|
|
}
|
|
var blocks = categoryInfo.blocksXml;
|
|
for (var iBlock = 0; iBlock < blocks.length; iBlock++) {
|
|
xmlString += blocks[iBlock];
|
|
}
|
|
if (this.includeBlocks.groupByCategory) {
|
|
xmlString += "</category>";
|
|
}
|
|
}
|
|
return xmlString;
|
|
},
|
|
|
|
|
|
addExtraBlocks: function() {
|
|
var that = this;
|
|
|
|
Blockly.Blocks['controls_untilWhile'] = Blockly.Blocks['controls_whileUntil'];
|
|
Blockly.JavaScript['controls_untilWhile'] = Blockly.JavaScript['controls_whileUntil'];
|
|
Blockly.Python['controls_untilWhile'] = Blockly.Python['controls_whileUntil'];
|
|
|
|
Blockly.Blocks['math_extra_single'] = {
|
|
/**
|
|
* Block for advanced math operators with single operand.
|
|
* @this Blockly.Block
|
|
*/
|
|
init: function() {
|
|
var OPERATORS =
|
|
[
|
|
[Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE, 'ABS'],
|
|
['-', 'NEG']
|
|
];
|
|
this.setHelpUrl(Blockly.Msg.MATH_SINGLE_HELPURL);
|
|
this.setColour(Blockly.Blocks.math.HUE);
|
|
this.setOutput(true, 'Number');
|
|
this.appendValueInput('NUM')
|
|
.setCheck('Number')
|
|
.appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
|
|
// Assign 'this' to a variable for use in the tooltip closure below.
|
|
var thisBlock = this;
|
|
this.setTooltip(function() {
|
|
var mode = thisBlock.getFieldValue('OP');
|
|
var TOOLTIPS = {
|
|
'ABS': Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS,
|
|
'NEG': Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG
|
|
};
|
|
return TOOLTIPS[mode];
|
|
});
|
|
}
|
|
};
|
|
|
|
Blockly.JavaScript['math_extra_single'] = Blockly.JavaScript['math_single'];
|
|
Blockly.Python['math_extra_single'] = Blockly.Python['math_single'];
|
|
|
|
|
|
Blockly.Blocks['math_extra_double'] = {
|
|
/**
|
|
* Block for advanced math operators with double operand.
|
|
* @this Blockly.Block
|
|
*/
|
|
init: function() {
|
|
var OPERATORS =
|
|
[
|
|
['min', 'MIN'],
|
|
['max', 'MAX']
|
|
];
|
|
this.setColour(Blockly.Blocks.math.HUE);
|
|
this.setInputsInline(true);
|
|
this.setOutput(true, 'Number');
|
|
this.appendDummyInput('OP').appendField(new Blockly.FieldDropdown([["min", "MIN"], ["max", "MAX"], ["", ""]]), "OP");
|
|
this.appendDummyInput().appendField(" entre ");
|
|
this.appendValueInput('A').setCheck('Number');
|
|
this.appendDummyInput().appendField(" et ");
|
|
this.appendValueInput('B').setCheck('Number');
|
|
// Assign 'this' to a variable for use in the tooltip closure below.
|
|
var thisBlock = this;
|
|
this.setTooltip(function() {
|
|
var mode = thisBlock.getFieldValue('OP');
|
|
var TOOLTIPS = {
|
|
'MIN': that.strings.smallestOfTwoNumbers,
|
|
'MAX': that.strings.greatestOfTwoNumbers
|
|
};
|
|
return TOOLTIPS[mode];
|
|
});
|
|
}
|
|
};
|
|
|
|
Blockly.JavaScript['math_extra_double'] = function(block) {
|
|
// Math operators with double operand.
|
|
var operator = block.getFieldValue('OP');
|
|
var arg1 = Blockly.JavaScript.valueToCode(block, 'A', Blockly.JavaScript.ORDER_NONE) || '0';
|
|
var arg2 = Blockly.JavaScript.valueToCode(block, 'B', Blockly.JavaScript.ORDER_NONE) || '0';
|
|
if (operator == 'MIN') {
|
|
var code = "Math.min(" + arg1 + ", " + arg2 + ")";
|
|
}
|
|
if (operator == 'MAX') {
|
|
var code = "Math.max(" + arg1 + ", " + arg2 + ")";
|
|
}
|
|
return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
|
|
};
|
|
|
|
Blockly.Python['math_extra_double'] = function(block) {
|
|
// Math operators with double operand.
|
|
var operator = block.getFieldValue('OP');
|
|
var arg1 = Blockly.Python.valueToCode(block, 'A', Blockly.Python.ORDER_NONE) || '0';
|
|
var arg2 = Blockly.Python.valueToCode(block, 'B', Blockly.Python.ORDER_NONE) || '0';
|
|
if (operator == 'MIN') {
|
|
var code = "Math.min(" + arg1 + ", " + arg2 + ")";
|
|
}
|
|
if (operator == 'MAX') {
|
|
var code = "Math.max(" + arg1 + ", " + arg2 + ")";
|
|
}
|
|
return [code, Blockly.Python.ORDER_FUNCTION_CALL];
|
|
};
|
|
|
|
|
|
if(this.scratchMode) {
|
|
Blockly.Blocks['robot_start'] = {
|
|
init: function() {
|
|
this.jsonInit({
|
|
"id": "event_whenflagclicked",
|
|
"message0": that.strings.flagClicked,
|
|
"args0": [
|
|
{
|
|
"type": "field_image",
|
|
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/event_whenflagclicked.svg",
|
|
"width": 24,
|
|
"height": 24,
|
|
"alt": "flag",
|
|
"flip_rtl": true
|
|
}
|
|
],
|
|
"inputsInline": true,
|
|
"nextStatement": null,
|
|
"category": Blockly.Categories.event,
|
|
"colour": Blockly.Colours.event.primary,
|
|
"colourSecondary": Blockly.Colours.event.secondary,
|
|
"colourTertiary": Blockly.Colours.event.tertiary
|
|
});
|
|
}
|
|
};
|
|
|
|
} else {
|
|
var old = Blockly.Blocks.controls_if.init;
|
|
Blockly.Blocks.controls_if.init = function() {
|
|
old.call(this);
|
|
this.setMutator(undefined)
|
|
};
|
|
|
|
Blockly.Blocks['robot_start'] = {
|
|
init: function() {
|
|
this.appendDummyInput()
|
|
.appendField(that.strings.programOfRobot);
|
|
this.setNextStatement(true);
|
|
this.setColour(210);
|
|
this.setTooltip('');
|
|
this.deletable_ = false;
|
|
this.editable_ = false;
|
|
this.movable_ = false;
|
|
// this.setHelpUrl('http://www.example.com/');
|
|
}
|
|
};
|
|
}
|
|
|
|
Blockly.JavaScript['robot_start'] = function(block) {
|
|
return "";
|
|
};
|
|
|
|
Blockly.Python['robot_start'] = function(block) {
|
|
return "";
|
|
};
|
|
},
|
|
|
|
fixScratch: function() {
|
|
// Store the maxBlocks information somehwere, as Scratch ignores it
|
|
Blockly.Workspace.prototype.maxBlocks = function () { return maxBlocks; };
|
|
|
|
// Translate requested Blocks from Blockly to Scratch blocks
|
|
// TODO :: full translation
|
|
var newSingleBlocks = [];
|
|
for (var iBlock = 0; iBlock < this.includeBlocks.standardBlocks.singleBlocks.length; iBlock++) {
|
|
var blockName = this.includeBlocks.standardBlocks.singleBlocks[iBlock];
|
|
if(blocklyToScratch.singleBlocks[blockName]) {
|
|
for(var b=0; b<blocklyToScratch.singleBlocks[blockName].length; b++) {
|
|
newSingleBlocks.push(blocklyToScratch.singleBlocks[blockName][b]);
|
|
}
|
|
} else {
|
|
newSingleBlocks.push(blockName);
|
|
}
|
|
}
|
|
this.includeBlocks.standardBlocks.singleBlocks = newSingleBlocks;
|
|
},
|
|
|
|
glowBlock: function(id) {
|
|
// highlightBlock replacement for Scratch
|
|
if(this.glowingBlock) {
|
|
try {
|
|
this.workspace.glowBlock(this.glowingBlock, false);
|
|
} catch(e) {}
|
|
}
|
|
if(id) {
|
|
this.workspace.glowBlock(id, true);
|
|
}
|
|
this.glowingBlock = id;
|
|
},
|
|
|
|
initRun: function() {
|
|
var that = this;
|
|
var nbRunning = this.mainContext.runner.nbRunning();
|
|
if (nbRunning > 0) {
|
|
this.mainContext.runner.stop();
|
|
this.mainContext.delayFactory.createTimeout("run" + Math.random(), function() {
|
|
that.run()
|
|
}, 1000);
|
|
return;
|
|
}
|
|
if (this.mainContext.display) {
|
|
Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
|
|
Blockly.JavaScript.addReservedWords('highlightBlock');
|
|
} else {
|
|
Blockly.JavaScript.STATEMENT_PREFIX = '';
|
|
}
|
|
var topBlocks = this.workspace.getTopBlocks(true);
|
|
var robotStartHasChildren = false;
|
|
for(var b=0; b<topBlocks.length; b++) {
|
|
var block = topBlocks[b];
|
|
if(block.type == 'robot_start' && block.childBlocks_.length > 0) {
|
|
robotStartHasChildren = true;
|
|
break;
|
|
} // There can be multiple robot_start blocks sometimes
|
|
}
|
|
if(!robotStartHasChildren) {
|
|
$("#errors").html('<span class="testError">'+this.strings.emptyProgram+'</span>');
|
|
return;
|
|
}
|
|
|
|
this.savePrograms();
|
|
|
|
var codes = [];
|
|
for (var iRobot = 0; iRobot < this.mainContext.nbRobots; iRobot++) {
|
|
var language = this.languages[iRobot];
|
|
if (language == "blockly") {
|
|
language = "blocklyJS";
|
|
}
|
|
codes[iRobot] = this.getFullCode(this.programs[iRobot][language]);
|
|
}
|
|
that.highlightPause = false;
|
|
if(this.scratchMode) {
|
|
Blockly.DropDownDiv.hide();
|
|
if(that.workspace.remainingCapacity() < 0) {
|
|
$("#errors").html('<span class="testError">'+this.strings.tooManyBlocks+'</span>');
|
|
return;
|
|
}
|
|
} else {
|
|
that.workspace.traceOn(true);
|
|
that.workspace.highlightBlock(null);
|
|
}
|
|
this.mainContext.runner.initCodes(codes);
|
|
},
|
|
|
|
run: function () {
|
|
this.initRun();
|
|
this.mainContext.runner.run();
|
|
},
|
|
|
|
step: function () {
|
|
if(this.mainContext.runner.nbRunning() <= 0) {
|
|
this.initRun();
|
|
}
|
|
this.mainContext.runner.step();
|
|
},
|
|
|
|
getFullCode: function(code) {
|
|
return this.getBlocklyLibCode(this.generators) + code + "program_end()";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function initBlocklyRunner(context, messageCallback, language) {
|
|
init(context, [], [], [], false, {}, language);
|
|
|
|
function init(context, interpreters, isRunning, toStop, stopPrograms, runner, language) {
|
|
runner.hasActions = false;
|
|
runner.nbActions = 0;
|
|
runner.scratchMode = context.blocklyHelper ? context.blocklyHelper.scratchMode : false;
|
|
|
|
runner.stepInProgress = false;
|
|
runner.stepMode = false;
|
|
runner.nextCallBack = null;
|
|
runner.firstHighlight = true;
|
|
|
|
runner.strings = languageStrings[language];
|
|
|
|
runner.reportBlockValue = function(id, value, varName) {
|
|
// Show a popup displaying the value of a block in step-by-step mode
|
|
if(context.display && runner.stepMode) {
|
|
var displayStr = value.toString();
|
|
if(value.type == 'boolean') {
|
|
displayStr = value.data ? runner.strings.valueTrue : runner.strings.valueFalse;
|
|
}
|
|
if(varName) {
|
|
varName = varName.toString();
|
|
for(var dbIdx in Blockly.JavaScript.variableDB_.db_) {
|
|
if(Blockly.JavaScript.variableDB_.db_[dbIdx] == varName) {
|
|
varName = dbIdx.substring(0, dbIdx.length - 9);
|
|
break;
|
|
}
|
|
}
|
|
displayStr = varName + ' = ' + displayStr;
|
|
}
|
|
context.blocklyHelper.workspace.reportValue(id, displayStr);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
runner.waitDelay = function(callback, value, delay) {
|
|
if (delay > 0) {
|
|
context.delayFactory.createTimeout("wait" + context.curRobot + "_" + Math.random(), function() {
|
|
runner.noDelay(callback, value);
|
|
},
|
|
delay
|
|
);
|
|
} else {
|
|
runner.noDelay(callback, value);
|
|
}
|
|
};
|
|
|
|
runner.noDelay = function(callback, value) {
|
|
var primitive = undefined;
|
|
if (value != undefined) {
|
|
primitive = interpreters[context.curRobot].createPrimitive(value);
|
|
}
|
|
if (Math.random() < 0.1) {
|
|
context.delayFactory.createTimeout("wait_" + Math.random(), function() {
|
|
callback(primitive);
|
|
runner.runSyncBlock();
|
|
}, 0);
|
|
} else {
|
|
callback(primitive);
|
|
runner.runSyncBlock();
|
|
}
|
|
};
|
|
|
|
runner.initInterpreter = function(interpreter, scope) {
|
|
var makeHandler = function(runner, handler) {
|
|
// For commands belonging to the "actions" category, we count the
|
|
// number of actions to put a limit on steps without actions
|
|
return function () {
|
|
runner.nbActions += 1;
|
|
handler.apply(this, arguments);
|
|
};
|
|
};
|
|
|
|
for (var objectName in context.customBlocks) {
|
|
for (var iCategory in context.customBlocks[objectName]) {
|
|
for (var iBlock in context.customBlocks[objectName][iCategory].blocks) {
|
|
var blockInfo = context.customBlocks[objectName][iCategory].blocks[iBlock];
|
|
var code = context.strings.code[blockInfo.name];
|
|
|
|
if (typeof(code) == "undefined")
|
|
code = blockInfo.name;
|
|
|
|
if(iCategory == 'actions') {
|
|
runner.hasActions = true;
|
|
var handler = makeHandler(runner, blockInfo.handler);
|
|
} else {
|
|
var handler = blockInfo.handler;
|
|
}
|
|
|
|
interpreter.setProperty(scope, code, interpreter.createAsyncFunction(handler));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*for (var objectName in context.generators) {
|
|
for (var iGen = 0; iGen < context.generators[objectName].length; iGen++) {
|
|
var generator = context.generators[objectName][iGen];
|
|
interpreter.setProperty(scope, objectName + "_" + generator.labelEn, interpreter.createAsyncFunction(generator.fct));
|
|
}
|
|
}*/
|
|
interpreter.setProperty(scope, "program_end", interpreter.createAsyncFunction(context.program_end));
|
|
|
|
function highlightBlock(id, callback) {
|
|
id = id ? id.toString() : '';
|
|
|
|
if (context.display) {
|
|
if(!runner.scratchMode) {
|
|
context.blocklyHelper.workspace.traceOn(true);
|
|
context.blocklyHelper.workspace.highlightBlock(id);
|
|
highlightPause = true;
|
|
} else {
|
|
context.blocklyHelper.glowBlock(id);
|
|
highlightPause = true;
|
|
}
|
|
}
|
|
// We always execute directly the first highlightBlock
|
|
if(runner.firstHighlight || !runner.stepMode) {
|
|
runner.firstHighlight = false;
|
|
callback();
|
|
runner.runSyncBlock();
|
|
} else {
|
|
// Interrupt here for step mode, allows to stop before each
|
|
// instruction
|
|
runner.nextCallback = callback;
|
|
runner.stepInProgress = false;
|
|
}
|
|
}
|
|
|
|
// Add an API function for highlighting blocks.
|
|
interpreter.setProperty(scope, 'highlightBlock', interpreter.createAsyncFunction(highlightBlock));
|
|
|
|
// Add an API function to report a value.
|
|
interpreter.setProperty(scope, 'reportBlockValue', interpreter.createNativeFunction(runner.reportBlockValue));
|
|
};
|
|
|
|
runner.stop = function() {
|
|
for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
|
|
if (isRunning[iInterpreter]) {
|
|
toStop[iInterpreter] = true;
|
|
isRunning[iInterpreter] = false;
|
|
}
|
|
}
|
|
if(runner.scratchMode) {
|
|
Blockly.DropDownDiv.hide();
|
|
context.blocklyHelper.glowBlock(null);
|
|
}
|
|
context.reset();
|
|
};
|
|
|
|
runner.runSyncBlock = function() {
|
|
var maxIter = 40000;
|
|
var maxIterWithoutAction = 500;
|
|
if (context.infos.maxIter != undefined) {
|
|
maxIter = context.infos.maxIter;
|
|
}
|
|
if (context.infos.maxIterWithoutAction != undefined) {
|
|
maxIterWithoutAction = context.infos.maxIterWithoutAction;
|
|
}
|
|
|
|
if(!runner.hasActions) {
|
|
// If there's no actions in the current task, "disable" the limit
|
|
maxIterWithoutAction = maxIter;
|
|
}
|
|
|
|
/* if (turn > 90) {
|
|
task.program_end(function() {
|
|
that.stop();
|
|
});
|
|
return;
|
|
}*/
|
|
|
|
runner.stepInProgress = true;
|
|
|
|
// Handle the callback from last highlightBlock
|
|
if(runner.nextCallback) {
|
|
runner.nextCallback();
|
|
runner.nextCallback = null;
|
|
}
|
|
|
|
try {
|
|
for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
|
|
context.curRobot = iInterpreter;
|
|
if (context.infos.checkEndEveryTurn) {
|
|
context.infos.checkEndCondition(context, false);
|
|
}
|
|
var interpreter = interpreters[iInterpreter];
|
|
while (context.curSteps[iInterpreter].total < maxIter && context.curSteps[iInterpreter].withoutAction < maxIterWithoutAction) {
|
|
if (!interpreter.step() || toStop[iInterpreter]) {
|
|
isRunning[iInterpreter] = false;;
|
|
break;
|
|
}
|
|
if (interpreter.paused_) {
|
|
break;
|
|
}
|
|
context.curSteps[iInterpreter].total++;
|
|
if(context.curSteps[iInterpreter].lastNbMoves != runner.nbActions) {
|
|
context.curSteps[iInterpreter].lastNbMoves = runner.nbActions;
|
|
context.curSteps[iInterpreter].withoutAction = 0;
|
|
} else {
|
|
context.curSteps[iInterpreter].withoutAction++;
|
|
}
|
|
}
|
|
if (context.curSteps[iInterpreter].total >= maxIter) {
|
|
isRunning[iInterpreter] = false;
|
|
throw context.blocklyHelper.strings.tooManyIterations;
|
|
} else if(context.curSteps[iInterpreter].withoutAction >= maxIterWithoutAction) {
|
|
isRunning[iInterpreter] = false;
|
|
throw context.blocklyHelper.strings.tooManyIterationsWithoutAction;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
runner.stepInProgress = false;
|
|
|
|
for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
|
|
isRunning[iInterpreter] = false;
|
|
}
|
|
|
|
var message = e.toString();
|
|
|
|
// Translate "Unknown identifier" message
|
|
if(message.substring(0, 20) == "Unknown identifier: ") {
|
|
var varName = message.substring(20);
|
|
// Get original variable name if possible
|
|
for(var dbIdx in Blockly.JavaScript.variableDB_.db_) {
|
|
if(Blockly.JavaScript.variableDB_.db_[dbIdx] == varName) {
|
|
varName = dbIdx.substring(0, dbIdx.length - 9);
|
|
break;
|
|
}
|
|
}
|
|
message = this.strings.uninitializedVar + ' ' + varName;
|
|
}
|
|
|
|
if ((context.nbTestCases != undefined) && (context.nbTestCases > 1)) {
|
|
if (context.success) {
|
|
message = context.messagePrefixSuccess + message;
|
|
} else {
|
|
message = context.messagePrefixFailure + message;
|
|
}
|
|
}
|
|
if (context.success) {
|
|
message = "<span style='color:green;font-weight:bold'>" + message + "</span>";
|
|
if (context.linkBack) {
|
|
//message += "<br/><span onclick='window.parent.backToList()' style='font-weight:bold;cursor:pointer;text-decoration:underline;color:blue'>Retour à la liste des questions</span>";
|
|
}
|
|
}
|
|
messageCallback(message);
|
|
}
|
|
};
|
|
|
|
runner.initCodes = function(codes) {
|
|
//this.mainContext.delayFactory.stopAll(); pb: it would top existing graders
|
|
interpreters = [];
|
|
runner.nbActions = 0;
|
|
runner.stepInProgress = false;
|
|
runner.stepMode = false;
|
|
runner.firstHighlight = true;
|
|
context.programEnded = [];
|
|
context.curSteps = [];
|
|
context.reset();
|
|
for (var iInterpreter = 0; iInterpreter < codes.length; iInterpreter++) {
|
|
context.curSteps[iInterpreter] = {
|
|
total: 0,
|
|
withoutAction: 0,
|
|
lastNbMoves: 0};
|
|
context.programEnded[iInterpreter] = false;
|
|
interpreters.push(new Interpreter(codes[iInterpreter], runner.initInterpreter));
|
|
isRunning[iInterpreter] = true;
|
|
toStop[iInterpreter] = false;
|
|
}
|
|
};
|
|
|
|
runner.runCodes = function(codes) {
|
|
runner.initCodes(codes);
|
|
runner.runSyncBlock();
|
|
};
|
|
|
|
runner.run = function () {
|
|
runner.stepMode = false;
|
|
if(!runner.stepInProgress) {
|
|
for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
|
|
interpreters[iInterpreter].paused_ = false;
|
|
}
|
|
runner.runSyncBlock();
|
|
}
|
|
};
|
|
|
|
runner.step = function () {
|
|
runner.stepMode = true;
|
|
if(!runner.stepInProgress) {
|
|
for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
|
|
interpreters[iInterpreter].paused_ = false;
|
|
}
|
|
runner.runSyncBlock();
|
|
}
|
|
};
|
|
|
|
runner.nbRunning = function() {
|
|
var nbRunning = 0;
|
|
for (var iInterpreter = 0; iInterpreter < interpreters.length; iInterpreter++) {
|
|
if (isRunning[iInterpreter]) {
|
|
nbRunning++;
|
|
}
|
|
}
|
|
return nbRunning;
|
|
};
|
|
|
|
context.runner = runner;
|
|
context.callCallback = runner.noDelay;
|
|
context.programEnded = [];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Merges arrays by values
|
|
// (Flat-Copy only)
|
|
function mergeIntoArray(into, other) {
|
|
for (var iOther in other) {
|
|
var intoContains = false;
|
|
|
|
for (var iInto in into) {
|
|
if (other[iOther] == into[iInto]) {
|
|
intoContains = true;
|
|
}
|
|
}
|
|
|
|
if (!intoContains) {
|
|
into.push(other[iOther]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merges objects into each other similar to $.extend, but
|
|
// merges Arrays differently (see above)
|
|
// (Deep-Copy only)
|
|
function mergeIntoObject(into, other) {
|
|
for (var property in other) {
|
|
if (other[property] instanceof Array) {
|
|
if (!(into[property] instanceof Array)) {
|
|
into[property] = [];
|
|
}
|
|
mergeIntoArray(into[property], other[property]);
|
|
}
|
|
if (other[property] instanceof Object) {
|
|
if (!(into[property] instanceof Object)) {
|
|
into[property] = {};
|
|
}
|
|
mergeIntoObject(into[property], other[property]);
|
|
}
|
|
into[property] = other[property];
|
|
}
|
|
}
|
|
|
|
/*
|
|
{ shared: { field1: X }, easy: { field2: Y } } becomes { field1: X, field2: Y } if the current level is easy
|
|
{ shared: [X, Y], easy: [Z] } becomes [X, Y, Z] if the current level is easy
|
|
{ easy: X, medium: Y, hard: Z} becomes X if the current level is easy
|
|
*/
|
|
|
|
function testLevelSpecific() {
|
|
var tests = [
|
|
{
|
|
in: { field1: "X", field2: "Y" },
|
|
out: { field1: "X", field2: "Y" }
|
|
},
|
|
{
|
|
in: { easy: "X", medium: "Y", hard: "Z"},
|
|
out: "X"
|
|
},
|
|
{
|
|
in: { shared: { field1: "X" }, easy: { field2: "Y" } },
|
|
out: { field1: "X", field2: "Y" }
|
|
},
|
|
{
|
|
in: { shared: ["X", "Y"], easy: ["Z"] },
|
|
out: ["X", "Y", "Z"]
|
|
}
|
|
];
|
|
for (var iTest = 0; iTest < tests.length; iTest++) {
|
|
var res = extractLevelSpecific(tests[iTest].in, "easy");
|
|
if (JSON.stringify(res) != JSON.stringify(tests[iTest].out)) { // TODO better way to compare two objects
|
|
console.error("Test " + iTest + " failed: returned " + JSON.stringify(res));
|
|
}
|
|
}
|
|
}
|
|
|
|
function extractLevelSpecific(item, level) {
|
|
if ((typeof item != "object") || Array.isArray(item)) {
|
|
return item;
|
|
}
|
|
if (item.shared === undefined) {
|
|
if (item[level] === undefined) {
|
|
var newItem = {};
|
|
for (var prop in item) {
|
|
newItem[prop] = extractLevelSpecific(item[prop], level);
|
|
}
|
|
return newItem;
|
|
}
|
|
return extractLevelSpecific(item[level], level);
|
|
}
|
|
if (Array.isArray(item.shared)) {
|
|
var newItem = [];
|
|
for (var iElem = 0; iElem < item.shared.length; iElem++) {
|
|
newItem.push(extractLevelSpecific(item.shared[iElem], level));
|
|
}
|
|
if (item[level] != undefined) {
|
|
if (!Array.isArray(item[level])) {
|
|
console.error("Incompatible types when merging shared and " + level);
|
|
}
|
|
for (var iElem = 0; iElem < item[level].length; iElem++) {
|
|
newItem.push(extractLevelSpecific(item[level][iElem], level));
|
|
}
|
|
}
|
|
return newItem;
|
|
}
|
|
if (typeof item.shared == "object") {
|
|
var newItem = {};
|
|
for (var prop in item.shared) {
|
|
newItem[prop] = extractLevelSpecific(item.shared[prop], level);
|
|
}
|
|
if (item[level] != undefined) {
|
|
if (typeof item[level] != "object") {
|
|
console.error("Incompatible types when merging shared and " + level);
|
|
}
|
|
for (var prop in item[level]) {
|
|
newItem[prop] = extractLevelSpecific(item[level][prop], level);
|
|
}
|
|
}
|
|
return newItem;
|
|
}
|
|
console.error("Invalid type for shared property");
|
|
}
|
|
|
|
|
|
var initBlocklySubTask = function(subTask, language) {
|
|
if (subTask.data["medium"] == undefined) {
|
|
subTask.load = function(views, callback) {
|
|
subTask.loadLevel("easy");
|
|
callback();
|
|
};
|
|
}
|
|
if (language == undefined) {
|
|
language = "fr";
|
|
}
|
|
|
|
subTask.loadLevel = function(curLevel) {
|
|
subTask.levelGridInfos = extractLevelSpecific(subTask.gridInfos, curLevel);
|
|
|
|
subTask.blocklyHelper = getBlocklyHelper(subTask.levelGridInfos.maxInstructions);
|
|
subTask.answer = null;
|
|
subTask.state = {};
|
|
subTask.iTestCase = 0;
|
|
|
|
this.level = curLevel;
|
|
|
|
// TODO: fix bebras platform to make this unnecessary
|
|
try {
|
|
$('#question-iframe', window.parent.document).css('width', '100%');
|
|
} catch(e) {
|
|
}
|
|
$('body').css("width", "100%").addClass('blockly');
|
|
window.focus();
|
|
|
|
this.iTestCase = 0;
|
|
this.nbTestCases = subTask.data[curLevel].length;
|
|
if (this.display) {
|
|
var gridHtml = "<center>";
|
|
gridHtml += "<div id='gridButtonsBefore'></div>";
|
|
gridHtml += "<div id='grid'></div>";
|
|
gridHtml += "<div id='gridButtonsAfter'></div>";
|
|
gridHtml += "</center>";
|
|
$("#gridContainer").html(gridHtml)
|
|
if (subTask.levelGridInfos.hideSaveOrLoad) {
|
|
// TODO: do without a timeout
|
|
setTimeout(function() {
|
|
$("#saveOrLoad").hide();
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
this.context = getContext(this.display, subTask.levelGridInfos, curLevel);
|
|
this.context.raphaelFactory = this.raphaelFactory;
|
|
this.context.delayFactory = this.delayFactory;
|
|
this.context.blocklyHelper = this.blocklyHelper;
|
|
|
|
this.blocklyHelper.mainContext = this.context;
|
|
this.blocklyHelper.createGeneratorsAndBlocks();
|
|
|
|
//this.answer = task.getDefaultAnswerObject();
|
|
displayHelper.hideValidateButton = true;
|
|
displayHelper.timeoutMinutes = 30;
|
|
|
|
this.blocklyHelper.includeBlocks = extractLevelSpecific(this.context.infos.includeBlocks, curLevel);;
|
|
|
|
this.blocklyHelper.load(stringsLanguage, this.display, this.data[curLevel].length);
|
|
if(this.display) {
|
|
this.blocklyHelper.initTestSelector(this.nbTestCases);
|
|
}
|
|
|
|
subTask.changeTest(0);
|
|
};
|
|
|
|
subTask.updateScale = function() {
|
|
this.context.updateScale();
|
|
this.blocklyHelper.updateSize();
|
|
};
|
|
|
|
var resetScores = function() {
|
|
};
|
|
|
|
var updateScores = function() {
|
|
};
|
|
|
|
function changeScore(robot, deltaScore) {
|
|
scores[robot] += deltaScore;
|
|
updateScores();
|
|
};
|
|
|
|
subTask.unloadLevel = function(callback) {
|
|
this.context.unload();
|
|
this.blocklyHelper.unload();
|
|
callback();
|
|
};
|
|
|
|
subTask.unload = subTask.unloadLevel;
|
|
|
|
subTask.reset = function() {
|
|
this.context.reset();
|
|
};
|
|
|
|
subTask.program_end = function(callback) {
|
|
this.context.program_end(callback);
|
|
};
|
|
|
|
var initContextForLevel = function(iTestCase) {
|
|
subTask.iTestCase = iTestCase;
|
|
subTask.context.reset(subTask.data[subTask.level][iTestCase]);
|
|
subTask.context.iTestCase = iTestCase;
|
|
subTask.context.nbTestCases = subTask.nbTestCases;
|
|
// var prefix = "Test " + (subTask.iTestCase + 1) + "/" + subTask.nbTestCases + " : ";
|
|
subTask.context.messagePrefixFailure = '';
|
|
subTask.context.messagePrefixSuccess = '';
|
|
subTask.context.linkBack = false;
|
|
};
|
|
|
|
subTask.run = function() {
|
|
initBlocklyRunner(subTask.context, function(message, success) {
|
|
$("#errors").html('<span class="testError">'+message+'</span>');
|
|
}, language);
|
|
initContextForLevel(subTask.iTestCase);
|
|
subTask.blocklyHelper.run(subTask.context);
|
|
};
|
|
|
|
subTask.submit = function() {
|
|
this.context.display = false;
|
|
this.getAnswerObject(); // to fill this.answer;
|
|
$('#displayHelper_graderMessage').html('<div style="margin: .2em 0; color: red; font-weight: bold;">' + languageStrings[language].gradingInProgress + '</div>');
|
|
this.getGrade(function(result) {
|
|
$('#displayHelper_graderMessage').html('');
|
|
subTask.context.display = true;
|
|
subTask.context.changeDelay(200);
|
|
initBlocklyRunner(subTask.context, function(message, success) {
|
|
$("#errors").html('<span class="testError">'+message+'</span>');
|
|
platform.validate("done");
|
|
}, language);
|
|
subTask.changeTest(result.iTestCase - subTask.iTestCase);
|
|
initContextForLevel(result.iTestCase);
|
|
subTask.context.linkBack = true;
|
|
subTask.context.messagePrefixSuccess = languageStrings[language].allTests;
|
|
subTask.blocklyHelper.run(subTask.context);
|
|
});
|
|
};
|
|
|
|
subTask.step = function () {
|
|
subTask.context.changeDelay(200);
|
|
if(!subTask.context.runner || subTask.context.runner.nbRunning() <= 0) {
|
|
initBlocklyRunner(subTask.context, function(message, success) {
|
|
$("#errors").html('<span class="testError">'+message+'</span>');
|
|
}, language);
|
|
initContextForLevel(subTask.iTestCase);
|
|
}
|
|
subTask.blocklyHelper.step(subTask.context);
|
|
};
|
|
|
|
subTask.stop = function() {
|
|
this.context.runner.stop();
|
|
};
|
|
|
|
subTask.reloadStateObject = function(stateObj) {
|
|
this.state = stateObj;
|
|
// this.level = state.level;
|
|
|
|
// initContextForLevel(this.level);
|
|
|
|
// this.context.runner.stop();
|
|
};
|
|
|
|
subTask.getDefaultStateObject = function() {
|
|
return { level: "easy" };
|
|
};
|
|
|
|
subTask.getStateObject = function() {
|
|
this.state.level = this.level;
|
|
return this.state;
|
|
};
|
|
|
|
subTask.changeSpeed = function(speed) {
|
|
this.context.changeDelay(speed);
|
|
if ((this.context.runner == undefined) || (this.context.runner.nbRunning() == 0)) {
|
|
this.run();
|
|
} else if (this.context.runner.stepMode) {
|
|
this.context.runner.run();
|
|
}
|
|
};
|
|
|
|
subTask.getAnswerObject = function() {
|
|
this.blocklyHelper.savePrograms();
|
|
|
|
this.answer = this.blocklyHelper.programs;
|
|
return this.answer;
|
|
};
|
|
|
|
subTask.reloadAnswerObject = function(answerObj) {
|
|
if(typeof answerObj === "undefined") {
|
|
this.answer = this.getDefaultAnswerObject();
|
|
} else {
|
|
this.answer = answerObj;
|
|
}
|
|
this.blocklyHelper.programs = this.answer;
|
|
if (this.answer != undefined) {
|
|
this.blocklyHelper.loadPrograms();
|
|
}
|
|
};
|
|
|
|
subTask.getDefaultAnswerObject = function() {
|
|
var defaultBlockly = this.blocklyHelper.getDefaultBlocklyContent();
|
|
return [{javascript:"", blockly: defaultBlockly, blocklyJS: ""}];
|
|
};
|
|
|
|
subTask.changeTest = function(delta) {
|
|
var newTest = subTask.iTestCase + delta;
|
|
if ((newTest >= 0) && (newTest < this.nbTestCases)) {
|
|
initContextForLevel(newTest);
|
|
if(subTask.context.display) {
|
|
subTask.blocklyHelper.updateTestSelector(newTest);
|
|
}
|
|
}
|
|
};
|
|
|
|
subTask.changeTestTo = function(iTest) {
|
|
var delta = iTest - subTask.iTestCase;
|
|
if(delta != 0) {
|
|
subTask.changeTest(delta);
|
|
}
|
|
};
|
|
|
|
subTask.getGrade = function(callback) {
|
|
subTask.context.changeDelay(0);
|
|
var code = subTask.blocklyHelper.getCodeFromXml(subTask.answer[0].blockly, "javascript");
|
|
var codes = [subTask.blocklyHelper.getFullCode(code)];
|
|
subTask.iTestCase = 0;
|
|
initBlocklyRunner(subTask.context, function(message, success) {
|
|
subTask.testCaseResults[subTask.iTestCase] = subTask.levelGridInfos.computeGrade(subTask.context, message);
|
|
subTask.iTestCase++;
|
|
if (subTask.iTestCase < subTask.nbTestCases) {
|
|
initContextForLevel(subTask.iTestCase);
|
|
subTask.context.runner.runCodes(codes);
|
|
} else {
|
|
var iWorstTestCase = 0;
|
|
var worstRate = 1;
|
|
var nbSuccess = 0;
|
|
for (var iCase = 0; iCase < subTask.nbTestCases; iCase++) {
|
|
if (subTask.testCaseResults[iCase].successRate >= 1) {
|
|
nbSuccess++;
|
|
}
|
|
if (subTask.testCaseResults[iCase].successRate < worstRate) {
|
|
worstRate = subTask.testCaseResults[iCase].successRate;
|
|
iWorstTestCase = iCase;
|
|
}
|
|
}
|
|
subTask.testCaseResults[iWorstTestCase].iTestCase = iWorstTestCase;
|
|
subTask.blocklyHelper.updateTestScores(subTask.testCaseResults);
|
|
if(subTask.testCaseResults[iWorstTestCase].successRate < 1) {
|
|
if(nbSuccess > 0) {
|
|
var msg = languageStrings[language].resultsPartialSuccess.format({
|
|
nbSuccess: nbSuccess,
|
|
nbTests: subTask.nbTestCases
|
|
});
|
|
} else {
|
|
var msg = languageStrings[language].resultsNoSuccess;
|
|
}
|
|
var results = {
|
|
message: msg,
|
|
successRate: subTask.testCaseResults[iWorstTestCase].successRate,
|
|
iTestCase: iWorstTestCase
|
|
};
|
|
} else {
|
|
var results = subTask.testCaseResults[iWorstTestCase];
|
|
}
|
|
callback(results);
|
|
}
|
|
}, language);
|
|
subTask.iTestCase = 0;
|
|
subTask.testCaseResults = [];
|
|
initContextForLevel(subTask.iTestCase);
|
|
subTask.context.linkBack = true;
|
|
subTask.context.messagePrefixSuccess = languageStrings[language].allTests;
|
|
subTask.context.runner.runCodes(codes);
|
|
};
|
|
}
|
|
|
|
// We need to be able to clean all events
|
|
|
|
if (Node && Node.prototype.addEventListenerBase == undefined) {
|
|
// IE11 doesn't have EventTarget
|
|
if(typeof EventTarget === 'undefined') {
|
|
var targetPrototype = Node.prototype;
|
|
} else {
|
|
var targetPrototype = EventTarget.prototype;
|
|
}
|
|
targetPrototype.addEventListenerBase = targetPrototype.addEventListener;
|
|
targetPrototype.addEventListener = function(type, listener)
|
|
{
|
|
if(!this.EventList) { this.EventList = []; }
|
|
this.addEventListenerBase.apply(this, arguments);
|
|
if(!this.EventList[type]) { this.EventList[type] = []; }
|
|
var list = this.EventList[type];
|
|
for(var index = 0; index != list.length; index++)
|
|
{
|
|
if(list[index] === listener) { return; }
|
|
}
|
|
list.push(listener);
|
|
};
|
|
|
|
targetPrototype.removeEventListenerBase = targetPrototype.removeEventListener;
|
|
targetPrototype.removeEventListener = function(type, listener)
|
|
{
|
|
if(!this.EventList) { this.EventList = []; }
|
|
if(listener instanceof Function) { this.removeEventListenerBase.apply(this, arguments); }
|
|
if(!this.EventList[type]) { return; }
|
|
var list = this.EventList[type];
|
|
for(var index = 0; index != list.length;)
|
|
{
|
|
var item = list[index];
|
|
if(!listener)
|
|
{
|
|
this.removeEventListenerBase(type, item);
|
|
list.splice(index, 1); continue;
|
|
}
|
|
else if(item === listener)
|
|
{
|
|
list.splice(index, 1); break;
|
|
}
|
|
index++;
|
|
}
|
|
if(list.length == 0) { delete this.EventList[type]; }
|
|
};
|
|
}
|
|
|
|
function removeBlockly() {
|
|
$(".blocklyWidgetDiv").remove();
|
|
$(".blocklyTooltipDiv").remove();
|
|
document.removeEventListener("keydown"); //, Blockly.onKeyDown_); // TODO: find correct way to remove all event listeners
|
|
// delete Blockly;
|
|
}
|