openct-tasks/_common/modules/pemFioi/quickAlgo/interface-mobileFirst.js

1512 lines
65 KiB
JavaScript

/*
interface:
Main interface for quickAlgo, common to all languages.
*/
var quickAlgoInterface = {
strings: {},
nbTestCases: 0,
delayFactory: new DelayFactory(),
curMode: null,
fullscreen: false,
lastHeight: null,
checkHeightInterval: null,
hasHelp: false,
editorMenuIsOpen: false,
longIntroShown: false,
taskIntroContent: null,
blocklyHelper: null,
editorReadOnly: false,
options: {},
capacityPopupDisplayed: {},
userTaskData: null, // contain the subject and title, and also the about
keypadData: {
value: '',
callbackModify: null,
callbackFinished: null
},
// Contain all the licenses supported with their link
// There is also the "copyright" license or other license that the user can write himself
licenses: {
"CC BY-SA 4.0": "https://creativecommons.org/licenses/by-sa/4.0/deed.fr",
"CC BY-NC-SA 4.0": "https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=ccsearch&atype=rich",
"CC BY 4.0": "https://creativecommons.org/licenses/by/4.0/deed.fr"
},
enterFullscreen: function() {
var el = document.documentElement;
if(el.requestFullscreen) {
el.requestFullscreen();
} else if(el.mozRequestFullScreen) {
el.mozRequestFullScreen();
} else if(el.webkitRequestFullscreen) {
el.webkitRequestFullscreen();
} else if(el.msRequestFullscreen) {
el.msRequestFullscreen();
}
this.fullscreen = true;
this.updateFullscreenElements();
},
exitFullscreen: function() {
if(window.iOSDetected) {
// iOS tries to extend the iframe if the contents don't fit.
// To remedy that, we delete the blockly editor after returning
// from fullscreen, and reload it
$('#blocklyLibContent').addClass('interfaceToggled');
setTimeout(function() {
$('#blocklyDiv').html('');
$('#blocklyLibContent').removeClass('interfaceToggled');
quickAlgoInterface.blocklyHelper.reload();
quickAlgoInterface.onResize();
}, 500);
}
var el = document;
if(el.exitFullscreen) {
el.exitFullscreen();
} else if(el.mozCancelFullScreen) {
el.mozCancelFullScreen();
} else if(el.webkitExitFullscreen) {
el.webkitExitFullscreen();
} else if(el.msExitFullscreen) {
el.msExitFullscreen();
}
this.fullscreen = false;
this.updateFullscreenElements();
},
toggleFullscreen: function() {
this.fullscreen = !this.fullscreen;
if(this.fullscreen) {
this.enterFullscreen();
} else {
this.exitFullscreen();
}
setTimeout(function() {
quickAlgoInterface.onResize();
}, 500);
},
updateFullscreenState: function() {
if(document.fullscreenElement || document.msFullscreenElement || document.mozFullScreen || document.webkitIsFullScreen) {
this.fullscreen = true;
} else {
this.fullscreen = false;
}
this.updateFullscreenElements();
},
updateFullscreenElements: function() {
if(this.fullscreen) {
$('body').addClass('fullscreen');
$('#fullscreenButton').html('<i class="fas fa-compress"></i>');
} else {
$('body').removeClass('fullscreen');
$('#fullscreenButton').html('<i class="fas fa-expand"></i>');
}
},
registerFullscreenEvents: function() {
if(this.fullscreenEvents) { return; }
document.addEventListener("fullscreenchange", this.updateFullscreenState.bind(this));
document.addEventListener("webkitfullscreenchange", this.updateFullscreenState.bind(this));
document.addEventListener("mozfullscreenchange", this.updateFullscreenState.bind(this));
document.addEventListener("MSFullscreenChange", this.updateFullscreenState.bind(this));
this.fullscreenEvents = true;
},
loadUserTaskData: function(taskData) {
this.userTaskData = taskData;
},
loadSubjectFromUserTaskData : function() {
document.title = this.userTaskData.title;
$(".exerciseText").text(this.userTaskData.subject);
},
loadInterface: function(context, level) {
////TODO: function is called twice
// Load quickAlgo interface into the DOM
this.context = context;
quickAlgoImportLanguage();
this.strings = window.languageStrings;
this.level = level;
// if we don't have userTaskData loaded, then we load it from the subject
if (!this.userTaskData) {
// default userTaskData
this.userTaskData = {
title: document.title,
subject: $(".exerciseText").first().text(),
about: {
authors: "France-Ioi",
license: "CC BY-SA 4.0"
}
};
} else {
this.loadSubjectFromUserTaskData();
}
var gridHtml = "";
gridHtml += "<div id='gridButtonsBefore'></div>";
gridHtml += "<div class='gridArea'><div id='grid'></div></div>";
gridHtml += "<div id='gridButtonsAfter'></div>";
$("#gridContainer").html(gridHtml);
$("#blocklyLibContent").html(
"<div id='editorBar'>" +
"<div id='capacity' class='capacity'></div>" +
"<div class='buttons'>" +
"<button type='button' id='fullscreenButton' onclick='quickAlgoInterface.toggleFullscreen();'><span class='fas fa-expand'></span></button>" +
"<button type='button' class='displayHelpBtn' onclick='conceptViewer.show()'><span class='fas fa-question'></span></button>" +
"</div>" +
"</div>" +
"<div id='languageInterface'></div>"
);
// Buttons from buttonsAndMessages
var addTaskHTML = '<div id="displayHelperAnswering" class="contentCentered" style="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 scaleControl = '';
if(context.display && context.infos.buttonScaleDrawing) {
var scaleControl = '<div class="scaleDrawingControl">' +
'<label for="scaleDrawing"><input id="scaleDrawing" type="checkbox">' +
this.strings.scaleDrawing +
'</label>' +
'</div>';
}
var gridButtonsAfter = scaleControl
+ "<div id='testSelector'></div>"
//+ "<button type='button' id='submitBtn' class='btn btn-primary' onclick='task.displayedSubTask.submit()'>"
//+ this.strings.submitProgram
//+ "</button><br/>"
//+ "<div id='messages'><span id='tooltip'></span><span id='errors'></span></div>"
+ addTaskHTML;
$("#gridButtonsAfter").html(gridButtonsAfter);
$('#scaleDrawing').change(this.onScaleDrawingChange.bind(this));
this.createModeTaskToolbar();
this.createEditorMenu();
this.updateInterfaceCss();
this.updateBestAnswerStatus();
this.setupTaskIntro(level);
this.wrapIntroAndGrid();
FontsLoader.checkFonts();
this.registerFullscreenEvents();
if(!this.curMode || !$('#task').hasClass(this.curMode)) {
this.selectMode('mode-instructions');
}
if(!this.checkHeightInterval) {
this.checkHeightInterval = setInterval(this.checkHeight.bind(this), 1000);
}
setTimeout(function() {
quickAlgoInterface.onResize();
}, 100);
setTimeout(function() {
quickAlgoInterface.onResize();
}, 1000);
},
createEditorMenu: function() {
$('#tabsContainer').toggleClass('noLevelTabs', !displayHelper.hasLevels);
if(!$('#openEditorMenu').length) {
$("#tabsContainer").append("<div id='openEditorMenu' class='icon' onclick='quickAlgoInterface.toggleEditorMenu();'><span class='fas fa-bars'></span></div>");
}
if($('#editorMenu').length) { return; }
$("body").append('' +
"<div id='editorMenu' style='display: none;'>" +
"<div class='editorMenuHeader'>" +
"<div id='closeEditorMenu' onclick='quickAlgoInterface.closeEditorMenu();'><span class='fas fa-times'></span></div>" +
"<div>Menu</div>" +
"</div>" +
"<div class='editorActions'>" +
"<div rel='example' class='item' onclick='quickAlgoInterface.editorBtn(\"example\");'><span class='fas fa-paste'></span> " + this.strings.loadExample + "</div>" +
"<div rel='copy' class='item' onclick='quickAlgoInterface.editorBtn(\"copy\");'><span class='fas fa-copy'></span> " + this.strings.copy + "</div>" +
"<div rel='paste' class='item' onclick='quickAlgoInterface.editorBtn(\"paste\");'><span class='fas fa-paste'></span> " + this.strings.paste + "</div>" +
"<div rel='restart' class='item' onclick='quickAlgoInterface.editorBtn(\"restart\");'><span class='fas fa-trash-alt'></span> " + this.strings.restart + "</div>" +
"<div rel='save' class='item' onclick='quickAlgoInterface.editorBtn(\"save\");'><span class='fas fa-download'></span> " + this.strings.saveProgram + "</div>" +
"<div rel='load' class='item'>" +
"<input type='file' id='task-upload-file' " +
"onchange='quickAlgoInterface.loadPrograms(this)'>" +
"<span class='fas fa-upload'></span> " +
this.strings.reloadProgram +
"</div>" +
"<div rel='edit' class='item' onclick='quickAlgoInterface.editorBtn(\"edit\");'><span class='fas fa-pencil-alt'></span>" + this.strings.editButton + "</div>" +
"<div rel='best-answer' class='item' onclick='quickAlgoInterface.editorBtn(\"best-answer\");'><span class='fas fa-trophy'></span> " + this.strings.loadBestAnswer + "</div>" +
"<div rel='blockly-python' class='item' onclick='quickAlgoInterface.editorBtn(\"blockly-python\");'><span class='fas fa-file-code'></span> " + this.strings.blocklyToPython + "</div>" +
"<div rel='about' class='item' onclick='quickAlgoInterface.editorBtn(\"about\");'><span class='fas fa-question-circle'></span>" + this.strings.about + "</div>" +
"</div>" +
"<span id='saveUrl'></span>" +
"</div>"
);
this.updateControlsDisplay();
},
toggleEditorMenu: function() {
if(this.editorMenuIsOpen) {
this.closeEditorMenu();
} else {
this.openEditorMenu();
}
},
openEditorMenu: function() {
this.editorMenuIsOpen = true;
var menuWidth = $('#editorMenu').css('width');
$('#editorMenu').css('display','block');
$('body').animate({left: '-' + menuWidth}, 500);
this.updateControlsDisplay();
},
closeEditorMenu: function() {
this.editorMenuIsOpen = false;
$('body').animate({left: '0'}, 500, function() {
$('#editorMenu').css('display','none')
});
},
editorBtn: function(btn) {
// Handle an editor button press
this.closeEditorMenu();
if (btn == 'example') {
task.displayedSubTask.loadExample()
} else if (btn == 'copy') {
task.displayedSubTask.blocklyHelper.copyProgram();
} else if (btn == 'paste') {
task.displayedSubTask.blocklyHelper.pasteProgram();
} else if (btn == 'save') {
task.displayedSubTask.blocklyHelper.saveProgram();
} else if (btn == 'restart') {
displayHelper.restartAll();
} else if (btn == 'edit') {
this.openEditExercise();
} else if (btn == 'best-answer') {
displayHelper.retrieveAnswer();
} else if (btn == 'blockly-python') {
this.displayBlocklyPython();
} else if (btn == 'about') {
this.openAbout();
}
},
/**
* This function is to handle exit buttons and check if we must ask for confirmation or not.
* To check for confirmation it works this way:
* if for i in len(toCompare) step 2
* toCompare[i] === toCompare[i + 1]
* then we don't need confirmation because everything is the same
* The variable toCompare is variadic because we can confirm on more than two values later.
* Otherwise we show confirmation to ask the user if he is sure to quit the menu without saving
* @param toCompare An array containing at toCompare[i] the new value and at toCompare[i + 1] the old value (it
* compares both one to each other). Be careful, every compared value must be <b>exactly</b> the same object.
* @private This function is private because it is not usefull outside of interface-mobileFirst.js
* @return true if we can exit without problem, false otherwise
* @throws An error, if two compare.size() is not a multiple of 2
*/
_handleConfirmationExitWindow: function(toCompare) {
if (toCompare.length % 2 != 0) {
// this should never happen in prod, if an error is thrown the error come from the programmer.
throw "interface-mobileFirst.js: _handleConfirmationExitWindow: toCompare must be a multiple of 2, you did"
+ "something wrong!";
}
// if this variable remain true, then we don't need to show the confirm window
var same = true;
for (var i = 0; i < toCompare.length && same; i += 2) {
// here we must use the '===' operator, because the values must be the same in object, we can have boolean
// on one side
same = toCompare[i] === toCompare[i + 1];
}
// window.confirm will return true, if the user confirmed quitting without saving
return same || window.confirm(this.strings.quitWithoutSavingConfirmation);
},
/**
* This method close the popup without confirmation
*/
closePopup: function() {
$('#popupMessage').hide();
window.displayHelper.popupMessageShown = false;
},
/**
* This method call the function {@link #_handleConfirmationExitWindow} to check if we can close the window. If we
* can, then it close the popup window created with {@link #window.displayHelper.showPopupDialog}.
* @param toCompare The arguments to compare, you need to setup it this way: toCompare[i] is the newValue and
* toCompare[i + 1] is the old value (i step of 2).
* @public Because we must use it inside of button "onClick" functions, so we can't make it "private" because the
* strict private convention is not respected here (we must use that.closePopupWithConfirmation inside of a button).
*/
closePopupWithConfirmation: function(toCompare) {
if (this._handleConfirmationExitWindow(toCompare)) {
this.closePopup();
}
},
/**
* This function return the button hidden or not depending on the boolean in argument.
* This function can also be called without arguments and the button will not be hidden.
*
* The button is hidden in case we have not a license that we know about.
* @param hidden If the button should be hidden or not (or no argument in this case the button is shown)
* @return {string} The html for the button
*/
_getAboutLicenseButton: function(hidden, license) {
if (!hidden)
hidden = "";
else
hidden = "style='display: none;'";
return "<span id='aboutLicenseIcon' class='icon fas fa-question-circle' onclick='window.open(\""
+ this.licenses[license] + "\", \"_blank\");' " + hidden + "></span>";
},
openEditExercise: function() {
// in python, there are two "exerciseText", we need to selected only the first one
// there are two "exerciseText" in python, because we also have a "long" version of the
// subject
var title = this.userTaskData.title;
var subject = this.userTaskData.subject;
var authors = this.userTaskData.about.authors;
var license = this.userTaskData.about.license;
var aboutAuthorsLicenseSection = "<div id='aboutAuthorsLicense'>";
var authorsTxt = "<label for='author'>" + this.strings.authors + "</label>";
authorsTxt += "<input id='aboutAuthorsInput' type='text' name='author' value='" + authors + "'>";
var licenseOther = "";
if (!(license in this.licenses))
licenseOther = "selected";
var licenseDropdown = "<p>" + this.strings.license + "</p>" +
"<select name='chooseLicense' id='aboutLicenseDropdown'>";
for (var licenseName in this.licenses) {
var selected = "";
if (license === licenseName)
selected = "selected";
licenseDropdown += "<option value='" + licenseName + "'" + selected + ">" + licenseName + "</option>";
}
licenseDropdown += "<option value='" + this.strings.other + "' " + licenseOther + ">" + this.strings.other
+ "</option>";
licenseDropdown += "</select>";
var licenseInput = null;
if (!(license in this.licenses)) {
licenseDropdown += " " + this._getAboutLicenseButton(true, license);
licenseInput = " <input id='aboutLicenseInput' type='text' name='chooseLicenseTxt' value='"
+ license + "' placeholder='" + this.strings.otherLicense + "'>";
} else {
licenseDropdown += " " + this._getAboutLicenseButton(false, license);
licenseInput = " <input id='aboutLicenseInput' type='text' name='chooseLicenseTxt' value='' " +
"style='display: none;' placeholder='" + this.strings.otherLicense + "'>"
}
aboutAuthorsLicenseSection += authorsTxt;
aboutAuthorsLicenseSection += licenseDropdown + licenseInput + "</div>";
var editExerciseHtml = "<div class=\"content connectPi qpi\">" +
" <div class=\"panel-heading\">" +
" <h2 class=\"sectionTitle\">" +
" <span class=\"iconTag\"><i class=\"icon fas fa-pencil-alt\"></i></span>" +
this.strings.editWindowTitle +
" </h2>" +
" <div class=\"exit\" id=\"editclose\"><i class=\"icon fas fa-times\"></i></div>" +
" </div>" +
" <div class=\"panel-body\">" +
" <div id=\"editExerciseTitle\">" +
" <label>" + this.strings.titleEdition + "</label><input id=\"editExerciseTitleInput\" type=\"text\" value=\"" + title + "\"/>" +
" </div>" +
" <div id=\"editExerciseDescription\">" +
" <label>" + this.strings.descriptionEdition + "</label>" +
" <textarea rows=\"10\" id=\"editExerciseDescriptionTextarea\">" + subject + "</textarea>" +
" </div>" +
aboutAuthorsLicenseSection +
" <div id='panel-body-bottom'>" +
" <button id='saveExerciseChanges'>" + this.strings.saveAndQuit + "</button>" +
" </div>" +
" </div>" +
"</div>";
window.displayHelper.showPopupDialog(editExerciseHtml);
var that = this;
/**
* This method allow us to get the new license from the dropdown or the input box according to this predicate:
* if the dropdown has this.strings.other as selection, then we select the value of the input box.
* @return The new license
*/
function getLicenseChanges() {
var selectedDropdown = $('#aboutLicenseDropdown option:selected').text();
if (selectedDropdown === that.strings.other) {
return $('#aboutLicenseInput').val();
} else {
return selectedDropdown;
}
}
$("#editclose").click(function() {
var newTitle = $("#editExerciseTitleInput").val();
var newDesc = $("#editExerciseDescriptionTextarea").val();
var oldTitle = that.userTaskData.title;
var oldDescription = that.userTaskData.subject;
var newAuthors = $('#aboutAuthorsInput').val();
var newLicense = getLicenseChanges();
var oldAuthors = that.userTaskData.about.authors;
var oldLicense = that.userTaskData.about.license;
that.closePopupWithConfirmation([newTitle, oldTitle, newDesc, oldDescription, newAuthors,
oldAuthors, newLicense, oldLicense]);
});
$('#aboutLicenseDropdown').change(function() {
var val = $('#aboutLicenseDropdown option:selected').text();
if (val === that.strings.other) {
$("#aboutLicenseIcon").hide();
$("#aboutLicenseInput").show();
} else {
$("#aboutLicenseInput").hide();
$("#aboutLicenseIcon").attr("onclick", "window.open(\"" + that.licenses[val] + "\", \"_blank\");");
$("#aboutLicenseIcon").show();
}
});
$("#saveExerciseChanges").click(function() {
$('#popupMessage').hide();
window.displayHelper.popupMessageShown = false;
var newTitle = $("#editExerciseTitleInput").val();
var newSubject = $("#editExerciseDescriptionTextarea").val();
var newAuthors = $('#aboutAuthorsInput').val();
var newLicense = getLicenseChanges();
that.userTaskData.title = newTitle;
that.userTaskData.subject = newSubject;
that.userTaskData.about.authors = newAuthors;
that.userTaskData.about.license = newLicense;
that.loadSubjectFromUserTaskData();
});
},
openAbout: function() {
var that = this;
var authors = this.userTaskData.about.authors;
var license = this.userTaskData.about.license;
// if the license is not inside of our predefined licenses then we write it without "more details" button
var licenseTxt = this.strings.license;
if (!this.licenses[license])
licenseTxt += license;
else
licenseTxt += license + " " + this._getAboutLicenseButton(false, license);
var aboutAuthorsLicenseSection = "<p>" + this.strings.authors + " " + authors +"</p>" +
" <p>" + licenseTxt + "</p>";
var typeTxt = this.strings.exerciseTypeAbout["default"];
if (this.context.title)
typeTxt = this.strings.exerciseTypeAbout[this.context.title];
var aboutHtml = "<div class=\"content connectPi qpi\">" +
" <div class=\"panel-heading\">" +
" <h2 class=\"sectionTitle\">" +
" <span class=\"iconTag\"><i class=\"icon fas fa-question-circle\"></i></span>" +
this.strings.about +
" </h2>" +
" <div class=\"exit\" id=\"aboutclose\"><i class=\"icon fas fa-times\"></i></div>" +
" </div>" +
" <div class=\"panel-body\" id='aboutPanel'>"+
" <div id='aboutAuthorsLicense'>" +
aboutAuthorsLicenseSection +
" </div>" +
" <div id='aboutFranceIOI'>" +
" <br/>" +
" <p>" + typeTxt + "</p>" +
" </div>" +
" </div>" +
"</div>";
window.displayHelper.showPopupDialog(aboutHtml);
$("#aboutclose").click(function() {
that.closePopup();
});
},
loadPrograms: function(formElement) {
this.blocklyHelper.handleFiles(formElement.files);
resetFormElement($(formElement));
this.closeEditorMenu();
},
/**
* This function allow us to save the subject into the additional data saved inside of the interface
* This also save the element from the context
* @param additional The additional data where we should save subject
*/
saveAdditional: function(additional) {
if (this.options.canEditSubject) {
additional.userTaskData = this.userTaskData;
}
// save additional from context too
if (this.context.saveAdditional) {
this.context.saveAdditional(additional);
}
},
/**
* This function allow us to load the additional things for the exercise like subject/sensors for quickpi
* @param additional The additional object containing additional things to load
*/
loadAdditional: function(additional) {
// load subject if edition is enabled
if (additional.userTaskData && this.options.canEditSubject) {
this.userTaskData = additional.userTaskData;
this.loadSubjectFromUserTaskData();
}
// Load additional from context (sensors for quickpi for example)
if (this.context.loadAdditional) {
this.context.loadAdditional(additional);
}
},
setOptions: function(opt) {
// Load options from the task
// We use a class 'interfaceToggled' as using jquery's .toggle(true)
// would force an element to display, even if the layout wants it
// hidden. Using a class ensures we don't break the elements' display
// property from the layout
$.extend(this.options, opt);
this.updateControlsDisplay();
this.updateInterfaceCss();
if(opt.conceptViewer) {
conceptViewer.selectLanguage(opt.conceptViewerLang);
this.hasHelp = true;
} else {
this.hasHelp = false;
}
},
updateControlsDisplay: function() {
var hideControls = this.options.hideControls ? this.options.hideControls : {};
$('.displayHelpBtn').toggleClass('interfaceToggled', !this.hasHelp);
$('#editorMenu div[rel=example]').toggleClass('interfaceToggled', !this.options.hasExample);
$('#editorMenu div[rel=paste]').toggleClass('editorActionDisabled', !this.blocklyHelper || (this.blocklyHelper.canPaste() === null));
$('#editorMenu div[rel=paste]').toggleClass('editorActionForbidden', this.blocklyHelper && (this.blocklyHelper.canPaste() === false));
$('#editorMenu div[rel=restart]').toggleClass('interfaceToggled', !!hideControls.restart);
$('#editorMenu div[rel=save]').toggleClass('interfaceToggled', !!hideControls.saveOrLoad);
$('#editorMenu div[rel=load]').toggleClass('interfaceToggled', !!hideControls.saveOrLoad);
$('#editorMenu div[rel=best-answer]').toggleClass('interfaceToggled', !!hideControls.loadBestAnswer);
$('#editorMenu div[rel=blockly-python]').toggleClass('interfaceToggled', hideControls.blocklyToPython !== false || !this.blocklyHelper || !this.blocklyHelper.isBlockly);
$('#editorMenu div[rel=edit]').toggleClass('interfaceToggled', !this.options.canEditSubject);
var menuHidden = !this.options.hasExample && hideControls.restart && hideControls.saveOrLoad && hideControls.loadBestAnswer;
$('#openEditorMenu').toggleClass('interfaceToggled', !!menuHidden);
$('div.speedSlider').toggleClass('interfaceToggled', !!hideControls.speedSlider);
$('div.displaySpeedSlider').toggleClass('interfaceToggled', !!hideControls.speedSlider);
$('div.backToFirst').toggleClass('interfaceToggled', !!hideControls.backToFirst);
$('div.nextStep').toggleClass('interfaceToggled', !!hideControls.nextStep);
$('div.goToEnd').toggleClass('interfaceToggled', !!hideControls.goToEnd);
},
updateInterfaceCss: function() {
$('style#quickAlgoInterface').remove();
var taskIntroMaxHeight = this.options.introMaxHeight ? this.options.introMaxHeight : '33%';
$('head').append('' +
'<style id="quickAlgoInterface">' +
'@media screen and (min-width: 855px) and (min-height: 450px) and (orientation: landscape) { #taskIntro { max-height: '+taskIntroMaxHeight+'; }}' +
'</style>');
},
bindBlocklyHelper: function(blocklyHelper) {
this.blocklyHelper = blocklyHelper;
},
devMode: function() {
$('#editorMenu .item').show();
},
onScaleDrawingChange: function(e) {
var scaled = $(e.target).prop('checked');
$("#gridContainer").toggleClass('gridContainerScaled', scaled);
$("#blocklyLibContent").toggleClass('blocklyLibContentScaled', scaled);
this.context.setScale(scaled ? 2 : 1);
},
onEditorChangeFct: function() {
if(this.displayedAltCode == 'python') {
this.displayBlocklyPython();
}
},
onEditorChange: function() {
// This function will replace itself with the debounced onEditorChangeFct
this.onEditorChange = debounce(this.onEditorChangeFct.bind(this), 500, false);
this.onEditorChangeFct();
},
blinkRemaining: function(times, red) {
var capacity = $('.capacity');
if(times % 2 == 0) {
capacity.removeClass('capacityRed');
} else {
capacity.addClass('capacityRed');
}
this.delayFactory.destroy('blinkRemaining');
if(times > (red ? 1 : 0)) {
this.delayFactory.createTimeout('blinkRemaining', function() { quickAlgoInterface.blinkRemaining(times - 1, red); }, 400);
}
},
displayCapacity: function(info) {
// Display remaining capacity
// Accepts an info item with optional keys :
// -text : Text to display
// -warning : Display as a warning
// -invalid : Display as invalid (program can't be executed)
// -type : Type of the text displayed (capacity, forbidden, limited)
$('.capacity').html(info.text ? info.text : '');
if(info.invalid) {
this.blinkRemaining(11, true);
// Lock player controls
this.displayError(info.text, true);
if(displayHelper && info.popup && info.type == 'capacity' && !this.capacityPopupDisplayed[info.type]) {
// Display warning (only for capacity-type messages)
displayHelper.showPopupMessage(this.strings.capacityWarning, 'blanket', displayHelper.strings.alright, null, null, "warning");
this.capacityPopupDisplayed[info.type] = true;
}
} else if(info.warning) {
this.blinkRemaining(6);
this.displayError(null, true);
} else {
this.blinkRemaining(0);
this.displayError(null, true);
}
},
stepDelayMin: 25,
stepDelayMax: 250,
refreshStepDelay: function() {
var v = parseInt($('.speedCursor').val(), 10);
var delay = this.stepDelayMax - v;
task.displayedSubTask.setStepDelay(delay);
},
initPlaybackControls: function() {
var speedControls =
'<div class="speedControls">' +
'<div class="playerControls">' +
'<div class="icon backToFirst" onclick="quickAlgoInterface.playerControls(\'backToFirst\');"><span class="fas fa-fast-backward"></span></div>' +
'<div class="icon playPause play" onclick="quickAlgoInterface.playerControls(\'playPause\');"><span class="fas fa-play"></span></div>' +
'<div class="icon nextStep" onclick="quickAlgoInterface.playerControls(\'nextStep\');"><span class="fas fa-step-forward"></span></div>' +
'<div class="icon goToEnd" onclick="quickAlgoInterface.playerControls(\'goToEnd\');"><span class="fas fa-fast-forward"></span></div>' +
'<div class="icon displaySpeedSlider" onclick="quickAlgoInterface.playerControls(\'displaySpeedSlider\');"><span class="fas fa-tachometer-alt"></span></div>' +
'</div>' +
'<div class="speedSlider">' +
'<span class="icon hideSpeedSlider" onclick="quickAlgoInterface.playerControls(\'hideSpeedSlider\');"><span class="fas fa-tachometer-alt"></span></span>' +
'<span class="icon speedSlower" onclick="quickAlgoInterface.playerControls(\'speedSlower\');"><span class="fas fa-walking"></span></span>' +
'<input type="range" min="0" max="' +
(this.stepDelayMax - this.stepDelayMin) +
'" value="0" class="slider speedCursor" oninput="quickAlgoInterface.refreshStepDelay();" onchange="quickAlgoInterface.refreshStepDelay();"/>' +
'<span class="icon speedFaster" onclick="quickAlgoInterface.playerControls(\'speedFaster\');"><span class="fas fa-running"></span></span>' +
'</div>' +
'</div>';
if($('#task .speedControls').length) {
return;
}
// place speed controls depending on layout
// speed controls in taskToolbar on mobiles
// in intro on portrait tablets
// in introGrid on other layouts (landscape tablets and desktop)
$('#mode-player').append(speedControls);
$('#introGrid').append(speedControls);
this.updateControlsDisplay();
},
playerControls: function(ctrl) {
if(ctrl == 'backToFirst') {
task.displayedSubTask.stop();
this.setPlayPause(false);
} else if(ctrl == 'playPause') {
if($('.playerControls .playPause').hasClass('play')) {
this.refreshStepDelay();
this.setPlayPause(true);
task.displayedSubTask.play();
} else {
this.setPlayPause(false);
task.displayedSubTask.pause();
}
} else if(ctrl == 'nextStep') {
this.setPlayPause(false);
task.displayedSubTask.step();
} else if(ctrl == 'goToEnd') {
task.displayedSubTask.setStepDelay(0);
task.displayedSubTask.play();
this.setPlayPause(false);
} else if(ctrl == 'displaySpeedSlider') {
$('#mode-player').addClass('displaySpeedSlider');
$('#introGrid .speedControls').addClass('displaySpeedSlider');
} else if(ctrl == 'hideSpeedSlider') {
$('#mode-player').removeClass('displaySpeedSlider');
$('#introGrid .speedControls').removeClass('displaySpeedSlider');
} else if(ctrl == 'speedSlower') {
var el = $('.speedCursor'),
maxVal = parseInt(el.attr('max'), 10),
delta = Math.floor(maxVal / 10),
newVal = parseInt(el.val(), 10) - delta;
el.val(Math.max(newVal, 0));
quickAlgoInterface.refreshStepDelay();
} else if(ctrl == 'speedFaster') {
var el = $('.speedCursor'),
maxVal = parseInt(el.attr('max'), 10),
delta = Math.floor(maxVal / 10),
newVal = parseInt(el.val(), 10) + delta;
el.val(Math.min(newVal, maxVal));
quickAlgoInterface.refreshStepDelay();
}
},
setPlayPause: function(isPlaying) {
if(isPlaying) {
$('.playerControls .playPause').html('<span class="fas fa-pause"></span>');
$('.playerControls .playPause').removeClass('play').addClass('pause');
} else {
$('.playerControls .playPause').html('<span class="fas fa-play"></span>');
$('.playerControls .playPause').removeClass('pause').addClass('play');
}
},
initTestSelector: function (nbTestCases) {
// Create the DOM for the tests display
this.nbTestCases = nbTestCases;
var curLevel = this.level;
var testTabs = '<div class="tabs">';
for(var iTest=0; iTest<this.nbTestCases; iTest++) {
if(this.nbTestCases > 1) {
var curTest = iTest + 1;
var testImg = '';
// Test thumbnail
var levelTestImg = $('img#test_' + curLevel + '_' + curTest);
if(levelTestImg.length) {
testImg = '<div class="testThumbnail">' +
'<img src="' + levelTestImg.attr('src') + '" alt="grid thumbnail for test '+curTest+'" width=120 height=120/>' +
'</div>';
} else if (this.options.hasTestThumbnails) {
// hasTestThumbnails is a legacy option
// TODO :: remove
testImg = '<div class="testThumbnail">' +
'<img src="test_' + curLevel + '_' + curTest + '.png" alt="grid thumbnail for test '+curTest+'" width=120 height=120 />' +
'</div>';
}
testTabs += '' +
'<div id="testTab'+iTest+'" class="testTab" onclick="task.displayedSubTask.changeTestTo('+iTest+')">' +
'<span class="testTitle"></span>' +
testImg +
'</div>';
}
}
testTabs += "</div>";
$('#testSelector').html(testTabs);
this.updateTestSelector(0);
this.resetTestScores();
this.initPlaybackControls();
},
updateTestScores: function (testScores) {
// Display test results
var testData = task.displayedSubTask.data[task.displayedSubTask.level];
for(var iTest=0; iTest<testScores.length; iTest++) {
if(!testScores[iTest]) { continue; }
if(testScores[iTest].evaluating) {
var icon = '<span class="testResultIcon testEvaluating fas fa-spinner fa-spin" title="'+this.strings.evaluatingAnswer+'"></span>';
} else if(testScores[iTest].successRate >= 1) {
var icon = '\
<span class="testResultIcon testSuccess" title="'+this.strings.correctAnswer+'">\
<span class="fas fa-check"></span>\
</span>';
} else if(testScores[iTest].successRate > 0) {
var icon = '\
<span class="testResultIcon testPartial" title="'+this.strings.partialAnswer+'">\
<span class="fas fa-times"></span>\
</span>';
} else {
var icon = '\
<span class="testResultIcon testFailure" title="'+this.strings.wrongAnswer+'">\
<span class="fas fa-times"></span>\
</span>';
}
var testName = this.strings.testLabel + ' '+(iTest+1);
if (testData[iTest].hasOwnProperty("testName"))
{
testName = testData[iTest].testName;
}
$('#testTab'+iTest+' .testTitle').html(icon+' ' + testName);
}
},
resetTestScores: function () {
// Reset test results display
var testData = task.displayedSubTask.data[task.displayedSubTask.level];
for(var iTest=0; iTest<this.nbTestCases; iTest++) {
var testName = this.strings.testLabel + ' '+(iTest+1);
if (testData && testData[iTest] && testData[iTest].hasOwnProperty("testName"))
{
testName = testData[iTest].testName;
}
$('#testTab'+iTest+' .testTitle').html('<span class="testResultIcon">&nbsp;</span> ' + testName);
}
},
updateTestSelector: function (newCurTest) {
$("#testSelector .testTab").removeClass('currentTest');
$("#testSelector .testTab .testThumbnail").show();
$("#testTab"+newCurTest).addClass('currentTest');
$("#testTab"+newCurTest + " .testThumbnail").hide();
$("#task").append($('#messages'));
//$("#testTab"+newCurTest+" .panel-body").prepend($('#grid')).append($('#messages')).show();
},
updateBestAnswerStatus: function() {
this.hasBestAnswer = window.displayHelper && window.displayHelper.hasSavedAnswer();
$('.editorActions div[rel=best-answer]').toggleClass('editorActionDisabled', !this.hasBestAnswer);
},
createModeTaskToolbar: function() {
if($('#taskToolbar').length) { return; }
$("#task").append('' +
'<div id="taskToolbar">' +
'<div id="modeSelector">' +
'<div id="mode-instructions" class="mode" onclick="quickAlgoInterface.selectMode(\'mode-instructions\');">' +
'<span><span class="fas fa-file-alt"></span><span class="label">' + this.strings.instructions + '</span></span>' +
'</div>' +
'<div id="mode-editor" class="mode" onclick="quickAlgoInterface.selectMode(\'mode-editor\');">' +
'<span>' +
'<span class="fas fa-pencil-alt"></span>' +
'<span class="label">' + this.strings.editor + '</span>' +
'</span>' +
'<span>' +
"<span class='capacity'></span>" +
"<button type='button' onclick='quickAlgoInterface.toggleFullscreen();'><span class='fas fa-expand'></span></button>" +
"<button type='button' class='displayHelpBtn' onclick='conceptViewer.show()'><span class='fas fa-question'></span></button>" +
'</span>' +
'</div>' +
'<div id="mode-player" class="mode" onclick="quickAlgoInterface.selectMode(\'mode-player\');">' +
'<span class="fas fa-play selectIcon"></span>' +
'</div>' +
'</div>' +
'</div>');
},
selectMode: function(mode) {
if(mode === this.curMode) return;
$('#modeSelector').children('div').removeClass('active');
$('#modeSelector #' + mode).addClass('active');
$('#task').removeClass(this.curMode).addClass(mode);
$('#mode-player').removeClass('displaySpeedSlider'); // there should be a better way to achieve this
if(mode != 'mode-instructions' && this.blocklyHelper) {
this.blocklyHelper.reload();
}
if (mode === 'mode-editor') {
this.hideAnalysis();
} else if (mode === 'mode-player') {
this.showAnalysis();
}
this.curMode = mode;
this.onResize();
},
setupTaskIntro: function(level) {
if(this.taskIntroContent === null) {
this.taskIntroContent = $('#taskIntro').html();
}
$('#taskIntro').html(this.taskIntroContent);
if(level) {
for(var otherLevel in displayHelper.levelsRanks) {
if(otherLevel == level) { continue; }
$('#taskIntro .' + otherLevel).not('.'+level).remove();
}
$('#taskIntro .' + level).show();
}
var levelIntroContent = $('#taskIntro').html();
var hasLong = $('#taskIntro').find('.long').length;
if (hasLong) {
$('#taskIntro').addClass('hasLongIntro');
// if long version of introduction exists, append its content to #blocklyLibContent
// with proper title and close button
// add titles
// add display long version button
var introLong = '' +
'<div id="taskIntroLong" style="display:none;" class="panel">' +
'<div class="panel-heading">'+
'<h2 class="sectionTitle"><i class="fas fa-search-plus icon"></i>' + this.strings.introDetailsTitle + '</h2>' +
'<button type="button" class="closeLongIntro exit" onclick="quickAlgoInterface.toggleLongIntro(false);"><i class="fas fa-times"></i></button>' +
'</div><div class="panel-body">' +
levelIntroContent +
'</div>' +
'<div>';
$('#blocklyLibContent').append(introLong);
var renderTaskIntro = '' +
'<div class="introContent">' +
'<h2 class="introTitleIcon"><span class="fas fa-book icon"></span></h2>' +
levelIntroContent +
'</div>' +
'<div id="introControls">' +
'<button type="button" class="showLongIntro" onclick="quickAlgoInterface.toggleLongIntro();"></button>' +
'</div>';
$('#taskIntro').html(renderTaskIntro);
quickAlgoInterface.toggleLongIntro(false);
} else {
$('#taskIntro').html(
'<div class="introContent">' +
'<h2 class="introTitleIcon"><span class="fas fa-book icon"></span></h2>' +
levelIntroContent +
'</div>');
}
this.bindVideoBtns();
},
appendTaskIntro: function(html) {
if(this.taskIntroContent === null) {
this.taskIntroContent = $('#taskIntro').html();
}
this.taskIntroContent += html;
$('#taskIntro').html(this.taskIntroContent);
this.setupTaskIntro();
},
toggleLongIntro: function(forceNewState) {
if(forceNewState === false || this.longIntroShown) {
$('#taskIntroLong').removeClass('displayIntroLong');
$('.showLongIntro').html('<span class="fas fa-plus-circle icon"></span>' + this.strings.showDetails + '</button>');
this.longIntroShown = false;
} else {
$('#taskIntroLong').addClass('displayIntroLong');
$('.showLongIntro').html('<span class="fas fa-minus-circle icon"></span>' + this.strings.hideDetails + '</button>');
this.longIntroShown = true;
}
},
unloadLevel: function() {
// Called when level is unloaded
this.resetTestScores();
$('#quickAlgo-keypad').remove();
if(this.curMode == 'mode-editor') {
// Don't stay in editor mode as it can cause task display issues
this.selectMode('mode-instructions');
}
},
onResize: function(e) {
// 100% and 100vh work erratically on some mobile browsers (Safari on
// iOS) because of the toolbar, so we set directly the height as pixels
var browserHeight = document.documentElement.clientHeight;
var browserWidth = document.documentElement.clientWidth;
$('body').css('height', browserHeight);
if($('#miniPlatformHeader').length) {
$('#task').css('height', (browserHeight - 40) + 'px');
} else {
$('#task').css('height', '');
}
// Determine right size for editor
var languageArea = document.getElementById('blocklyContainer');
if(!languageArea) { return; }
var toolbarDiv = document.getElementById('taskToolbar');
var heightBeforeToolbar = toolbarDiv ? toolbarDiv.getBoundingClientRect().top - languageArea.getBoundingClientRect().top : Infinity;
var heightBeforeWindow = browserHeight - languageArea.getBoundingClientRect().top - 2;
if($('#taskToolbar').is(':visible')) {
// TODO :: why did we have a condition window.innerHeight < window.innerWidth ?
var targetHeight = Math.floor(Math.min(heightBeforeToolbar, heightBeforeWindow));
} else {
var targetHeight = Math.floor(heightBeforeWindow);
}
if($('#blocklyDiv').length) {
$('#blocklyDiv').height(targetHeight);
} else {
$('#blocklyContainer').height(targetHeight);
}
// Check whether we should set readOnly mode
this.readOnly = this.curMode == 'mode-player' &&
((browserWidth <= browserHeight && browserWidth < 767) ||
(browserWidth > browserHeight && browserWidth >= 480 && browserWidth <= 854));
if(this.blocklyHelper) {
this.blocklyHelper.setReadOnly(this.readOnly);
}
// Resize editor elements
if(this.blocklyHelper) {
this.blocklyHelper.onResize();
}
// Resize grid
if(task.displayedSubTask && $('#grid').is(':visible')) {
task.displayedSubTask.updateScale();
}
// Check size and hide overflow if less than 5 pixels, to avoid big
// scrollbars when the layout is just slightly off for some reason
$('body').css('overflow-x', document.documentElement.scrollWidth - browserWidth < 5 ? 'hidden' : '');
$('body').css('overflow-y', document.documentElement.scrollHeight - browserHeight < 5 ? 'hidden' : '');
},
checkHeight: function() {
var browserHeight = document.documentElement.clientHeight;
if(this.lastHeight !== null && this.lastHeight != browserHeight) {
this.onResize();
}
this.lastHeight = browserHeight;
},
displayNotification: function(type, message, lock, yesFunc, noFunc) {
if(lock) {
$('.notificationMessageLock.notificationMessageLock-'+type).remove();
} else {
$('.notificationMessage').not('.notificationMessageLock').remove();
}
if(!message) return;
var divClass = lock ? 'notificationMessageLock notificationMessageLock-'+type : '';
if(type == 'error') {
divClass += ' errorMessage';
var icon = 'fa-bell';
} else if(type == 'wait') {
divClass += ' waitMessage';
var icon = 'fa-clock';
} else {
divClass += ' successMessage';
var icon = 'fa-check';
}
var id = Math.random();
var html =
'<div class="notificationMessage '+divClass+'" data-id="'+id+'">' +
'<button type="button" class="close notificationMessageClose">'+
'<span class="fas fa-times"></span>'+
'</button>' +
'<div class="messageWrapper">' +
'<span class="icon fas ' + icon + '"></span>' +
'<p class="message">' + message + '</p>' +
'</div>' +
'</div>';
$("#taskToolbar").append($(html));
$("#introGrid .speedControls").append($(html));
function closeNotification(e) {
var targetNotification = $(e.currentTarget).closest('.notificationMessage');
if(!targetNotification) { return; }
var targetId = targetNotification.attr('data-id');
targetNotification.remove();
$(".notificationMessage[data-id='"+targetId+"']").remove();
}
$(".notificationMessage").not('.notificationMessageLock').click(function(e) {
closeNotification(e);
if(noFunc) { noFunc(); }
});
if(yesFunc) {
$('.notificationMessage .btn_yes').click(function(e) {
closeNotification(e);
yesFunc();
});
}
},
showPopupMessage: function(message, mode, yesButtonText, agreeFunc, noButtonText, avatarMood, defaultText, disagreeFunc) {
// Replacement for displayHelper's showPopupMessage in some cases
if(!this.context || !this.context.inlinePopupMessage || mode != 'blanket') { return false; }
message = message.replace(/<br\/?>/g, ' ');
var buttonYes = '<button class="btn btn_yes">' + (yesButtonText || this.strings.alright) + '</button>';
var buttonNo = '';
if(noButtonText != undefined) {
buttonNo = '&nbsp;<button class="btn btn_no">' + noButtonText + '</button>';
}
message += buttonYes + buttonNo;
this.displayNotification('success', message, false, agreeFunc, disagreeFunc);
return true;
},
displayError: function(message, lock) {
this.displayNotification('error', message, lock);
},
makeTestResult: function(results, link) {
return '' +
'<span class="testResults">' +
'<span class="' + (results.successRate < 1 ? 'testError' : 'testSuccess') + '">' +
this.strings.testLabel + ' ' + (results.iTestCase+1) + ' : ' +
(results.successRate < 1 ? this.strings.testError : this.strings.testSuccess) +
'</span>' +
(link ? ' <span class="testLink" onclick="quickAlgoInterface.runTestCase('+results.iTestCase+')">' + this.strings.seeTest + '</span>' : '') +
'</span>';
},
displayResults: function(mainResults, worstResults) {
if(mainResults.iTestCase == worstResults.iTestCase) {
this.displayError(mainResults.message);
} else {
this.displayError(this.makeTestResult(mainResults) + this.makeTestResult(worstResults, true));
}
},
runTestCase: function(iTestCase) {
task.displayedSubTask.changeTestTo(iTestCase);
task.displayedSubTask.setStepDelay(0);
task.displayedSubTask.run();
},
wrapIntroAndGrid: function() {
if ($('#introGrid').length) { return; }
$("#taskIntro, #gridContainer").wrapAll("<div id='introGrid'></div>");
},
bindVideoBtns: function() {
// TODO :: move that out of quickAlgoInterface?
$('button.videoBtn').off('click', this.videoBtnHandler);
$('button.videoBtn').on('click', this.videoBtnHandler).html('<span class="fas fa-play-circle icon"></span> ' + this.strings.displayVideo);
$('a.videoBtn').off('click', this.videoBtnHandler);
$('a.videoBtn').on('click', this.videoBtnHandler);
},
/**
* Shows the analysis container.
*/
showAnalysis: function() {
if (this.blocklyHelper.showSkulptAnalysis) {
this.blocklyHelper.showSkulptAnalysis();
}
},
/**
* Hides the analysis container.
*/
hideAnalysis: function() {
if (this.blocklyHelper.hideSkulptAnalysis) {
this.blocklyHelper.hideSkulptAnalysis();
}
},
videoBtnHandler: function() {
var that = $(this);
var video = $('<video controls></video>');
$.each(that[0].attributes, function() {
if(this.name == 'data-video') {
video.attr('src', this.value);
} else if(this.name == 'data-style') {
video.attr('style', this.value);
} else {
video.attr(this.name, this.value);
}
});
that.replaceWith(video);
video[0].play();
},
exportCurrentAsPng: function(name) {
if(typeof window.saveSvgAsPng == 'undefined') {
throw "Unable to export without save-svg-as-png. Please add 'save-svg-as-png' to the importModules statement.";
}
if(!name) { name = 'export.png'; }
var svgBbox = $('#blocklyDiv svg')[0].getBoundingClientRect();
var blocksBbox = $('#blocklyDiv svg > .blocklyWorkspace > .blocklyBlockCanvas')[0].getBoundingClientRect();
var svg = $('#blocklyDiv svg').clone();
svg.find('.blocklyFlyout, .blocklyMainBackground, .blocklyTrash, .blocklyBubbleCanvas, .blocklyScrollbarVertical, .blocklyScrollbarHorizontal, .blocklyScrollbarBackground').remove();
var options = {
backgroundColor: '#FFFFFF',
top: blocksBbox.top - svgBbox.top - 4,
left: blocksBbox.left - svgBbox.left - 4,
width: blocksBbox.width + 8,
height: blocksBbox.height + 8
};
window.saveSvgAsPng(svg[0], name, options);
},
renderKeypad: function() {
if($('#quickAlgo-keypad').length) { return; }
// Type of the screen element
var screenType = window.touchDetected ? 'div' : 'input';
var html = '' +
'<div id="quickAlgo-keypad"><div class="keypad">' +
' <div class="keypad-exit" data-btn="C"><span class="fas fa-times"></span></div>' +
' <div class="keypad-row">' +
' <'+screenType+' class="keypad-value"></'+screenType+'>' +
' </div>' +
' <div class="keypad-row keypad-row-margin">' +
' <div class="keypad-btn" data-btn="1">1</div>' +
' <div class="keypad-btn" data-btn="2">2</div>' +
' <div class="keypad-btn" data-btn="3">3</div>' +
' </div>' +
' <div class="keypad-row">' +
' <div class="keypad-btn" data-btn="4">4</div>' +
' <div class="keypad-btn" data-btn="5">5</div>' +
' <div class="keypad-btn" data-btn="6">6</div>' +
' </div>' +
' <div class="keypad-row">' +
' <div class="keypad-btn" data-btn="7">7</div>' +
' <div class="keypad-btn" data-btn="8">8</div>' +
' <div class="keypad-btn" data-btn="9">9</div>' +
' </div>' +
' <div class="keypad-row">' +
' <div class="keypad-btn" data-btn="0">0</div>' +
' <div class="keypad-btn" data-btn=".">.</div>' +
' <div class="keypad-btn" data-btn="-">+/-</div>' +
' </div>' +
' <div class="keypad-row keypad-row-margin">' +
' <div class="keypad-btn keypad-btn-r" data-btn="R"><span class="fas fa-backspace"></span></div>' +
' <div class="keypad-btn keypad-btn-v" data-btn="V"><span class="fas fa-check-circle"></span></div>' +
' </div>' +
'</div></div>';
$('body').append(html);
$('#quickAlgo-keypad').on('click keydown', quickAlgoInterface.handleKeypadKey);
},
handleKeypadKey: function(e) {
// Update if we detected a touch event
if($('input.keypad-value').length && window.touchDetected) {
$('input.keypad-value').replaceWith('<div class="keypad-value"></div>');
}
var finished = false;
var btn = null;
if(e && e.type == 'click') {
// Click on buttons
var btn = $(e.target).closest('div.keypad-btn, div.keypad-exit').attr('data-btn');
if(!btn && $(e.target).closest('div.keypad').length == 0) {
// Click outside of the keypad
finished = true;
}
} else if(e && e.type == 'keydown') {
// Key presses
// Note : keyCode is deprecated, but there aren't good
// cross-browser replacements as of now.
if(e.key && /^\d$/.test(e.key)) {
btn = e.key;
} else if(e.key == 'Backspace' || e.keyCode == 8) {
btn = 'R';
} else if(e.key == 'Enter' || e.keyCode == 13) {
btn = 'V';
} else if(e.key == 'Escape' || e.keyCode == 27) {
btn = 'C';
} else if(e.key == '.' || e.key == ',' || e.keyCode == 110 || e.keyCode == 188 || e.keyCode == 190) {
btn = '.';
} else if(e.key == '-' || e.keyCode == 54 || e.keyCode == 109) {
btn = '-';
} else if(e.keyCode >= 96 && e.keyCode <= 105) {
var btn = '' + (e.keyCode - 96);
}
e.preventDefault();
}
var data = quickAlgoInterface.keypadData;
if(btn == 'R') {
data.value = data.value.substring(0, data.value.length - 1);
if(data.value == '' || data.value == '-') { data.value = '0'; }
} else if(btn == 'C') {
data.value = data.initialValue;
finished = true;
} else if(btn == 'V') {
if(data.value == '') { data.value = '0'; }
finished = true;
} else if(btn == '0') {
data.value += '0';
} else if(btn == '-') {
if(data.value == '') {
data.value = '0';
}
if(data.value[0] == '-') {
data.value = data.value.substring(1);
} else {
data.value = '-' + data.value;
}
} else if(btn == '.') {
if(data.value == '') {
data.value = '0';
}
if(data.value.indexOf('.') == -1) {
data.value += '.';
}
} else if(btn) {
data.value += btn;
}
while(data.value.length > 1 && data.value.substring(0, 1) == '0' && data.value.substring(0, 2) != '0.') {
data.value = data.value.substring(1);
}
while(data.value.length > 2 && data.value.substring(0, 2) == '-0' && data.value.substring(0, 3) != '-0.') {
data.value = '-' + data.value.substring(2);
}
if(data.value.length > 16) {
data.value = data.value.substring(0, 16);
}
else if(data.value.length > 12) {
$('.keypad-value').addClass('keypad-value-small');
} else {
$('.keypad-value').removeClass('keypad-value-small');
}
var displayValue = data.value == '' ? '0' : data.value;
$('input.keypad-value').val(displayValue);
$('div.keypad-value').text(displayValue);
if(finished) {
$('#quickAlgo-keypad').hide();
// Second argument could be !!btn if we want to be able to click on
// the block's input
var finalValue = data.value == '' ? data.initialValue : data.value;
data.callbackFinished(parseFloat(finalValue), true);
return;
} else if(e !== null) {
data.callbackModify(parseFloat(data.value || 0));
}
$('input.keypad-value').focus();
},
displayKeypad: function(initialValue, position, callbackModify, callbackFinished) {
this.renderKeypad();
$('#quickAlgo-keypad').show();
$('.keypad').css('top', position.top).css('left', position.left);
quickAlgoInterface.keypadData = {
value: '',
initialValue: initialValue,
callbackModify: callbackModify,
callbackFinished: callbackFinished
};
quickAlgoInterface.handleKeypadKey(null);
},
hideAlternateCode: function() {
$('#quickAlgo-altcode').remove();
this.displayedAltCode = null;
},
displayBlocklyPython: function() {
if(!this.blocklyHelper || !this.blocklyHelper.canConvertBlocklyToPython()) {
return;
}
var code = this.blocklyHelper.getCode("python", null, true);
var strings = this.strings;
code = code.replace(/(\n\s*)pass *\n/g, function(m, w) { return w + strings.blocklyToPythonPassComment + '\n'; });
if(!$('#quickAlgo-altcode').length) {
var html = '' +
'<div id="quickAlgo-altcode" class="blanket">' +
' <div id="quickAlgo-altcode-header" class="panel-heading panel-heading-nopadding">' +
' <h2 class="sectionTitle"><span class="icon fas fa-code"></span>' + this.strings.blocklyToPythonTitle + '</h2>' +
' <div class="exit" onclick="quickAlgoInterface.hideAlternateCode();"><span class="icon fas fa-times"></span></div>' +
' </div>' +
' <p>' + this.strings.blocklyToPythonIntro + '</p>' +
' <textarea readonly></textarea>' +
'</div>';
$('#task').append(html);
dragElement($('#quickAlgo-altcode')[0]);
this.displayedAltCode = 'python';
$('#quickAlgo-altcode').on('mouseenter', function() {
$('#quickAlgo-altcode textarea').focus();
});
$('#quickAlgo-altcode').on('mouseleave', function() {
$('#quickAlgo-altcode textarea').blur();
});
}
$('#quickAlgo-altcode textarea').text(code.trim());
}
};
window.quickAlgoResponsive = true;
$(document).ready(function() {
FontsLoader.loadFonts(['fontawesome', 'titillium-web']);
var taskTitleTarget = $("#miniPlatformHeader table td").first();
if(taskTitleTarget.length) {
// Put title in miniPlatformHeader
$("#task h1").appendTo(taskTitleTarget);
} else {
// Remove title, the platform displays it
$("#task h1").remove();
}
window.addEventListener('resize', function() {
quickAlgoInterface.onResize();
}, false);
// Set up our popup handler in displayHelper
if(window.displayHelper) {
window.displayHelper.popupMessageHandler = function() {
return quickAlgoInterface.showPopupMessage.apply(quickAlgoInterface, arguments);
}
}
// Set up task calls
if(window.task) {
// Add autoHeight = true to metadata sent back
var beaverGetMetaData = window.task.getMetaData;
window.task.getMetaData = function(callback) {
beaverGetMetaData(function(res) {
res.autoHeight = true;
callback(res);
});
}
// If platform still calls getHeight despite autoHeight set to true,
// send back a fixed height of 720px to avoid infinite expansion
window.task.getHeight = function(callback) {
callback(720);
}
}
});