$(function() { var ACE_FILES_PATH = '/assets/ace/'; var ADEQUATE_PERCENTAGE = 50; var ALT_1_KEY_CODE = 161; var ALT_2_KEY_CODE = 8220; var ALT_3_KEY_CODE = 182; var ALT_4_KEY_CODE = 162; var ALT_R_KEY_CODE = 174; var ALT_S_KEY_CODE = 8218; var ALT_T_KEY_CODE = 8224; var FILENAME_URL_PLACEHOLDER = '{filename}'; var SUCCESSFULL_PERCENTAGE = 90; var THEME = 'ace/theme/textmate'; var REMEMBER_TAB = false; var AUTOSAVE_INTERVAL = 15 * 1000; var REQUEST_FOR_COMMENTS_DELAY = 3 * 60 * 1000; var editors = []; var editor_for_file = new Map(); var regex_for_language = new Map(); var tracepositions_regex; var resetTurtle = true; var active_file = undefined; var active_frame = undefined; var running = false; var qa_api = undefined; var output_mode_is_streaming = true; var websocket, turtlescreen, numMessages = 0, turtlecanvas = $('#turtlecanvas'), prompt = $('#prompt'), commands = ['input', 'write', 'turtle', 'turtlebatch', 'render', 'exit', 'timeout', 'status'], streams = ['stdin', 'stdout', 'stderr']; var ENTER_KEY_CODE = 13; var flowrOutputBuffer = ""; var QaApiOutputBuffer = {'stdout': '', 'stderr': ''}; var flowrResultHtml = '
' var ajax = function(options) { return $.ajax(_.extend({ dataType: 'json', method: 'POST', }, options)); }; var ajaxError = function(response) { var message = ((response || {}).responseJSON || {}).message || ''; $.flash.danger({ text: message.length > 0 ? message : $('#flash').data('message-failure') }); }; var clearOutput = function() { $('#output pre').remove(); }; var collectFiles = function() { var editable_editors = _.filter(editors, function(editor) { return !editor.getReadOnly(); }); return _.map(editable_editors, function(editor) { return { content: editor.getValue(), file_id: $(editor.container).data('file-id') }; }); }; var configureEditors = function() { _.each(['modePath', 'themePath', 'workerPath'], function(attribute) { ace.config.set(attribute, ACE_FILES_PATH); }); }; var confirmDestroy = function(event) { event.preventDefault(); if (confirm($(this).data('message-confirm'))) { destroyFile(); } }; var confirmReset = function(event) { event.preventDefault(); if (confirm($(this).data('message-confirm'))) { resetCode(); } }; //var confirmSubmission = function(event) { // event.preventDefault(); // if (confirm($(this).data('message-confirm'))) { // submitCode(); // } //}; var createSubmission = function(initiator, filter, callback) { showSpinner(initiator); var jqxhr = ajax({ data: { submission: { cause: $(initiator).data('cause') || $(initiator).prop('id'), exercise_id: $('#editor').data('exercise-id'), files_attributes: (filter || _.identity)(collectFiles()) }, annotations_arr: [] }, dataType: 'json', method: 'POST', url: $(initiator).data('url') || $('#editor').data('submissions-url') }); jqxhr.always(hideSpinner); jqxhr.done(createSubmissionCallback); jqxhr.done(callback); jqxhr.fail(ajaxError); }; var createSubmissionCallback = function(data){ // set all frames context types to submission $('.frame').each(function(index, element) { $(element).data('context-type', 'Submission'); }); // update the ids of the editors and reload the annotations for (var i = 0; i < editors.length; i++) { // set the data attribute to submission //$(editors[i].container).data('context-type', 'Submission'); var file_id_old = $(editors[i].container).data('file-id'); // file_id_old is always set. Either it is a reference to a teacher supplied given file, or it is the actual id of a new user created file. // This is the case, since it is set via a call to ancestor_id on the model, which returns either file_id if set, or id if it is not set. // therefore the else part is not needed any longer... // if we have an file_id set (the file is a copy of a teacher supplied given file) and the new file-ids are present in the response if (file_id_old != null && data.files){ // if we find file_id_old (this is the reference to the base file) in the submission, this is the match for(var j = 0; j< data.files.length; j++){ if(data.files[j].file_id == file_id_old){ //$(editors[i].container).data('id') = data.files[j].id; $(editors[i].container).data('id', data.files[j].id ); } } } } // toggle button states (it might be the case that the request for comments button has to be enabled toggleButtonStates(); }; var destroyFile = function() { createSubmission($('#destroy-file'), function(files) { return _.reject(files, function(file) { return file.file_id === active_file.id; }); }, window.CodeOcean.refresh); }; var downloadCode = function(event) { event.preventDefault(); createSubmission(this, null,function(response) { var url = response.download_url; // to download just a single file, use the following url //var url = response.download_file_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); window.location = url; }); }; var evaluateCode = function(url, callback) { initWebsocketConnection(url, callback); }; var fileActionsAvailable = function() { return isActiveFileRenderable() || isActiveFileRunnable() || isActiveFileStoppable() || isActiveFileTestable(); }; var findOrCreateOutputElement = function(index) { if ($('#output-' + index).isPresent()) { return $('#output-' + index); } else { var element = $('').attr('id', 'output-' + index); $('#output').append(element); return element; } }; var findOrCreateRenderElement = function(index) { if ($('#render-' + index).isPresent()) { return $('#render-' + index); } else { var element = $('').attr('id', 'render-' + index); $('#render').append(element); return element; } }; var getPanelClass = function(result) { if (result.stderr && !result.score) { return 'panel-danger'; } else if (result.score < 1) { return 'panel-warning'; } else { return 'panel-success'; } }; var getProgressBarClass = function(percentage) { if (percentage < ADEQUATE_PERCENTAGE) { return 'progress-bar progress-bar-striped progress-bar-danger'; } else if (percentage < SUCCESSFULL_PERCENTAGE) { return 'progress-bar progress-bar-striped progress-bar-warning'; } else { return 'progress-bar progress-bar-striped progress-bar-success'; } }; var handleKeyPress = function(event) { if (event.which === ALT_1_KEY_CODE) { showWorkspaceTab(event); } else if (event.which === ALT_2_KEY_CODE) { showTab(1); } else if (event.which === ALT_3_KEY_CODE) { showTab(2); } else if (event.which === ALT_R_KEY_CODE) { $('#run').trigger('click'); } else if (event.which === ALT_S_KEY_CODE) { $('#assess').trigger('click'); } else if (event.which === ALT_T_KEY_CODE) { $('#test').trigger('click'); } else { return; } event.preventDefault(); }; var lastCopyText; var handleCopyEvent = function(text){ lastCopyText = text; }; var handlePasteEvent = function (pasteObject) { //console.log("Handling paste event. this is ", this ); //console.log("Text: " + pasteObject.text); var same = (lastCopyText === pasteObject.text); //console.log("Text is the same: " + same); // if the text is not copied from within the editor (from any file), send an event to lanalytics if(!same){ publishCodeOceanEvent("codeocean_editor_paste", { text: pasteObject.text, exercise: $('#editor').data('exercise-id'), file_id: "1" }); } }; var handleScoringResponse = function(websocket_event) { results = JSON.parse(websocket_event.data); printScoringResults(results); var score = _.reduce(results, function(sum, result) { return sum + result.score * result.weight; }, 0).toFixed(2); $('#score').data('score', score); renderScore(); showTab(2); }; var handleQaApiOutput = function() { if (qa_api) { qa_api.executeCommand('syncOutput', [[QaApiOutputBuffer]]); // reset the object } QaApiOutputBuffer = {'stdout': '', 'stderr': ''}; } // activate flowr only for half of the audience var isFlowrEnabled = true;//parseInt($('#editor').data('user-id'))%2 == 0; var handleStderrOutputForFlowr = function() { if (!isFlowrEnabled) return var flowrUrl = $('#flowrHint').data('url'); var flowrHintBody = $('#flowrHint .panel-body'); var queryParameters = { query: flowrOutputBuffer } flowrHintBody.empty(); jQuery.getJSON(flowrUrl, queryParameters, function(data) { jQuery.each(data.queryResults, function(index, question) { var collapsibleTileHtml = flowrResultHtml.replace(/{{collapseId}}/g, 'collapse-' + question).replace(/{{headingId}}/g, 'heading-' + question); var resultTile = $(collapsibleTileHtml); resultTile.find('h4 > a').text(question.title + ' | Found via ' + question.source); resultTile.find('.panel-body').html(question.body); resultTile.find('.panel-body').append('Open this question'); flowrHintBody.append(resultTile); }); if (data.queryResults.length !== 0) { $('#flowrHint').fadeIn(); } }) flowrOutputBuffer = ''; }; var handleTestResponse = function(websocket_event) { result = JSON.parse(websocket_event.data); clearOutput(); printOutput(result, false, 0); if (qa_api) { qa_api.executeCommand('syncOutput', [result]); } showStatus(result); showTab(1); }; var hideSpinner = function() { $('button i.fa').show(); $('button i.fa-spin').hide(); }; var autosaveTimer; var autosaveLabel = $("#autosave-label span"); var resetSaveTimer = function(){ clearTimeout(autosaveTimer); autosaveTimer = setTimeout(autosave, AUTOSAVE_INTERVAL); }; var autosave = function(){ var date = new Date(); autosaveLabel.parent().css("visibility", "visible"); autosaveLabel.text(date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds()); autosaveLabel.text(date.toLocaleTimeString()); autosaveTimer = null; createSubmission($('#autosave'), null); } var initializeEditors = function() { $('.editor').each(function(index, element) { var editor = ace.edit(element); if (qa_api) { editor.getSession().on("change", function (deltaObject) { qa_api.executeCommand('syncEditor', [active_file, deltaObject]); }); } var document = editor.getSession().getDocument(); // insert pre-existing code into editor. we have to use insertLines, otherwise the deltas are not properly added var file_id = $(element).data('file-id'); var content = $('.editor-content[data-file-id=' + file_id + ']'); setActiveFile($(element).parent().data('filename'), file_id); document.insertLines(0, content.text().split(/\n/)); // remove last (empty) that is there by default line document.removeLines(document.getLength()-1,document.getLength()-1); editor.setReadOnly($(element).data('read-only') !== undefined); editor.setShowPrintMargin(false); editor.setTheme(THEME); editor.commands.bindKey("ctrl+alt+0", null); editors.push(editor); editor_for_file.set($(element).parent().data('filename'), editor); var session = editor.getSession(); session.setMode($(element).data('mode')); session.setTabSize($(element).data('indent-size')); session.setUseSoftTabs(true); session.setUseWrapMode(true); // set regex for parsing error traces based on the mode of the main file. if( $(element).parent().data('role') == "main_file"){ tracepositions_regex = regex_for_language.get($(element).data('mode')); } var file_id = $(element).data('id'); /* * Register event handlers */ // editor itself editor.on("paste", handlePasteEvent); editor.on("copy", handleCopyEvent); // listener for autosave session.on("change", function (deltaObject) { resetSaveTimer(); }); }); }; var initializeEventHandlers = function() { $(document).on('click', '#results a', showOutput); $(document).on('keypress', handleKeyPress); $('a[data-toggle="tab"]').on('show.bs.tab', storeTab); initializeFileTreeButtons(); initializeWorkflowButtons(); initializeWorkspaceButtons(); initializeRequestForComments() }; var initializeFileTree = function() { $('#files').jstree($('#files').data('entries')); $('#files').on('click', 'li.jstree-leaf', function() { active_file = { filename: $(this).text(), id: parseInt($(this).attr('id')) }; var frame = $('[data-file-id="' + active_file.id + '"]').parent(); showFrame(frame); toggleButtonStates(); }); }; var initializeFileTreeButtons = function() { $('#create-file').on('click', showFileDialog); $('#destroy-file').on('click', confirmDestroy); $('#download').on('click', downloadCode); $('#request-for-comments').on('click', requestComments); }; var initializeRegexes = function(){ regex_for_language.set("ace/mode/python", /File "(.+?)", line (\d+)/g); regex_for_language.set("ace/mode/java", /(.*\.java):(\d+):/g); } var initializeTooltips = function() { $('[data-tooltip]').tooltip(); }; var initializeWorkflowButtons = function() { $('#start').on('click', showWorkspaceTab); //$('#submit').on('click', confirmSubmission); $('#submit').on('click', submitCode); }; var initializeWorkspaceButtons = function() { $('#assess').on('click', scoreCode); // todo $('#dropdown-render, #render').on('click', renderCode); $('#dropdown-run, #run').on('click', runCode); $('#dropdown-stop, #stop').on('click', stopCode); // todo $('#dropdown-test, #test').on('click', testCode); // todo $('#save').on('click', saveCode); $('#start-over').on('click', confirmReset); }; var initializeRequestForComments = function () { var button = $('.requestCommentsButton'); button.hide(); button.on('click', function() { $('#comment-modal').modal('show'); }); $('#askForCommentsButton').on('click', requestComments); setTimeout(function() { button.fadeIn(); }, REQUEST_FOR_COMMENTS_DELAY); }; var isActiveFileExecutable = function() { return 'executable' in active_frame.data(); }; var setActiveFile = function (filename, fileId) { active_file = { filename: filename, id: fileId }; }; var isActiveFileRenderable = function() { return 'renderable' in active_frame.data(); }; var isActiveFileRunnable = function() { return isActiveFileExecutable() && ['main_file', 'user_defined_file'].includes(active_frame.data('role')); }; var isActiveFileStoppable = function() { return isActiveFileRunnable() && running; }; var isActiveFileSubmission = function() { return ['Submission'].includes(active_frame.data('contextType')); }; var isActiveFileTestable = function() { return isActiveFileExecutable() && ['teacher_defined_test', 'user_defined_test'].includes(active_frame.data('role')); }; var isBrowserSupported = function() { // websockets is used for run, score and test return Modernizr.websockets; }; var populatePanel = function(panel, result, index) { panel.removeClass('panel-default').addClass(getPanelClass(result)); panel.find('.panel-title .filename').text(result.filename); panel.find('.panel-title .number').text(index + 1); panel.find('.row .col-sm-9').eq(0).find('.number').eq(0).text(result.passed); panel.find('.row .col-sm-9').eq(0).find('.number').eq(1).text(result.count); panel.find('.row .col-sm-9').eq(1).find('.number').eq(0).text((result.score * result.weight).toFixed(2)); panel.find('.row .col-sm-9').eq(1).find('.number').eq(1).text(result.weight); panel.find('.row .col-sm-9').eq(2).text(result.message); if (result.error_messages) panel.find('.row .col-sm-9').eq(3).text(result.error_messages.join(', ')); panel.find('.row .col-sm-9').eq(4).find('a').attr('href', '#output-' + index); }; var resetOutputTab = function() { clearOutput(); $('#hint').fadeOut(); $('#flowrHint').fadeOut(); showTab(1); } var printOutput = function(output, colorize, index) { var element = findOrCreateOutputElement(index); // disable streaming if desired //if (output.stdout && output.stdout.length >= 20 && output.stdout.substr(0,20) == "##DISABLESTREAMING##"){ // output_mode_is_streaming = false; //} if (!colorize) { if(output.stdout != undefined && output.stdout != ''){ element.append(output.stdout) } if(output.stderr != undefined && output.stderr != ''){ element.append('There was an error: StdErr: ' + output.stderr); } } else if (output.stderr) { element.addClass('text-warning').append(output.stderr); flowrOutputBuffer += output.stderr; QaApiOutputBuffer.stderr += output.stderr; } else if (output.stdout) { //if (output_mode_is_streaming){ element.addClass('text-success').append(output.stdout); flowrOutputBuffer += output.stdout; QaApiOutputBuffer.stdout += output.stdout; //}else{ // element.addClass('text-success'); // element.data('content_buffer' , element.data('content_buffer') + output.stdout); //} //} else if (output.code && output.code == '200'){ // element.append( element.data('content_buffer')); } else { element.addClass('text-muted').text($('#output').data('message-no-output')); } }; var printScoringResult = function(result, index) { $('#results').show(); var panel = $('#dummies').children().first().clone(); populatePanel(panel, result, index); $('#results ul').first().append(panel); }; var printScoringResults = function(response) { $('#results ul').first().html(''); $('.test-count .number').html(response.length); clearOutput(); _.each(response, function(result, index) { printOutput(result, false, index); printScoringResult(result, index); }); if (_.some(response, function(result) { return result.status === 'timeout'; })) { showTimeoutMessage(); } if (_.some(response, function(result) { return result.status === 'container_depleted'; })) { showContainerDepletedMessage(); } if (qa_api) { // send test response to QA qa_api.executeCommand('syncOutput', [response]); } }; // Publishing events for other (JS) components to react to codeocean events var publishCodeOceanEvent = function (eventName, contextData) { var payload = { user: { type: 'User', uuid: $('#editor').data('user-id') }, verb: { type: eventName }, resource: { type: 'page', uuid: document.location.href }, timestamp: new Date().toISOString(), with_result: {}, in_context: contextData }; $.ajax("https://open.hpi.de/lanalytics/log", { type: 'POST', cache: false, dataType: 'JSON', data: payload, success: {}, error: {} }) }; var renderCode = function(event) { event.preventDefault(); if ($('#render').is(':visible')) { createSubmission(this, null, function(response) { var url = response.render_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); var pop_up_window = window.open(url); if (pop_up_window) { pop_up_window.onerror = function(message) { clearOutput(); printOutput({ stderr: message }, true, 0); sendError(message, response.id); showTab(1); }; } }); } }; var renderHint = function(object) { var hint = object.data || object.hint; if (hint) { $('#hint .panel-body').text(hint); $('#hint').fadeIn(); } }; var renderProgressBar = function(score, maximum_score) { var percentage = score / maximum_score * 100; var progress_bar = $('#score .progress-bar'); progress_bar.removeClass().addClass(getProgressBarClass(percentage)); progress_bar.attr({ 'aria-valuemax': maximum_score, 'aria-valuemin': 0, 'aria-valuenow': score }); progress_bar.css('width', percentage + '%'); }; var renderScore = function() { var score = parseFloat($('#score').data('score')); var maxium_score = parseFloat($('#score').data('maximum-score')); if (score >= 0 && score <= maxium_score && maxium_score >0 ) { var percentage_score = (score / maxium_score * 100 ).toFixed(0); $('.score').html(percentage_score + '%'); } else { $('.score').html( 0 + '%'); } renderProgressBar(score, maxium_score); }; var resetCode = function() { showSpinner(this); ajax({ method: 'GET', url: $('#start-over').data('url') }).success(function(response) { hideSpinner(); _.each(editors, function(editor) { var file_id = $(editor.container).data('file-id'); var file = _.find(response.files, function(file) { return file.id === file_id; }); editor.setValue(file.content); }); }); }; var runCode = function(event) { event.preventDefault(); if ($('#run').is(':visible')) { createSubmission(this, null, function(response) { $('#stop').data('url', response.stop_url); running = true; showSpinner($('#run')); toggleButtonStates(); var url = response.run_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); evaluateCode(url, function(evt) { parseCanvasMessage(evt.data, true); }); }); } }; var saveCode = function(event) { event.preventDefault(); createSubmission(this, null, function() { $.flash.success({ text: $('#save').data('message-success') }); }); }; var sendError = function(message, submission_id) { showSpinner($('#render')); var jqxhr = ajax({ data: { error: { message: message, submission_id: submission_id } }, url: $('#editor').data('errors-url') }); jqxhr.always(hideSpinner); jqxhr.success(renderHint); }; var scoreCode = function(event) { event.preventDefault(); createSubmission(this, null, function(response) { showSpinner($('#assess')); var url = response.score_url; evaluateCode(url, handleScoringResponse); }); }; var showFileDialog = function(event) { event.preventDefault(); createSubmission(this, null, function(response) { $('#code_ocean_file_context_id').val(response.id); $('#modal-file').modal('show'); }); }; var showFirstFile = function() { var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first(); var file_id = frame.find('.editor').data('file-id'); setActiveFile(frame.data('filename'), file_id); $('#files').jstree().select_node(file_id); showFrame(frame); toggleButtonStates(); }; var showFrame = function(frame) { active_frame = frame; $('.frame').hide(); frame.show(); }; var showOutput = function(event) { event.preventDefault(); showTab(1); $('#output').scrollTo($(this).attr('href')); }; var showRequestedTab = function() { if(REMEMBER_TAB){ var regexp = /tab=(\d+)/; if (regexp.test(window.location.search)) { var index = regexp.exec(window.location.search)[1] - 1; } else { var index = localStorage.tab; } } else { // else start with first tab. var index = 0; } showTab(index); }; var showSpinner = function(initiator) { $(initiator).find('i.fa').hide(); $(initiator).find('i.fa-spin').show(); }; var showStatus = function(output) { if (output.status === 'timeout') { showTimeoutMessage(); } else if (output.status === 'container_depleted') { showContainerDepletedMessage(); } else if (output.stderr) { $.flash.danger({ icon: ['fa', 'fa-bug'], text: $('#run').data('message-failure') }); } /* do not show the success message any longer, puzzles and distracts users. else { $.flash.success({ icon: ['fa', 'fa-check'], text: $('#run').data('message-success') }); } */ }; var showContainerDepletedMessage = function() { $.flash.danger({ icon: ['fa', 'fa-clock-o'], text: $('#editor').data('message-depleted') }); }; var showTab = function(index) { $('a[data-toggle="tab"]').eq(index || 0).tab('show'); }; var showTimeoutMessage = function() { $.flash.info({ icon: ['fa', 'fa-clock-o'], text: $('#editor').data('message-timeout') }); }; var showWebsocketError = function() { $.flash.danger({ text: $('#flash').data('message-failure') }); } var showWorkspaceTab = function(event) { if(event){ event.preventDefault(); } showTab(0); }; var stopCode = function(event) { event.preventDefault(); if (isActiveFileStoppable()) { killWebsocketAndContainer(); } }; var killWebsocketAndContainer = function() { if (websocket.readyState != WebSocket.OPEN) { return; } websocket.send(JSON.stringify({cmd: 'exit'})); websocket.flush(); websocket.close(); if(turtlescreen != null){ resetTurtle = true; } hideSpinner(); running = false; toggleButtonStates(); hidePrompt(); } var storeTab = function(event) { localStorage.tab = $(event.target).parent().index(); }; var submitCode = function() { createSubmission($('#submit'), null, function(response) { if (response.redirect) { localStorage.removeItem('tab'); window.location = response.redirect; } }); }; var testCode = function(event) { event.preventDefault(); if ($('#test').is(':visible')) { createSubmission(this, null, function(response) { showSpinner($('#test')); var url = response.test_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); evaluateCode(url, handleTestResponse); }); } }; var toggleButtonStates = function() { $('#destroy-file').prop('disabled', active_frame.data('role') !== 'user_defined_file'); $('#dropdown-render').toggleClass('disabled', !isActiveFileRenderable()); $('#dropdown-run').toggleClass('disabled', !isActiveFileRunnable() || running); $('#dropdown-stop').toggleClass('disabled', !isActiveFileStoppable()); $('#dropdown-test').toggleClass('disabled', !isActiveFileTestable()); $('#dummy').toggle(!fileActionsAvailable()); $('#editor-buttons .dropdown-toggle').toggle(fileActionsAvailable()); $('#render').toggle(isActiveFileRenderable()); $('#run').toggle(isActiveFileRunnable() && !running); $('#stop').toggle(isActiveFileStoppable()); $('#test').toggle(isActiveFileTestable()); }; var initWebsocketConnection = function(url, onmessageFunction) { //TODO: get the protocol from config file dependent on environment. (dev: ws, prod: wss) //causes: Puma::HttpParserError: Invalid HTTP format, parsing fails. //TODO: make sure that this gets cached. websocket = new WebSocket('<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url); websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection websocket.onclose = function(evt) { /* expected at some point */ }; websocket.onmessage = onmessageFunction; websocket.onerror = function(evt) { showWebsocketError(); }; websocket.flush = function() { this.send('\n'); } }; var initTurtle = function() { // todo guard clause if turtle is not required for the current exercise // clear canvas // turtlecanvas.getContext("2d").clearRect(0, 0, turtlecanvas.width, turtlecanvas.height); if(resetTurtle) { turtlescreen = new Turtle(websocket, turtlecanvas); showCanvas(); resetTurtle = false; } }; var initPrompt = function() { if ($('#run').isPresent()) { $('#run').bind('click', hidePrompt); } if ($('#prompt').isPresent()) { $('#prompt').on('keypress', handlePromptKeyPress); $('#prompt-submit').on('click', submitPromptInput); } } var executeWebsocketCommand = function(msg) { if ($.inArray(msg.cmd, commands) == -1) { console.log("Unknown command: " + msg.cmd); // skipping unregistered commands is required // as we may receive mirrored response due to internal behaviour return; } switch(msg.cmd) { case 'input': showPrompt(msg); break; case 'write': printWebsocketOutput(msg); break; case 'turtle': initTurtle(); showCanvas(); handleTurtleCommand(msg); break; case 'turtlebatch': initTurtle(); showCanvas(); handleTurtlebatchCommand(msg); break; case 'render': renderWebsocketOutput(msg); break; case 'exit': killWebsocketAndContainer(); handleQaApiOutput(); handleStderrOutputForFlowr(); augmentStacktraceInOutput(); break; case 'timeout': // just show the timeout message here. Another exit command is sent by the rails backend when the socket to the docker container closes. showTimeoutMessage(); break; case 'status': showStatus(msg) break; } }; var jumpToSourceLine = function(event){ var file = $(event.target).data('file'); var line = $(event.target).data('line'); showWorkspaceTab(null); // set active file ?!?! var frame = $('div.frame[data-filename="' + file + '"]'); showFrame(frame); var editor = editor_for_file.get(file); editor.gotoLine(line, 0); }; var augmentStacktraceInOutput = function() { if(tracepositions_regex){ var element = $('#output>pre'); var text = element.text(); element.on( "click", "a", jumpToSourceLine); var matches; while(matches = tracepositions_regex.exec(text)){ var frame = $('div.frame[data-filename="' + matches[1] + '"]') if(frame.length > 0){ element.html(text.replace(matches[0], "" + matches[0] + "")); } } } }; var renderWebsocketOutput = function(msg){ var element = findOrCreateRenderElement(0); element.append(msg.data); }; var printWebsocketOutput = function(msg) { if (!msg.data) { return; } //msg.data = msg.data.replace(/(\r\n|\n|\r)/gm, "
"); msg.data = msg.data.replace(/(\r)/gm, "\n"); var stream = {}; stream[msg.stream] = msg.data; printOutput(stream, true, 0); }; var handleTurtleCommand = function(msg) { if (msg.action in turtlescreen) { result = turtlescreen[msg.action].apply(turtlescreen, msg.args); websocket.send(JSON.stringify({cmd: 'result', 'result': result})); } else { websocket.send(JSON.stringify({cmd: 'exception', exception: 'AttributeError', message: msg.action})); } websocket.flush(); }; var handleTurtlebatchCommand = function(msg) { for (i = 0; i < msg.batch.length; i++) { cmd = msg.batch[i]; turtlescreen[cmd[0]].apply(turtlescreen, cmd[1]); } }; var handlePromptKeyPress = function(evt) { if (evt.which === ENTER_KEY_CODE) { submitPromptInput(); } } var submitPromptInput = function() { var input = $('#prompt-input'); var message = input.val(); websocket.send(JSON.stringify({cmd: 'result', 'data': message})); websocket.flush(); input.val(''); hidePrompt(); } var parseCanvasMessage = function(message, recursive) { var msg; message = message.replace(/^\s+|\s+$/g, ""); try { // todo validate json instead of catching msg = JSON.parse(message); } catch (e) { if (!recursive) { return false; } // why does docker sometimes send multiple commands at once? message = message.replace(/^\s+|\s+$/g, ""); messages = message.split("\n"); for (var i = 0; i < messages.length; i++) { if (!messages[i]) { continue; } parseCanvasMessage(messages[i], false); } return; } executeWebsocketCommand(msg); }; var showPrompt = function(msg) { var label = $('#prompt .input-group-addon'); label.text(msg.data || label.data('prompt')); if (prompt.isPresent() && prompt.hasClass('hidden')) { prompt.removeClass('hidden'); } $('#prompt input').focus(); } var hidePrompt = function() { if (prompt.isPresent() && !prompt.hasClass('hidden')) { prompt.addClass('hidden'); } } var showCanvas = function() { if ($('#turtlediv').isPresent() && turtlecanvas.hasClass('hidden')) { // initialize two-column layout $('#output-col1').addClass('col-lg-7 col-md-7 two-column'); turtlecanvas.removeClass('hidden'); } }; var hideCanvas = function() { if ($('#turtlediv').isPresent() && !(turtlecanvas.hasClass('hidden'))) { output = $('#output-col1'); if (output.hasClass('two-column')) { output.removeClass('col-lg-7 col-md-7 two-column'); } turtlecanvas.addClass('hidden'); } }; var requestComments = function() { var user_id = $('#editor').data('user-id') var exercise_id = $('#editor').data('exercise-id') var file_id = $('.editor').data('id') var question = $('#question').val(); var createRequestForComments = function(submission) { $.ajax({ method: 'POST', url: '/request_for_comments', data: { request_for_comment: { exercise_id: exercise_id, file_id: file_id, submission_id: submission.id, question: question } } }).done(function() { hideSpinner(); $.flash.success({ text: $('#askForCommentsButton').data('message-success') }); }).error(ajaxError); } createSubmission($('.requestCommentsButton'), null, createRequestForComments); $('#comment-modal').modal('hide'); var button = $('.requestCommentsButton'); button.fadeOut(); } var initializeCodePilot = function() { if ($('#questions-column').isPresent() && (typeof QaApi != 'undefined') && QaApi.isBrowserSupported()) { $('#editor-column').addClass('col-md-8').removeClass('col-md-10'); $('#questions-column').addClass('col-md-3'); var node = document.getElementById('questions-holder'); var url = $('#questions-holder').data('url'); qa_api = new QaApi(node, url); } } // save on quit $(window).on("beforeunload", function() { if(autosaveTimer){ autosave(); } }); if ($('#editor').isPresent()) { if (isBrowserSupported()) { initializeRegexes(); initializeCodePilot(); $('.score, #development-environment').show(); configureEditors(); initializeEditors(); initializeEventHandlers(); initializeFileTree(); initializeTooltips(); initPrompt(); renderScore(); showFirstFile(); showRequestedTab(); } else { $('#alert').show(); } } });