Skip to content

Commit 5d3461c

Browse files
committed
severely simplified the edit -> display state transitions
1 parent 6b142df commit 5d3461c

7 files changed

Lines changed: 166 additions & 216 deletions

File tree

v3/css/pytutor.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ div.ExecutionVisualizer div#editCodeLinkDiv {
149149
margin-bottom: 4px;
150150
*/
151151
margin: 8px auto;
152+
font-size: 11pt;
152153
}
153154

154155
div.ExecutionVisualizer div#annotateLinkDiv {

v3/js/composingprograms.js

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3838

3939

4040
$(document).ready(function() {
41-
genericOptFrontendReady();
4241
setSurveyHTML();
4342

4443
$("#embedLinkDiv").hide();
@@ -55,12 +54,10 @@ $(document).ready(function() {
5554
pyInputCodeMirror.setSize(null, '420px');
5655

5756

58-
5957
// be friendly to the browser's forward and back buttons
6058
// thanks to http://benalman.com/projects/jquery-bbq-plugin/
6159
$(window).bind("hashchange", function(e) {
62-
appMode = $.bbq.getState('mode'); // assign this to the GLOBAL appMode
63-
updateAppDisplay();
60+
updateAppDisplay($.bbq.getState('mode'));
6461
});
6562

6663

@@ -133,49 +130,5 @@ $(document).ready(function() {
133130
$("#executeBtn").attr('disabled', false);
134131
$("#executeBtn").click(executeCodeFromScratch);
135132

136-
137-
var queryStrOptions = getQueryStringOptions();
138-
setToggleOptions(queryStrOptions);
139-
140-
if (queryStrOptions.preseededCode) {
141-
setCodeMirrorVal(queryStrOptions.preseededCode);
142-
}
143-
144-
appMode = queryStrOptions.appMode; // assign this to the GLOBAL appMode
145-
if ((appMode == "display") && queryStrOptions.preseededCode /* jump to display only with pre-seeded code */) {
146-
preseededCurInstr = queryStrOptions.preseededCurInstr; // ugly global
147-
$("#executeBtn").trigger('click');
148-
}
149-
else {
150-
if (appMode === undefined) {
151-
// default mode is 'edit', don't trigger a "hashchange" event
152-
appMode = 'edit';
153-
}
154-
else {
155-
// fail-soft by killing all passed-in hashes and triggering a "hashchange"
156-
// event, which will then go to 'edit' mode
157-
$.bbq.removeState();
158-
}
159-
}
160-
161-
// redraw connector arrows on window resize
162-
$(window).resize(function() {
163-
if (appMode == 'display') {
164-
myVisualizer.redrawConnectors();
165-
}
166-
});
167-
168-
$('#genUrlBtn').bind('click', function() {
169-
var myArgs = {code: pyInputCodeMirror.getValue(),
170-
mode: appMode,
171-
cumulative: $('#cumulativeModeSelector').val(),
172-
py: $('#pythonVersionSelector').val()};
173-
174-
if (appMode == 'display') {
175-
myArgs.curInstr = myVisualizer.curInstr;
176-
}
177-
178-
var urlStr = $.param.fragment(window.location.href, myArgs, 2 /* clobber all */);
179-
$('#urlOutput').val(urlStr);
180-
});
133+
genericOptFrontendReady(); // initialize at the end
181134
});

v3/js/csc108h.js

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3939

4040

4141
$(document).ready(function() {
42-
genericOptFrontendReady();
4342
setSurveyHTML();
4443

4544
$("#embedLinkDiv").hide();
@@ -56,12 +55,10 @@ $(document).ready(function() {
5655
pyInputCodeMirror.setSize(null, '420px');
5756

5857

59-
6058
// be friendly to the browser's forward and back buttons
6159
// thanks to http://benalman.com/projects/jquery-bbq-plugin/
6260
$(window).bind("hashchange", function(e) {
63-
appMode = $.bbq.getState('mode'); // assign this to the GLOBAL appMode
64-
updateAppDisplay();
61+
updateAppDisplay($.bbq.getState('mode'));
6562
});
6663

6764

@@ -136,47 +133,5 @@ $(document).ready(function() {
136133
$("#executeBtn").attr('disabled', false);
137134
$("#executeBtn").click(executeCodeFromScratch);
138135

139-
140-
var queryStrOptions = getQueryStringOptions();
141-
if (queryStrOptions.preseededCode) {
142-
setCodeMirrorVal(queryStrOptions.preseededCode);
143-
}
144-
145-
appMode = queryStrOptions.appMode; // assign this to the GLOBAL appMode
146-
if ((appMode == "display") && queryStrOptions.preseededCode /* jump to display only with pre-seeded code */) {
147-
preseededCurInstr = queryStrOptions.preseededCurInstr; // ugly global
148-
$("#executeBtn").trigger('click');
149-
}
150-
else {
151-
if (appMode === undefined) {
152-
// default mode is 'edit', don't trigger a "hashchange" event
153-
appMode = 'edit';
154-
}
155-
else {
156-
// fail-soft by killing all passed-in hashes and triggering a "hashchange"
157-
// event, which will then go to 'edit' mode
158-
$.bbq.removeState();
159-
}
160-
}
161-
162-
// redraw connector arrows on window resize
163-
$(window).resize(function() {
164-
if (appMode == 'display') {
165-
myVisualizer.redrawConnectors();
166-
}
167-
});
168-
169-
$('#genUrlBtn').bind('click', function() {
170-
var myArgs = {code: pyInputCodeMirror.getValue(),
171-
mode: appMode,
172-
cumulative: $('#cumulativeModeSelector').val(),
173-
py: $('#pythonVersionSelector').val()};
174-
175-
if (appMode == 'display') {
176-
myArgs.curInstr = myVisualizer.curInstr;
177-
}
178-
179-
var urlStr = $.param.fragment(window.location.href, myArgs, 2 /* clobber all */);
180-
$('#urlOutput').val(urlStr);
181-
});
136+
genericOptFrontendReady(); // initialize at the end
182137
});

v3/js/opt-frontend-common.js

Lines changed: 121 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ var domain = "http://pythontutor.com/"; // for deployment
6161
var isExecutingCode = false; // nasty, nasty global
6262

6363

64-
var appMode = 'edit'; // 'edit', 'display', or 'display_no_frills' also support
64+
var appMode = 'edit'; // 'edit', 'display', 'display_no_frills'. also support
6565
// 'visualize' for backward compatibility (same as 'display')
6666

6767
var preseededCurInstr = null; // if you passed in a 'curInstr=<number>' in the URL, then set this var
@@ -91,11 +91,49 @@ function guid() {
9191

9292
var myVisualizer = null; // singleton ExecutionVisualizer instance
9393

94-
9594
var rawInputLst = []; // a list of strings inputted by the user in response to raw_input or mouse_input events
9695

96+
97+
function redrawConnectors() {
98+
if (appMode == 'display' || appMode == 'visualize' /* deprecated */) {
99+
if (myVisualizer) {
100+
myVisualizer.redrawConnectors();
101+
}
102+
}
103+
}
104+
105+
97106
function genericOptFrontendReady() {
98-
// log a generic AJAX error handler
107+
var queryStrOptions = getQueryStringOptions();
108+
setToggleOptions(queryStrOptions);
109+
110+
if (queryStrOptions.preseededCode) {
111+
setCodeMirrorVal(queryStrOptions.preseededCode);
112+
}
113+
114+
// ugh tricky -- always start in edit mode by default, and then
115+
// simulate a click to get it to switch to display mode ONLY after the
116+
// code successfully executes
117+
appMode = 'edit';
118+
if ((queryStrOptions.appMode == 'display' ||
119+
queryStrOptions.appMode == 'visualize' /* 'visualize' is deprecated */) &&
120+
queryStrOptions.preseededCode /* jump to display only with pre-seeded code */) {
121+
console.log('seeded in display mode');
122+
preseededCurInstr = queryStrOptions.preseededCurInstr; // ugly global
123+
$("#executeBtn").trigger('click');
124+
}
125+
$.bbq.removeState(); // clean up the URL no matter what
126+
127+
$(window).resize(redrawConnectors);
128+
129+
$('#genUrlBtn').bind('click', function() {
130+
var myArgs = getAppState();
131+
var urlStr = $.param.fragment(window.location.href, myArgs, 2 /* clobber all */);
132+
$('#urlOutput').val(urlStr);
133+
});
134+
135+
136+
// register a generic AJAX error handler
99137
$(document).ajaxError(function(evt, jqxhr, settings, exception) {
100138
// ignore errors related to togetherjs stuff:
101139
if (settings.url.indexOf('togetherjs') > -1) {
@@ -150,15 +188,33 @@ function setToggleOptions(dat) {
150188

151189
// get the ENTIRE current state of the app
152190
function getAppState() {
153-
return {code: pyInputCodeMirror.getValue(),
154-
mode: appMode,
155-
cumulative: $('#cumulativeModeSelector').val(),
156-
heapPrimitives: $('#heapPrimitivesSelector').val(),
157-
drawParentPointers: $('#drawParentPointerSelector').val(),
158-
textReferences: $('#textualMemoryLabelsSelector').val(),
159-
showOnlyOutputs: $('#showOnlyOutputsSelector').val(),
160-
py: $('#pythonVersionSelector').val(),
161-
curInstr: myVisualizer ? myVisualizer.curInstr : undefined};
191+
var ret = {code: pyInputCodeMirror.getValue(),
192+
mode: appMode,
193+
cumulative: $('#cumulativeModeSelector').val(),
194+
heapPrimitives: $('#heapPrimitivesSelector').val(),
195+
drawParentPointers: $('#drawParentPointerSelector').val(),
196+
textReferences: $('#textualMemoryLabelsSelector').val(),
197+
showOnlyOutputs: $('#showOnlyOutputsSelector').val(),
198+
py: $('#pythonVersionSelector').val(),
199+
curInstr: myVisualizer ? myVisualizer.curInstr : undefined};
200+
201+
// keep this really clean by avoiding undefined values
202+
if (ret.cumulative === undefined)
203+
delete ret.cumulative;
204+
if (ret.heapPrimitives === undefined)
205+
delete ret.heapPrimitives;
206+
if (ret.drawParentPointers === undefined)
207+
delete ret.drawParentPointers;
208+
if (ret.textReferences === undefined)
209+
delete ret.textReferences;
210+
if (ret.showOnlyOutputs === undefined)
211+
delete ret.showOnlyOutputs;
212+
if (ret.py === undefined)
213+
delete ret.py;
214+
if (ret.curInstr === undefined)
215+
delete ret.curInstr;
216+
217+
return ret;
162218
}
163219

164220
// return whether two states match, except don't worry about curInstr
@@ -186,10 +242,20 @@ function setVisibleAppState(appState) {
186242
}
187243

188244

189-
// update the app display based on current state of the appMode global
190-
// TODO: refactor all frontend clients to call this unified function
191-
function updateAppDisplay() {
192-
if (appMode === undefined || appMode == 'edit') {
245+
// sets the global appMode variable if relevant and also the URL hash to
246+
// support some amount of Web browser back button goodness
247+
function updateAppDisplay(newAppMode) {
248+
// idempotence is VERY important here
249+
if (newAppMode == appMode) {
250+
return;
251+
}
252+
253+
appMode = newAppMode; // global!
254+
255+
if (appMode === undefined || appMode == 'edit' ||
256+
!myVisualizer /* subtle -- if no visualizer, default to edit mode */) {
257+
appMode = 'edit'; // canonicalize
258+
193259
$("#pyInputPane").show();
194260
$("#pyOutputPane,#surveyHeader").hide();
195261
$("#embedLinkDiv").hide();
@@ -208,39 +274,48 @@ function updateAppDisplay() {
208274
$("#pyOutputPane").empty();
209275
myVisualizer = null;
210276
$(document).unbind('keydown'); // also kill kb bindings to avoid staleness
277+
278+
$(document).scrollTop(0); // scroll to top to make UX better on small monitors
279+
280+
$.bbq.pushState({ mode: 'edit' }, 2 /* completely override other hash strings to keep URL clean */);
211281
}
212282
else if (appMode == 'display' || appMode == 'visualize' /* 'visualize' is deprecated */) {
213-
if (!myVisualizer) {
214-
enterEditMode(); // if there's no visualizer, switch back to edit mode
215-
}
216-
else {
217-
$("#pyInputPane").hide();
218-
$("#pyOutputPane,#surveyHeader").show();
219-
$("#embedLinkDiv").show();
220-
221-
doneExecutingCode();
222-
223-
// do this AFTER making #pyOutputPane visible, or else
224-
// jsPlumb connectors won't render properly
225-
myVisualizer.updateOutput();
226-
227-
// customize edit button click functionality AFTER rendering (NB: awkward!)
228-
$('#pyOutputPane #editCodeLinkDiv').show();
229-
$('#pyOutputPane #editBtn').click(function() {
230-
enterEditMode();
231-
});
232-
}
233-
}
234-
else if (appMode == 'display_no_frills') {
283+
assert(myVisualizer);
284+
appMode = 'display'; // canonicalize
285+
235286
$("#pyInputPane").hide();
236287
$("#pyOutputPane,#surveyHeader").show();
237288
$("#embedLinkDiv").show();
289+
290+
doneExecutingCode();
291+
292+
// do this AFTER making #pyOutputPane visible, or else
293+
// jsPlumb connectors won't render properly
294+
myVisualizer.updateOutput();
295+
296+
// customize edit button click functionality AFTER rendering (NB: awkward!)
297+
$('#pyOutputPane #editCodeLinkDiv').show();
298+
$('#pyOutputPane #editBtn').click(function() {
299+
enterEditMode();
300+
});
301+
302+
$(document).scrollTop(0); // scroll to top to make UX better on small monitors
303+
304+
$.bbq.pushState({ mode: 'display' }, 2 /* completely override other hash strings to keep URL clean */);
238305
}
239306
else {
240-
assert(false);
307+
assert(appMode == 'display_no_frills');
308+
assert(myVisualizer);
309+
310+
$("#pyInputPane").hide();
311+
$("#pyOutputPane,#surveyHeader").show();
312+
$("#embedLinkDiv").show();
313+
$.bbq.pushState({ mode: 'display_no_frills' }, 2 /* completely override other hash strings to keep URL clean */);
241314
}
242315

243316
$('#urlOutput,#embedCodeOutput').val(''); // clear to avoid stale values
317+
318+
console.log('END updateAppDisplay', appMode);
244319
}
245320

246321

@@ -270,18 +345,20 @@ function doneExecutingCode() {
270345
isExecutingCode = false; // nasty global
271346
}
272347

348+
273349
function enterDisplayMode() {
274-
$(document).scrollTop(0); // scroll to top to make UX better on small monitors
275-
$.bbq.pushState({ mode: 'display' }, 2 /* completely override other hash strings to keep URL clean */);
350+
console.log('enterDisplayMode');
351+
updateAppDisplay('display');
276352
}
277353

278354
function enterEditMode() {
279-
$(document).scrollTop(0); // scroll to top to make UX better on small monitors
280-
$.bbq.pushState({ mode: 'edit' }, 2 /* completely override other hash strings to keep URL clean */);
355+
console.log('enterEditMode');
356+
updateAppDisplay('edit');
281357
}
282358

283359
function enterDisplayNoFrillsMode() {
284-
$.bbq.pushState({ mode: 'display_no_frills' }, 2 /* completely override other hash strings to keep URL clean */);
360+
console.log('enterDisplayNoFrillsMode');
361+
updateAppDisplay('display_no_frills');
285362
}
286363

287364

0 commit comments

Comments
 (0)