diff options
author | Marko Pušnik <marko.pusnik@guru.si> | 2015-09-30 19:07:37 +0200 |
---|---|---|
committer | Marko Pušnik <marko.pusnik@guru.si> | 2015-09-30 19:07:37 +0200 |
commit | 8bb68a1e979022ead00535c25403b341f2cb24bb (patch) | |
tree | 0ad16cf9acb81cbb6f8484f32fed657e8c66dabb /js/codeq/robot.js | |
parent | ba6135a83273c625097fe7cdb59319a51acca31e (diff) | |
parent | 0da1117cfc28688633be7b8382aa60435bf740eb (diff) |
Merge branch 'master' of odie.guru.si:codeq-web
Conflicts:
index.html
Diffstat (limited to 'js/codeq/robot.js')
-rw-r--r-- | js/codeq/robot.js | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/js/codeq/robot.js b/js/codeq/robot.js new file mode 100644 index 0000000..f2f4a9b --- /dev/null +++ b/js/codeq/robot.js @@ -0,0 +1,300 @@ +/** + * The robot state of the state machine. When it is entered it'll prepare the code editor and load a sub-state machine which represents the 3 different parts of the screen. + */ + +(function() { + var subScreens, //this will be the actual (sub)state machine + stateNameTag = 'stateName', //a tag for data which is added to some html elements + jqScreen = $('#screen_robot'), // the screen container element + //quadrants + jqDescription = jqScreen.find('.block1'), + jqCode = jqScreen.find('.block2'), + jqConsole = jqScreen.find('.block3'), + jqInfo = jqScreen.find('.block4'), + jqAllQuadrants = jqDescription.add(jqCode).add(jqConsole).add(jqInfo), // all the quadrants + // buttons + jqBtnPlan = jqScreen.find('.btn-plan'), + jqBtnHint = jqScreen.find('.btn-hint'), + jqBtnRun = jqScreen.find('.btn-run'), + jqBtnStop = jqScreen.find('.btn-stop'), + jqInfoButtons = jqBtnPlan.add(jqBtnHint), // all info-focusing buttons + jqAllButtons = jqInfoButtons.add(jqBtnRun).add(jqBtnStop), // all buttons + // misc + currentSubState = null, + transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below) + substates = { + 'description': { + 'enter': function () { + currentSubState = 'block1'; + jqScreen.addClass(currentSubState); + }, + 'exit': function () { + jqScreen.removeClass(currentSubState); + currentSubState = null; + } + }, + 'code': { + 'enter': function () { + currentSubState = 'block2'; + jqScreen.addClass(currentSubState); + }, + 'exit': function () { + jqScreen.removeClass(currentSubState); + currentSubState = null; + } + }, + 'info': { + 'enter': function () { + currentSubState = 'block4'; + jqScreen.addClass(currentSubState); + }, + 'exit': function () { + jqScreen.removeClass(currentSubState); + currentSubState = null; + } + }, + 'console': { + 'enter': function () { + currentSubState = 'block3'; + jqScreen.addClass(currentSubState); + }, + 'exit': function () { + jqScreen.removeClass(currentSubState); + currentSubState = null; + } + } + }; + var robotHandler; //created when we enter the robot state and destroyed once we leave it + codeq.globalStateMachine.register('robot', { + 'enter': function (problemDef, commonDef, currentSolution) { + $('#navigation-language').css('display', ''); + $('#navigation-problem').css('display', ''); + $("#navigation-robot").addClass("active"); + $('#navigation-robot').css('display', ''); + + jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly + robotHandler = createRobotHandler(problemDef, commonDef, currentSolution); + subScreens = codeq.makeStateMachine(substates); + subScreens.transition(jqDescription.data(stateNameTag)); +/* Q.delay(100).then(function(){ + jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading + }).done();*/ + jqInfoButtons.on(transitionEventName, function (event) { + subScreens.transition('info'); // set focus on the hints quadrant + event.stopPropagation(); // don't allow the event to go on and trigger further transition + }); + jqBtnRun.on(transitionEventName, function (event) { + subScreens.transition('console'); // set focus on the hints quadrant + event.stopPropagation(); // don't allow the event to go on and trigger further transition + }); + + jqAllQuadrants.on(transitionEventName, function () { + subScreens.transition($(this).data(stateNameTag)); + }); + }, + 'exit': function () { + jqAllButtons.off(); // unregister all event handlers + jqAllQuadrants.off(); + jqScreen.css('display', 'none'); +// jqAllQuadrants.removeClass('transition'); + robotHandler.destroy(); + robotHandler = null; + subScreens.destroy(); + subScreens = null; + jqScreen.addClass('block1'); + + $('#navigation-language').css('display', 'none'); + $('#navigation-problem').css('display', 'none'); + $("#navigation-robot").removeClass("active"); + $('#navigation-robot').css('display', 'none'); + } + }); + + jqDescription.data(stateNameTag, 'description'); + jqCode.data(stateNameTag, 'code'); + jqConsole.data(stateNameTag, 'console'); + jqInfo.data(stateNameTag, 'info'); + + // a constant + var firstCharacterPos = {'line': 0, 'ch': 0}; + + var makeRobotTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { + var terminal = codeq.makeConsole(jqConsole, { + 'greeting': 'CodeQ Robot terminal proxy', + 'autoHistory': true + }); + + terminal.onInput = function (text) { + terminal.append('Not implemented.\n', 'output'); + }; + return terminal; + }; + + var makeActivityHandler = function (editor, problem_id) { + var lastActivityMillis = Date.now(), + deltaActivityMillis = function deltaActivityMillisFunc () { + var now = Date.now(), + dt = now - lastActivityMillis; + lastActivityMillis = now; + return dt; + }, + queue = [], + ts = null, + timer = function () { + var promise; + ts = null; + if (queue.length === 0) return Q(true); + promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id); + queue.length = 0; + return promise; + }, + flush = function () { + clearTimeout(ts); + return timer(); + }; + + return { + 'queueTrace': function (trace) { + trace['dt'] = deltaActivityMillis(); + queue.push(trace); + if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds + return this; + }, + 'flush': flush, + 'addAndPurge': function (trace) { + var accumulatedTrace = queue; + queue = []; + trace['dt'] = deltaActivityMillis(); + accumulatedTrace.push(trace); + if (ts !== null) { + clearTimeout(ts); + ts = null; + } + return accumulatedTrace; + } + }; + }; + + codeq.on('init', function (args) { + codeq.tr.registerDictionary('robot', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active + }); + + /** + * Creates a new handler for the given Prolog assignment definition. + * + * @param {PrologTaskDef} info + * @returns {{destroy: Function, processServerHints: Function}} + */ + var createRobotHandler = function (problemDef, commonDef, currentSolution) { + var //problem = info.problem, + jqDescriptionContent = jqDescription.find('.description'), + jqEditor = jqCode.find('.code_editor'), + jqTerminal = jqConsole.find('.console'), + jqHints = jqInfo.find('.hints'), + editor = codeq.makeEditor(jqEditor[0], { + mode: 'python', + indentUnit: 4 + }), + activityHandler = makeActivityHandler(editor, problemDef.id), + terminal = makeRobotTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler), + hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'robot_hints', problemDef, commonDef), + commError = function (error) { + alert(error); + }; + + codeq.tr.registerDictionary('robot', problemDef.translations); + codeq.tr.translateDom(jqScreen); + if (currentSolution) editor.setValue(currentSolution); + jqBtnPlan.prop('disabled', !hinter.hasNextPlan()); + + editor.on('change', function (instance, changeObj) { + var doc = editor.getDoc(), + pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)); + + if (changeObj.removed) { + activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed.join(''))}); + } + + if (changeObj.text) { + activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); + } + }); + + jqBtnPlan.on('click', function () { + if (!hinter.planNext()) { + jqBtnPlan.prop('disabled', true).blur(); + } + }); + jqBtnHint.on('click', function () { + var doc = editor.getDoc(); + codeq.comms.sendHint({ + 'language': 'robot', + 'program': editor.getDoc().getValue(), + 'problem_id': problemDef.id + }) + .then(function (data) { + if (data.code === 0) { + hinter.handle(data.hints); + } + else { + terminal.append('error: ' + data.message); + } + }) + .fail(commError) + .done(); + }); + jqBtnRun.on('click', function () { + var url = 'http://' + $('#robot_ip').val() + ':8000/run', + doc = editor.getDoc(); + + $.ajax(url, { + method: 'POST', + data: JSON.stringify({program: doc.getValue()}) + }).done(function (data) { + if (data.code === 0) { + terminal.append('<run>\n', 'output'); + } + else { + terminal.append('error: ' + data.message + '\n'); + } + }).fail(function (jqXHR, textStatus, err) { + commError('Could not access '+url); + }); + }); + jqBtnStop.on('click', function () { + var url = 'http://' + $('#robot_ip').val() + ':8000/stop'; + + $.ajax(url, { + method: 'POST' + }).done(function (data) { + if (data.code === 0) { + terminal.append('<stop>\n', 'output'); + } + else { + terminal.append('error: ' + data.message + '\n'); + } + }).fail(function (jqXHR, textStatus, err) { + commError('Could not access '+url); + }); + }); + + return { + destroy: function () { + $('#screen_robot .title').text('');//empty the title text + jqAllButtons.off(); + editor.off('change'); + codeq.comms.off('terminal_output'); // stop listening for the terminal events from server + hinter.destroy(); + terminal.destroy(); + jqDescriptionContent.empty(); + jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it + jqTerminal.empty(); // TODO: the same with the console + jqDescriptionContent = null; + jqEditor = null; + jqTerminal = null; + jqHints = null; + codeq.tr.registerDictionary('robot', codeq.tr.emptyDictionary); + } + }; + }; +})(); |