diff options
author | Timotej Lazar <timotej.lazar@araneo.org> | 2015-09-30 16:43:56 +0200 |
---|---|---|
committer | Timotej Lazar <timotej.lazar@araneo.org> | 2015-09-30 16:43:56 +0200 |
commit | be66c44e2b882219c44bcf0de3238f6e3d62cfad (patch) | |
tree | a61ab9f176ef0f5b7ee672eca0f762c052c7eb65 | |
parent | 20ba9213950544c6a7f1dd04af160bf674561e3b (diff) |
Add the robot problem screen
Mostly a copy of the Python screen, a lot of functionality is missing.
-rw-r--r-- | index.html | 34 | ||||
-rw-r--r-- | js/codeq/language.js | 8 | ||||
-rw-r--r-- | js/codeq/navigation.js | 6 | ||||
-rw-r--r-- | js/codeq/robot.js | 300 |
4 files changed, 345 insertions, 3 deletions
@@ -40,6 +40,7 @@ <li style="display: none;" id="navigation-problem"><a href="">Problem</a></li> <li style="display: none;" id="navigation-python"><a href="">Python</a></li> <li style="display: none;" id="navigation-prolog"><a href="">Prolog</a></li> + <li style="display: none;" id="navigation-robot"><a href="">Robot</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <p class="navbar-text" id="signed-in-title"></p> @@ -215,6 +216,38 @@ </div><!--/row--> </div><!--container--> + <!-- problem screen: robot --> + <div class="container-fluid quadrants block1" id="screen_robot" style="display: none;"> + <div class="row"> + <div class="col-lg-3 col-md-6 col-sm-12 block block1"> + <h2 class="title translatable" data-dict="robot" data-tkey="slug"></h2> + <div class="description translatable" data-dict="robot" data-tkey="description"></div> + <div class="block-label">Instructions</div> + </div> + <div class="col-lg-3 col-md-6 col-sm-12 block block2"> + <nav class="navbar navbar-default"> + <div class="container-fluid"> + <button type="button" class="btn btn-default navbar-btn btn-plan">Plan</button> + <button type="button" class="btn btn-default navbar-btn btn-hint">Hint</button> + <button type="button" class="btn btn-default navbar-btn btn-run">Run</button> + <input type="text" id="robot_ip" placeholder="Robot's IP"></input> + <button type="button" class="btn btn-default navbar-btn btn-stop">Stop</button> + </div> + </nav> + <div class="code_editor"></div> + <div class="block-label">Code</div> + </div> + <div class="col-lg-3 col-md-6 col-sm-12 block block3"> + <div class="console"></div> + <div class="block-label">Console</div> + </div> + <div class="col-lg-3 col-md-6 col-sm-12 block block4"> + <div class="hints"></div> + <div class="block-label">Hints</div> + </div> + </div><!--/row--> + </div><!--container--> + <!-- problem screen: profile --> <div class="container-fluid" id="screen_profile" style="display: none;"> <h2> @@ -436,6 +469,7 @@ <script src="js/codeq/hint.js"></script> <script src="js/codeq/prolog.js"></script> <script src="js/codeq/python.js"></script> + <script src="js/codeq/robot.js"></script> <script src="js/codeq/login.js"></script> <script src="js/codeq/profile.js"></script> <script src="js/codeq/language.js"></script> diff --git a/js/codeq/language.js b/js/codeq/language.js index aac4e78..0893e33 100644 --- a/js/codeq/language.js +++ b/js/codeq/language.js @@ -6,8 +6,10 @@ var jqScreen = $('#screen_language'),
jqProlog = $('#choose-prolog'),
jqPython = $('#choose-python'),
+ jqRobot = $('#choose-robot'),
chooseProlog = function () {codeq.globalStateMachine.transition('problem', 'prolog');},
- choosePython = function () {codeq.globalStateMachine.transition('problem', 'python');};
+ choosePython = function () {codeq.globalStateMachine.transition('problem', 'python');},
+ chooseRobot = function () {codeq.globalStateMachine.transition('problem', 'robot');};
codeq.globalStateMachine.register('language',{
'enter': function(){
@@ -16,13 +18,15 @@ jqScreen.css('display', '');
jqProlog.on('click', chooseProlog);
jqPython.on('click', choosePython);
+ jqRobot.on('click', chooseRobot);
},
'exit' : function(){
jqProlog.off();
jqPython.off();
+ jqRobot.off();
jqScreen.css('display', 'none');
$('#navigation-language').css('display', 'none').removeClass("active");
}
});
-})();
\ No newline at end of file +})();
diff --git a/js/codeq/navigation.js b/js/codeq/navigation.js index 80fd321..4eff834 100644 --- a/js/codeq/navigation.js +++ b/js/codeq/navigation.js @@ -97,6 +97,10 @@ codeq.globalStateMachine.transition('prolog'); e.preventDefault(); }); + $('#navigation-robot').on('click', function(e){ + codeq.globalStateMachine.transition('robot'); + e.preventDefault(); + }); $('#navigation-logout').on('click', function(e){ codeq.globalStateMachine.transition('login'); e.preventDefault();//prevent this since we'll trigger a page reload otherwise @@ -106,4 +110,4 @@ e.preventDefault();//prevent this since we'll trigger a page reload otherwise }); -})();
\ No newline at end of file +})(); 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); + } + }; + }; +})(); |