/** * Created by robert on 9/17/15. */ (function() { var problems,//this will the actual (sub)state machine stateNameTag = 'stateName'; //get the divs which are the main elements being change on transitions between states in the sub-state machine var divs = {}; divs['description'] = $('#description_outer_div');//$('div.col-lg-3.col-md-6.col-sm-12:has(#description)'); divs['code'] = $('#code_editor_outer_div');//$('div.col-lg-3.col-md-6.col-sm-12:has(#code_editor)'); divs['console'] = $('#console_outer_div');//$('div.col-lg-3.col-md-6.col-sm-12:has(#console)'); divs['info'] = $('#info_outer_div');//$('div.col-lg-3.col-md-6.col-sm-12:has(#info)'); //these tags connect the div to their respective state in the sub-state machine divs['description'].data(stateNameTag, 'description'); divs['code'].data(stateNameTag, 'code'); divs['console'].data(stateNameTag, 'console'); divs['info'].data(stateNameTag, 'info'); var eventName = 'mousedown',//event name of the event which will trigger the transition between these substates /** * the general function which should be called on the mousedown event to trigger the transition between states */ mouseDownEventFunction = function () { problems.transition($(this).data(stateNameTag)); }, /** * removes the above function from the divs */ removeListenersFromDivs = function () { $.each(divs, function (i, value) { value.off(eventName, mouseDownEventFunction); }); }, /** * removes the 'block' class from all divs we need to change when we change the current state */ removeBlockClassesFromDivs = function () { $.each(divs, function (i, value) { value.removeClass('block'); }); }, /** * removes all those special classes from the divs, which are set when a new state is entered and it also adds the 'block' class back */ removeChangedClassesFromDivs = function () { $.each(divs, function (i, value) { value.removeClass('block-focus block-less-width block-less-height block-less-everything').addClass('block');//these class names were chosen for the view where the screen is partitioned in 4 quarters (2 by 2) }); }, /** * * @param current the key of the state ('description','code','hint','console'), which is currently active, because it must not have a transition set */ setTransitionsBetweenDivs = function (current) { $.each(divs, function (key, value) { if (current !== key) value.on(eventName, mouseDownEventFunction); }); }, /** * * @param divsWithClasses a object where we have the keys of the divs object paired with the class name we want to assign to them e.g. : * { 'description': 'block-focus', 'code': 'block-less-width', 'console': 'block-less-height', 'info': 'block-less-everything' } */ addClassesToDivs = function (divsWithClasses) { $.each(divsWithClasses, function (divName, className) { divs[divName].addClass(className); }); }, substates = { 'description': { 'enter': function () { removeBlockClassesFromDivs(); addClassesToDivs({ 'description': 'block-focus', 'code': 'block-less-width', 'console': 'block-less-height', 'info': 'block-less-everything' }); setTransitionsBetweenDivs('description'); }, 'exit': function () { removeChangedClassesFromDivs(); removeListenersFromDivs(); } }, 'code': { 'enter': function () { removeBlockClassesFromDivs(); addClassesToDivs({ 'description': 'block-less-width', 'code': 'block-focus', 'console': 'block-less-everything', 'info': 'block-less-height' }); setTransitionsBetweenDivs('code'); }, 'exit': function () { removeChangedClassesFromDivs(); removeListenersFromDivs(); } }, 'info': { 'enter': function () { removeBlockClassesFromDivs(); addClassesToDivs({ 'description': 'block-less-everything', 'code': 'block-less-height', 'console': 'block-less-width', 'info': 'block-focus' }); setTransitionsBetweenDivs('info'); }, 'exit': function () { removeChangedClassesFromDivs(); removeListenersFromDivs(); } }, 'console': { 'enter': function () { removeBlockClassesFromDivs(); addClassesToDivs({ 'description': 'block-less-height', 'code': 'block-less-everything', 'console': 'block-focus', 'info': 'block-less-width' }); setTransitionsBetweenDivs('console'); }, 'exit': function () { removeChangedClassesFromDivs(); removeListenersFromDivs(); } } }; var pythonHandler; codeq.globalStateMachine.register('python', { 'enter': function (data) { $('#disabled').css('display', ''); $('#screen_prolog').css('display', '');//we have to show the screen now so the code editor shopws its initial values correctly pythonHandler = createPythonHandler(data.data);//codeq.createProgrammingLanguageHandler('prolog',data.data,codeq.makePythonTerminalHandler); problems = codeq.makeStateMachine(substates); problems.transition(divs['description'].data(stateNameTag)); Q.delay(100).then(function(){ $('div.col-lg-3.col-md-6.col-sm-12').addClass('transition'); }).done(); $('#disabled').css('display', 'none'); }, 'exit': function () { $('#screen_prolog').css('display', 'none'); $('div.col-lg-3.col-md-6.col-sm-12').removeClass('transition'); pythonHandler.destroy(); problems.destroy(); } }); // a constant var firstCharacterPos = {'line': 0, 'ch': 0}; //codeq.makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var terminal = codeq.makeConsole(jqConsole, { 'greeting': 'CodeQ Python terminal proxy' }), tcs = function terminalCommandSuccess (data) { if (data.code !== 0) { terminal.append(data.message, 'error'); } }, tcf = function terminalCommandFailed (error) { terminal.append(error + '\n', 'error'); }; terminal.onInput = function (text) { return codeq.comms.sendPush({ 'text': text + '\n' }).then(tcs, tcf); }; codeq.comms.on('terminal_output', function (data) { var text = data.text; terminal.append(text, 'output'); lines = text.split('\n'); terminal.leftmostCol = lines[lines.length-1].length; }); terminal.leftmostCol = 1; 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; } }; }; /** * Creates a new handler for the given Prolog assignment definition. * * @param {PrologTaskDef} info * @returns {{destroy: Function, processServerHints: Function}} */ var createPythonHandler = function (info) { var problem = info.problem, jqDescription = $('#description'), jqEditor = $('#code_editor'), jqConsole = $('#console'), jqHints = $('#info'), editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true, mode: 'python' }), activityHandler = makeActivityHandler(editor, problem.id), terminal = makePythonTerminalHandler(jqConsole, editor, problem.id, activityHandler), hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint); editor.setValue(info.solution); $('#title').text(problem.slug); jqDescription.html(problem.description); 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)}); } if (changeObj.text) { activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); } }); $('#btn_code_hint').on('click', function () { var doc = editor.getDoc(); codeq.comms.sendHint({ 'language': 'python', 'program': editor.getDoc().getValue(), 'problem_id': problem.id }).then( function hintSuccess(data) { if (data.code === 0) hinter.handle(data.hints); else terminal.append('error: ' + data.message); }, function hintFailed (error) { terminal.append('exception: ' + error); } ).done(); }); $('#btn_code_test').on('click', function () { var doc = editor.getDoc(); codeq.comms.sendTest({ 'language': 'python', 'program': editor.getDoc().getValue(), 'problem_id': problem.id }).then( function testSuccess(data) { if (data.code === 0) hinter.handle(data.hints); else terminal.append('error: ' + data.message); }, function testFailed (error) { terminal.append('exception: ' + error); } ).done(); }); // TODO first line of interpreter output is buffered without this, why? codeq.comms.sendPush({ 'text': '' }); return { destroy: function () { $('#btn_code_hint').off('click'); $('#btn_code_test').off('click'); editor.off('change'); hinter.destroy(); terminal.destroy(); jqDescription.empty(); jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it jqConsole.empty(); // TODO: the same with the console jqDescription = null; jqEditor = null; jqConsole = null; jqHints = null; } }; }; })();