/*
Online Python Tutor
https://github.com/pgbovine/OnlinePythonTutor/
Copyright (C) Philip J. Guo (philip@pgbovine.net)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// TODO: combine and minify with https://github.com/mishoo/UglifyJS2
// and add version numbering using a ?-style query string to prevent
// caching snafus
// Pre-reqs:
// - pytutor.js
// - jquery.ba-bbq.min.js
// - jquery.ba-dotimeout.min.js // for event debouncing: http://benalman.com/code/projects/jquery-dotimeout/examples/debouncing/
// - opt-frontend-common.js
// - js/togetherjs/togetherjs-min.js
// should all be imported BEFORE this file
// NASTY GLOBALS for socket.io!
var reconnectAttempts = 0;
var logEventQueue = []; // TODO: make sure this doesn't grow too large if socketio isn't enabled
var originFrontendJsFile = 'opt-frontend.js';
// for OPT live chat tutoring interface
var tutorRequested = false;
var helpQueueSize = 0;
var tutorAvailable = false;
var tutorWaitText = 'Please wait for the next available tutor.';
var activateSyntaxErrorSurvey = true; // true;
function setHelpQueueSizeLabel() {
if (helpQueueSize == 1) {
$("#helpQueueText").html('There is 1 person in line.');
}
else if (helpQueueSize == 0 || helpQueueSize > 1) {
$("#helpQueueText").html('There are ' + helpQueueSize + ' people in line.');
}
}
function requestTutor() {
$("#getTutorBtn,#ssDiv,#surveyHeader").hide(); // hide ASAP!
$("#togetherjsStatus").html("Please wait ... requesting a tutor");
tutorRequested = true;
TogetherJS();
}
function startSharedSession() { // override default
$("#getTutorBtn,#ssDiv,#surveyHeader").hide(); // hide ASAP!
$("#adHeader").hide(); // hide ASAP!
$("#togetherjsStatus").html("Please wait ... loading shared session");
tutorRequested = false;
TogetherJS();
}
function TogetherjsReadyHandler() {
$("#getTutorBtn,#surveyHeader").hide();
if (tutorRequested) {
$.get(TogetherJSConfig_hubBase + 'request-help',
{url: TogetherJS.shareUrl(), id: TogetherJS.shareId()},
null /* don't use a callback; rely on SSE */);
$("#togetherjsStatus").html('
\
Please wait for the next available tutor. \
');
setHelpQueueSizeLabel(); // run after creating span#helpQueueText
}
else {
populateTogetherJsShareUrl();
}
}
function TogetherjsCloseHandler() {
if (tutorAvailable) {
$("#getTutorBtn").show();
}
if (appMode == "display") {
$("#surveyHeader").show();
}
}
function getBaseBackendOptionsObj() {
var ret = {cumulative_mode: ($('#cumulativeModeSelector').val() == 'true'),
heap_primitives: ($('#heapPrimitivesSelector').val() == 'true'),
show_only_outputs: false,
py_crazy_mode: ($('#pythonVersionSelector').val() == '2crazy'),
origin: originFrontendJsFile};
var surveyObj = getSurveyObject();
if (surveyObj) {
ret.survey = surveyObj;
}
return ret;
}
function getBaseFrontendOptionsObj() {
var ret = {// tricky: selector 'true' and 'false' values are strings!
disableHeapNesting: ($('#heapPrimitivesSelector').val() == 'true'),
textualMemoryLabels: ($('#textualMemoryLabelsSelector').val() == 'true'),
executeCodeWithRawInputFunc: executeCodeWithRawInput,
// always use the same visualizer ID for all
// instantiated ExecutionVisualizer objects,
// so that they can sync properly across
// multiple clients using TogetherJS. this
// shouldn't lead to problems since only ONE
// ExecutionVisualizer will be shown at a time
visualizerIdOverride: '1',
updateOutputCallback: function() {$('#urlOutput,#embedCodeOutput').val('');},
// undocumented experimental modes:
pyCrazyMode: ($('#pythonVersionSelector').val() == '2crazy'),
holisticMode: ($('#cumulativeModeSelector').val() == 'holistic')
};
return ret;
}
function executeCode(forceStartingInstr, forceRawInputLst) {
if (forceRawInputLst !== undefined) {
rawInputLst = forceRawInputLst; // UGLY global across modules, FIXME
}
var backend_script = langToBackendScript($('#pythonVersionSelector').val());
var backendOptionsObj = getBaseBackendOptionsObj();
var startingInstruction = forceStartingInstr ? forceStartingInstr : 0;
var frontendOptionsObj = getBaseFrontendOptionsObj();
frontendOptionsObj.startingInstruction = startingInstruction;
executeCodeAndCreateViz(pyInputGetValue(),
backend_script, backendOptionsObj,
frontendOptionsObj,
'pyOutputPane',
optFinishSuccessfulExecution, handleUncaughtExceptionFunc);
}
// domID is the ID of the element to attach to (without the leading '#' sign)
function SyntaxErrorSurveyBubble(parentViz, domID) {
this.parentViz = parentViz;
this.domID = domID;
this.hashID = '#' + domID;
this.my = 'left center';
this.at = 'right center';
this.qtipHidden = false; // is there a qtip object present but hidden? (TODO: kinda confusing)
}
SyntaxErrorSurveyBubble.prototype.destroyQTip = function() {
$(this.hashID).qtip('destroy');
}
SyntaxErrorSurveyBubble.prototype.redrawCodelineBubble = function() {
if (isOutputLineVisibleForBubbles(this.domID)) {
if (this.qtipHidden) {
$(this.hashID).qtip('show');
}
else {
$(this.hashID).qtip('reposition');
}
this.qtipHidden = false;
}
else {
$(this.hashID).qtip('hide');
this.qtipHidden = true;
}
}
SyntaxErrorSurveyBubble.prototype.qTipContentID = function() {
return '#ui-tooltip-' + this.domID + '-content';
}
SyntaxErrorSurveyBubble.prototype.qTipID = function() {
return '#ui-tooltip-' + this.domID;
}
// created on 2015-04-18
function experimentalPopUpSyntaxErrorSurvey() {
if (prevExecutionExceptionObjLst.length > 0) {
// work with the most recent entry
var prevExecutionExceptionObj = prevExecutionExceptionObjLst[prevExecutionExceptionObjLst.length - 1];
var offendingLine = prevExecutionExceptionObj.killerException.line;
if (offendingLine === undefined) {
return; // get out early!
}
// make sure jquery.qtip has been imported
var codelineIDs = [];
$.each(myVisualizer.domRoot.find('#pyCodeOutput .cod'), function(i, e) {
// hacky!
var domID = $(e).attr('id');
var lineRE = new RegExp('cod' + String(offendingLine) + '$'); // $ for end-of-line match
if (lineRE.test(domID)) {
codelineIDs.push($(e).attr('id'));
}
});
// should find only 1 match, or else something is wonky, maybe
// because the code changed so much that the line number in question
// is no longer available
if (codelineIDs.length === 1) {
var codLineId = codelineIDs[0];
var bub = new SyntaxErrorSurveyBubble(myVisualizer, codLineId);
// if pyCodeOutputDiv is narrower than the current line, then
// adjust the x position of the pop-up bubble accordingly to be
// flush with the right of pyCodeOutputDiv
var pcodWidth = myVisualizer.domRoot.find('#pyCodeOutputDiv').width();
var codLineWidth = myVisualizer.domRoot.find('#' + codLineId).parent().width(); // get enclosing 'tr'
var adjustX = 0; // default
// actually nix this for now to keep things simple ...
//if (pcodWidth < codLineWidth) {
// adjustX = pcodWidth - codLineWidth; // should be negative!
//}
// destroy then create a new tip:
bub.destroyQTip();
$(bub.hashID).qtip($.extend({}, qtipShared, {
content: ' ', // can't be empty!
id: bub.domID,
position: {
my: bub.my,
at: bub.at,
adjust: {
x: adjustX,
},
effect: null, // disable all cutesy animations
},
style: {
classes: 'ui-tooltip-pgbootstrap ui-tooltip-pgbootstrap-RED'
}
}));
// need to set both max-width and width() ...
$(bub.qTipID()).css('max-width', '350px').width('350px');
var myUuid = supports_html5_storage() ? localStorage.getItem('opt_uuid') : '';
// Wording of the survey bubble:
/*
var version = 'v1'; // deployed on 2015-04-19, revoked on 2015-04-20
var surveyBubbleHTML = '
\
You just fixed the following error:
\
\
\
\
If you think this message wasn\'t helpful, what would have been the best error message for you here? \
\
\
\
\
'
*/
/*
var version = 'v2'; // deployed on 2015-04-20, revoked on 2015-09-08
var surveyBubbleHTML = '
\
You just fixed the following error:
\
\
\
\
If you think this message wasn\'t helpful, what would have been the best error message for you here? \
\
\
\
Hide all pop-ups\
\
'
*/
var version = 'v3'; // deployed on 2015-09-08
var surveyBubbleHTML = '
\
You just fixed the following error:
\
\
\
\
Please help us improve error messages for future users.\
If you think the above message wasn\'t helpful, what would have been the best message for you here? \
\
\
\
Hide all of these pop-ups\
\
'
$(bub.qTipContentID()).html(surveyBubbleHTML);
// unbind first, then bind a new one
myVisualizer.domRoot.find('#pyCodeOutputDiv')
.unbind('scroll')
.scroll(function() {
bub.redrawCodelineBubble();
});
$(bub.qTipContentID() + ' #syntaxErrSubmitBtn').click(function() {
var res = $(bub.qTipContentID() + ' #syntaxErrTxtInput').val();
var resObj = {appState: getAppState(),
exc: prevExecutionExceptionObj, // note that prevExecutionExceptionObjLst is BLOWN AWAY by now
opt_uuid: myUuid,
reply: res,
type: 'submit',
v: version};
//console.log(resObj);
$.get('syntax_err_survey.py', {arg: JSON.stringify(resObj)}, function(dat) {});
bub.destroyQTip();
});
$(bub.qTipContentID() + ' #syntaxErrCloseBtn').click(function() {
// grab the value anyways in case the learner wrote something decent ...
var res = $(bub.qTipContentID() + ' #syntaxErrTxtInput').val();
var resObj = {appState: getAppState(),
exc: prevExecutionExceptionObj, // note that prevExecutionExceptionObjLst is BLOWN AWAY by now
opt_uuid: myUuid,
reply: res,
type: 'close',
v: version};
//console.log(resObj);
$.get('syntax_err_survey.py', {arg: JSON.stringify(resObj)}, function(dat) {});
bub.destroyQTip();
});
$(bub.qTipContentID() + ' #syntaxErrHideAllLink').click(function() {
// grab the value anyways in case the learner wrote something decent ...
var res = $(bub.qTipContentID() + ' #syntaxErrTxtInput').val();
var resObj = {appState: getAppState(),
exc: prevExecutionExceptionObj, // note that prevExecutionExceptionObjLst is BLOWN AWAY by now
opt_uuid: myUuid,
reply: res,
type: 'killall',
v: version};
activateSyntaxErrorSurvey = false; // global!
//console.log(resObj);
$.get('syntax_err_survey.py', {arg: JSON.stringify(resObj)}, function(dat) {});
bub.destroyQTip();
return false; // otherwise the 'a href' will trigger a page reload, ergh!
});
var bubbleAceEditor = ace.edit('syntaxErrCodeDisplay');
// set the size and value ASAP to get alignment working well ...
bubbleAceEditor.setOptions({minLines: 1, maxLines: 5}); // keep this SMALL
bubbleAceEditor.setValue(prevExecutionExceptionObj.myAppState.code.rtrim() /* kill trailing spaces */,
-1 /* do NOT select after setting text */);
var s = bubbleAceEditor.getSession();
// tab -> 4 spaces
s.setTabSize(4);
s.setUseSoftTabs(true);
// disable extraneous indicators:
s.setFoldStyle('manual'); // no code folding indicators
bubbleAceEditor.setHighlightActiveLine(false);
bubbleAceEditor.setShowPrintMargin(false);
bubbleAceEditor.setBehavioursEnabled(false);
bubbleAceEditor.setFontSize(10);
$('#syntaxErrCodeDisplay').css('width', '320px');
$('#syntaxErrCodeDisplay').css('height', '90px'); // VERY IMPORTANT so that it works on I.E., ugh!
// don't do real-time syntax checks:
// https://github.com/ajaxorg/ace/wiki/Syntax-validation
s.setOption("useWorker", false);
var lang = prevExecutionExceptionObj.myAppState.py;
var mod = 'python';
if (lang === 'java') {
mod = 'java';
} else if (lang === 'js') {
mod = 'javascript';
} else if (lang === 'ts') {
mod = 'typescript';
} else if (lang === 'ruby') {
mod = 'ruby';
}
s.setMode("ace/mode/" + mod);
bubbleAceEditor.setReadOnly(true);
s.setAnnotations([{row: offendingLine - 1 /* zero-indexed */,
type: 'error',
text: prevExecutionExceptionObj.killerException.exception_msg}]);
// scroll down to the line where the error occurred, trying to center it
// by subtracing 3 from it (which should center it, assuming we're
// displaying 5 lines of context)
if ((offendingLine - 3) > 0) {
bubbleAceEditor.scrollToLine(offendingLine - 3);
}
// don't forget htmlspecialchars
$("#syntaxErrMsg").html(htmlspecialchars(prevExecutionExceptionObj.killerException.exception_msg));
bub.redrawCodelineBubble(); // do an initial redraw to align everything
//globalBub = bub; // for debugging
// log an event whenever this bubble is show (i.e., an 'impression')
// NB: it might actually be hidden if it appears on a line that
// isn't initially visible to the user, but whatevers ...
var impressionObj = {appState: getAppState(),
exceptionLst: prevExecutionExceptionObjLst,
opt_uuid: myUuid,
type: 'show',
v: version};
//console.log(impressionObj);
$.get('syntax_err_survey.py', {arg: JSON.stringify(impressionObj)}, function(dat) {});
}
}
}
function initAceAndOptions() {
if ($('#pythonVersionSelector').val() === 'java') {
$("#javaOptionsPane").show();
} else {
$("#javaOptionsPane").hide();
}
setAceMode(); // update syntax highlighting mode
}
var JS_EXAMPLES = {
jsFactExLink: 'js-example-code/fact.js',
jsDatatypesExLink: 'js-example-code/data-types.js',
jsExceptionExLink: 'js-example-code/caught-exception.js',
jsClosureExLink: 'js-example-code/closure1.js',
jsShadowingExLink: 'js-example-code/var-shadowing2.js',
jsConstructorExLink: 'js-example-code/constructor.js',
jsInhExLink: 'js-example-code/inheritance.js',
};
var TS_EXAMPLES = {
tsHelloExLink: 'ts-example-code/hello.ts',
tsGreeterExLink: 'ts-example-code/greeter.ts',
tsGreeterGenericsExLink: 'ts-example-code/greeter-generics.ts',
tsInheritanceExLink: 'ts-example-code/inheritance.ts',
};
var JAVA_EXAMPLES = {
javaVarLink: 'java-example-code/Variables.java',
javaCFLink: 'java-example-code/ControlFlow.java',
javaSqrtLink: 'java-example-code/Sqrt.java',
javaExecLimitLink: 'java-example-code/ExecLimit.java',
javaStringsLink: 'java-example-code/Strings.java',
javaPassByValLink: 'java-example-code/PassByValue.java',
javaRecurLink: 'java-example-code/Recursion.java',
javaSOLink: 'java-example-code/StackOverflow.java',
javaRolexLink: 'java-example-code/Rolex.java',
javaPersonLink: 'java-example-code/Person.java',
javaComplexLink: 'java-example-code/Complex.java',
javaCastingLink: 'java-example-code/Casting.java',
javaLLLink: 'java-example-code/LinkedList.java',
javaStackQueueLink: 'java-example-code/StackQueue.java',
javaPostfixLink: 'java-example-code/Postfix.java',
javaSTLink: 'java-example-code/SymbolTable.java',
javaToStringLink: 'java-example-code/ToString.java',
javaReflectLink: 'java-example-code/Reflect.java',
javaExceptionLink: 'java-example-code/Exception.java',
javaExceptionFlowLink: 'java-example-code/ExceptionFlow.java',
javaTwoClassesLink: 'java-example-code/TwoClasses.java',
javaForestLink: 'java-example-code/Forest.java',
javaKnapsackLink: 'java-example-code/Knapsack.java',
javaStaticInitLink: 'java-example-code/StaticInitializer.java',
javaSyntheticLink: 'java-example-code/Synthetic.java',
};
var PY2_EXAMPLES = {
tutorialExampleLink: "example-code/py_tutorial.txt",
strtokExampleLink: "example-code/strtok.txt",
listCompLink: "example-code/list-comp.txt",
compsLink: "example-code/comprehensions.txt",
fibonacciExampleLink: "example-code/fib.txt",
memoFibExampleLink: "example-code/memo_fib.txt",
factExampleLink: "example-code/fact.txt",
filterExampleLink: "example-code/filter.txt",
insSortExampleLink: "example-code/ins_sort.txt",
aliasExampleLink: "example-code/aliasing.txt",
happyExampleLink: "example-code/happy.txt",
newtonExampleLink: "example-code/sqrt.txt",
oopSmallExampleLink: "example-code/oop_small.txt",
mapExampleLink: "example-code/map.txt",
rawInputExampleLink: "example-code/raw_input.txt",
oop1ExampleLink: "example-code/oop_1.txt",
oop2ExampleLink: "example-code/oop_2.txt",
inheritanceExampleLink: "example-code/oop_inherit.txt",
sumExampleLink: "example-code/sum.txt",
pwGcdLink: "example-code/wentworth_gcd.txt",
pwSumListLink: "example-code/wentworth_sumList.txt",
towersOfHanoiLink: "example-code/towers_of_hanoi.txt",
pwTryFinallyLink: "example-code/wentworth_try_finally.txt",
sumCubesLink: "example-code/sum-cubes.txt",
decoratorsLink: "example-code/decorators.txt",
genPrimesLink: "example-code/gen_primes.txt",
genExprLink: "example-code/genexpr.txt",
closure1Link: "example-code/closures/closure1.txt",
closure2Link: "example-code/closures/closure2.txt",
closure3Link: "example-code/closures/closure3.txt",
closure4Link: "example-code/closures/closure4.txt",
closure5Link: "example-code/closures/closure5.txt",
lambdaParamLink: "example-code/closures/lambda-param.txt",
aliasing1Link: "example-code/aliasing/aliasing1.txt",
aliasing2Link: "example-code/aliasing/aliasing2.txt",
aliasing3Link: "example-code/aliasing/aliasing3.txt",
aliasing4Link: "example-code/aliasing/aliasing4.txt",
aliasing5Link: "example-code/aliasing/aliasing5.txt",
aliasing6Link: "example-code/aliasing/aliasing6.txt",
aliasing7Link: "example-code/aliasing/aliasing7.txt",
aliasing8Link: "example-code/aliasing/aliasing8.txt",
ll1Link: "example-code/linked-lists/ll1.txt",
ll2Link: "example-code/linked-lists/ll2.txt",
sumListLink: "example-code/sum-list.txt",
varargsLink: "example-code/varargs.txt",
forElseLink: "example-code/for-else.txt",
metaclassLink: "example-code/metaclass.txt",
}
var PY3_EXAMPLES = {
tortureLink: "example-code/closures/student-torture.txt",
nonlocalLink: "example-code/nonlocal.txt",
}
var RUBY_EXAMPLES = {
rubyBlocksLink: 'ruby-example-code/blocks-basic.rb',
rubyBlocksScopingLink: 'ruby-example-code/blocks-scoping-2.rb',
rubyInheritanceLink: 'ruby-example-code/class-inheritance.rb',
rubyConstantsLink: 'ruby-example-code/constants-4.rb',
rubyContainersLink: 'ruby-example-code/container-data-types.rb',
rubyGlobalsLink: 'ruby-example-code/globals.rb',
rubyLambdaScopingLink: 'ruby-example-code/lambda-scoping-2.rb',
rubyMegagreeterLink: 'ruby-example-code/megagreeter.rb',
rubyProcLink: 'ruby-example-code/proc-basic.rb',
rubyProcScopingLink: 'ruby-example-code/proc-scoping.rb',
rubySymbolsLink: 'ruby-example-code/symbols.rb',
rubyPrivateProtectedLink: 'ruby-example-code/class-private-protected.rb',
rubyInstClassVarsComplexLink: 'ruby-example-code/inst-class-vars-complex.rb',
rubyToplevelLink: 'ruby-example-code/toplevel-inst-class-vars.rb',
rubyBlocksScoping3Link: 'ruby-example-code/blocks-scoping-3.rb',
rubyProcReturnLink: 'ruby-example-code/proc-return.rb',
};
var chatBox = undefined;
function createChatBox() {
assert(!chatBox);
chatBox = $("#chat_div").chatbox({id: "Me",
user: {key : "value"},
title: "Live Chat With Tutor",
width: 250,
offset: 2, // offset from right edge
messageSent: chatMsgSent,
boxClosed: chatBoxClosed,
chatboxToggled: chatBoxToggled,
});
}
function chatMsgSent(id, user, msg) {
$("#chat_div").chatbox("option", "boxManager").addMsg(id, msg);
logEvent({type: 'opt-client-chat', text: msg, sid: loggingSocketIO ? loggingSocketIO.id : undefined});
}
// only called when the user hits the X button to explicitly close the chat box
function chatBoxClosed(id) {
logEvent({type: 'opt-client-chat', text: '[closed chat box]', sid: loggingSocketIO ? loggingSocketIO.id : undefined});
}
// called when the user toggles the chat box open or close
function chatBoxToggled(visible) {
var msg = '[minimized chat box]';
if (visible) {
msg = '[maximized chat box]';
}
logEvent({type: 'opt-client-chat', text: msg, sid: loggingSocketIO ? loggingSocketIO.id : undefined});
}
$(document).ready(function() {
setSurveyHTML();
// for OPT live chat tutoring interface -- DEPRECATED FOR NOW
/*
try {
var source = new EventSource(TogetherJSConfig_hubBase + 'learner-SSE');
source.onmessage = function(e) {
var dat = JSON.parse(e.data);
// nasty globals
helpQueueSize = dat.helpQueueUrls;
tutorAvailable = dat.helpAvailable;
setHelpQueueSizeLabel();
if (tutorAvailable && !TogetherJS.running) {
$("#getTutorBtn").fadeIn(750, redrawConnectors);
}
else {
$("#getTutorBtn").fadeOut(750, redrawConnectors);
}
};
}
catch(err) {
// ugh, SSE doesn't seem to work in Safari
console.warn("Sad ... EventSource not supported :(");
}
$("#getTutorBtn").click(requestTutor);
*/
$("#hideHeaderLink").click(function() {
$("#experimentalHeader").hide();
if (myVisualizer) {
myVisualizer.updateOutput(); // redraw arrows
}
return false; // don't reload da page
});
// canned examples
$(".exampleLink").click(function() {
var myId = $(this).attr('id');
var exFile;
var lang;
if (JS_EXAMPLES[myId] !== undefined) {
exFile = JS_EXAMPLES[myId];
lang = 'js';
} else if (TS_EXAMPLES[myId] !== undefined) {
exFile = TS_EXAMPLES[myId];
lang = 'ts';
} else if (JAVA_EXAMPLES[myId] !== undefined) {
exFile = JAVA_EXAMPLES[myId];
lang = 'java';
} else if (RUBY_EXAMPLES[myId] !== undefined) {
exFile = RUBY_EXAMPLES[myId];
lang = 'ruby';
} else if (PY2_EXAMPLES[myId] !== undefined) {
exFile = PY2_EXAMPLES[myId];
// only switch Python mode to 2 if we're not on '2' or '3'; otherwise
// leave as-is so as not to rock the boat
if ($('#pythonVersionSelector').val() !== '2' &&
$('#pythonVersionSelector').val() !== '3') {
lang = '2';
}
} else {
exFile = PY3_EXAMPLES[myId];
assert(exFile !== undefined);
lang = '3';
}
if (lang) {
$('#pythonVersionSelector').val(lang);
}
$.get(exFile, function(dat) {
pyInputSetValue(dat);
initAceAndOptions();
// very subtle! for TogetherJS to sync #pythonVersionSelector
// properly, we must manually send a sync request event:
if (TogetherJS && TogetherJS.running) {
TogetherJS.send({type: "syncAppState",
myAppState: getAppState(),
codeInputScrollTop: pyInputGetScrollTop(),
pyCodeOutputDivScrollTop: myVisualizer ?
myVisualizer.domRoot.find('#pyCodeOutputDiv').scrollTop() :
undefined});
}
}, 'text' /* data type - set to text or else jQuery tries to EXECUTE the JS example code! */);
return false; // prevent 'a' click from going to an actual link
});
$('#pythonVersionSelector').change(initAceAndOptions);
$('#genEmbedBtn').bind('click', function() {
assert(appMode == 'display' || appMode == 'visualize' /* 'visualize' is deprecated */);
var myArgs = getAppState();
delete myArgs.mode;
myArgs.codeDivWidth = myVisualizer.DEFAULT_EMBEDDED_CODE_DIV_WIDTH;
myArgs.codeDivHeight = myVisualizer.DEFAULT_EMBEDDED_CODE_DIV_HEIGHT;
var embedUrlStr = $.param.fragment(domain + "iframe-embed.html", myArgs, 2 /* clobber all */);
embedUrlStr = embedUrlStr.replace(/\)/g, '%29') // replace ) with %29 so that links embed well in Markdown
var iframeStr = '';
$('#embedCodeOutput').val(iframeStr);
});
genericOptFrontendReady(); // initialize at the end
// deployed on 2015-03-12, taken down on 2015-03-16
//$("#surveyHeader").html('Click here to help our research by collaboratively annotating a piece of Python code to create a tutorial for beginners.');
//$("#surveyHeader").css('font-size', '12pt');
// run this AFTER genericOptFrontendReady so that opt_uuid is already
// set by now
var myUuid = supports_html5_storage() ? localStorage.getItem('opt_uuid') : '';
// deployed on 2015-03-19, added opt_uuid param on 2015-03-20
// taken down on 2015-05-14
//$("#surveyHeader")
// .html('')
// .css('margin-bottom', '10px');
initAceAndOptions(); // do this after genericOptFrontendReady
// connect on-demand in logEvent(), not here
//loggingSocketIO = io('http://104.237.139.253:5000/userlog'); // PRODUCTION_PORT
//loggingSocketIO = io('http://104.237.139.253:5001/userlog'); // DEBUG_PORT
if (loggingSocketIO) {
loggingSocketIO.on('connect', function() {
//console.log('CONNECTED and emitting', logEventQueue.length, 'events');
if (logEventQueue.length > 0) {
// the reconnectAttempts field that denotes how many times you've
// attempted to reconnect (which is also how many times you've
// been kicked off by the logging server for, say, being idle).
// add this as an extra field on the FIRST event
if (reconnectAttempts > 0) {
logEventQueue[0].reconnectAttempts = reconnectAttempts;
}
while (logEventQueue.length > 0) {
loggingSocketIO.emit('opt-client-event', logEventQueue.pop());
}
}
assert(logEventQueue.length === 0);
reconnectAttempts++;
});
loggingSocketIO.on('opt-codeopticon-observer-chat', function(msg) {
if (!chatBox) {
createChatBox();
} else {
$("#chat_div").chatbox("option", "hidden", false);
$("#chat_div").chatbox("showContent");
}
$("#chat_div").chatbox("option", "boxManager").addMsg('Tutor', msg.text);
});
}
$("#createTestsLink").click(function() {
initTestcasesPane('#testCasesPane');
$(this).hide();
return false;
});
});