-
Notifications
You must be signed in to change notification settings - Fork 4
/
popUpProblems2.js
414 lines (374 loc) · 13.5 KB
/
popUpProblems2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
// Global variable popUpProblemTimer is defined in the HTML.
// Global variable HXpopUpOptions might be defined in the HTML; testing below.
$(document).ready(function() {
// Declaring semi-global variables for later use.
var video = $('.video');
var state;
var time;
var problemCounter;
var skipEmAll;
var protectedTime = false;
var problemsBeingShown = 0;
// Convert mm:ss format in popUpProblemTimer to seconds.
for (var i = 0; i < popUpProblemTimer.length; i++) {
for (var key in popUpProblemTimer[i]) {
if (key == 'time') {
popUpProblemTimer[i].time = hmsToTime(popUpProblemTimer[i].time);
}
}
}
// Sort the popUpProblemTimer by time.
popUpProblemTimer.sort(timeCompare); // Uses a custom function to sort by time.
// Pop-up settings may be defined in HXpopUpOptions.
// Use settings from there, or set defaults.
const popSettings = window.HXpopUpOptions;
const {
popWidth = 800,
popEffect = 'fade',
popEffectLength = 200,
popMyPosition = 'center',
popAtPosition = 'center',
popOfTarget = window
} = popSettings;
// Log play/pause events from the player.
// Also set the play/pause external control properly.
video.on('pause', function() {
Logger.log('harvardx.video_embedded_problems', { video_event: 'pause' });
console.log('pause');
$('#playpauseicon').html('‣');
$('#playpauseword').html('Play');
});
video.on('play', function() {
Logger.log('harvardx.video_embedded_problems', { video_event: 'play' });
console.log('play');
// Also set the play/pause external control properly.
$('#playpauseicon').html('||'); // Need a better-looking pause icon.
$('#playpauseword').html('Pause');
});
// Check to see whether the video is ready before continuing.
var waitForVid = setInterval(function() {
try {
state = video.data('video-player-state'); // Sometimes this fails and that's ok.
if (typeof state.videoPlayer.player.getPlayerState() !== 'undefined') {
console.log('video data loaded');
clearInterval(waitForVid);
var pause = setTimeout(function() {
console.log('done waiting');
setUpData();
setUpControls();
mainLoop();
}, 0);
}
} catch (err) {
console.log('Waiting for video to load');
}
}, 200);
// Checks local storage and gets data from the video.
function setUpData() {
console.log('setting up data');
// Get the video data.
video = $('.video');
state = video.data('video-player-state');
time = state.videoPlayer.currentTime;
// Storing a separate problem counter for each video.
// Create the counter if it doesn't exist.
if (!localStorage[state.id + '-counter']) {
localStorage[state.id + '-counter'] = '0';
localStorage[state.id + '-skip'] = 'false';
// If the counter didn't exist, we're on a new browser.
// Clear the questions from before the current time.
clearOlderPopUps(time);
}
// Which problem are we on? Using + to cast as integer.
problemCounter = +localStorage[state.id + '-counter'];
// Are we currently skipping problems?
skipEmAll = localStorage[state.id + '-skip'] === 'true';
// If so, let's update the button.
if (skipEmAll) {
$('#sunmoon').html('☾');
$('#onoff').html('Problems are Off');
Logger.log('harvardx.video_embedded_problems', {
reload_event: 'turn_problems_off',
time: time
});
console.log('problems are off after reload');
}
// Let's avoid situations where we're faced with a question
// mere seconds after the page loads, shall we?
// Unless we're past the last problem...
if (time < popUpProblemTimer[popUpProblemTimer.length - 1].time) {
if (problemCounter > 0) {
// Go to just after the previous question...
ISaidGoTo(popUpProblemTimer[problemCounter - 1].time + 1);
} else {
// Or all the way back to the beginning.
ISaidGoTo(0);
}
}
}
// Makes the buttons work and sets up event handlers.
function setUpControls() {
console.log('setting up controls');
// If they seek to a specific position, set the problem counter appropriately
// so that earlier problems don't gang up on them.
video.on('seek', function(event, ui) {
clearOlderPopUps(ui);
});
// Let someone go through the problems again if they want.
// Also useful for debugging.
$('#popUpReset').on('click tap', function() {
updateProblemCounter(0);
ISaidGoTo(0);
Logger.log('harvardx.video_embedded_problems', {
control_event: 'reset'
});
console.log('reset counter and time to zero');
// If problems are currently on, turn them off for two seconds after we go back.
// This addresses a bug that appears in Mobile Safari.
// Note that you can't put questions in the first two seconds of the video
// because of this.
if (!skipEmAll) {
skipEmAll = true;
var dontSpamProblemsEarly = setTimeout(function() {
skipEmAll = false;
}, 2000);
}
});
// Go back to one second after the previous problem.
$('#backOneProblem').on('click tap', function() {
if (problemCounter > 1) {
var newTime = popUpProblemTimer[problemCounter - 2].time + 1;
ISaidGoTo(newTime);
Logger.log('harvardx.video_embedded_problems', {
control_event: 'back_one'
});
console.log('going back one problem');
} else {
updateProblemCounter(0);
ISaidGoTo(0);
Logger.log('harvardx.video_embedded_problems', {
control_event: 'back_one_to_start'
});
console.log('going back to beginning');
}
});
// Play or pause the video
$('#popUpPlayPause').on('click tap', function() {
if (state.videoPlayer.isPlaying()) {
state.videoPlayer.pause();
$('#playpauseicon').html('‣');
$('#playpauseword').html('Play');
Logger.log('harvardx.video_embedded_problems', {
control_event: 'play'
});
console.log('play from exterior controls');
} else {
state.videoPlayer.play();
$('#playpauseicon').html('||');
$('#playpauseword').html('Pause');
Logger.log('harvardx.video_embedded_problems', {
control_event: 'pause'
});
console.log('pause from exterior controls');
}
});
// Let someone turn the pop-up questions on and off.
// Give visual indication by changing the button.
$('#problemToggle').on('click tap', function() {
if (skipEmAll) {
skipEmAll = false;
localStorage[state.id + '-skip'] = 'false';
$('#sunmoon').html('☼');
$('#onoff').html('Problems are On');
Logger.log('harvardx.video_embedded_problems', {
control_event: 'turn_problems_on',
time: time
});
console.log('no longer skipping all problems from time ' + time);
} else {
skipEmAll = true;
localStorage[state.id + '-skip'] = 'true';
$('#sunmoon').html('☾');
$('#onoff').html('Problems are Off');
Logger.log('harvardx.video_embedded_problems', {
control_event: 'turn_problems_off',
time: time
});
console.log('skipping all problems from time ' + time);
}
});
}
// Every 500 ms, check to see whether we're going to add a new problem.
function mainLoop() {
var timeChecker = setInterval(function() {
try {
state.videoPlayer.update(); // Forced update of time. Required for Safari.
} catch (err) {
// If this fails, shut down this loop.
// it's probably because we moved to a new tab.
clearInterval(timeChecker);
}
time = state.videoPlayer.currentTime;
if (problemCounter < popUpProblemTimer.length) {
if (time > popUpProblemTimer[problemCounter].time) {
if (!skipEmAll && !protectedTime) {
state.videoPlayer.pause();
popUpProblem(popUpProblemTimer[problemCounter].title, state);
updateProblemCounter(problemCounter + 1);
} else {
// We're still incrementing and tracking even if we skip problems.
updateProblemCounter(problemCounter + 1);
}
}
}
}, 500);
}
// Set all the time-related stuff to a particular time and then seek there.
// Does the work of creating the dialogue.
// It pulls a question from lower down in the page, and puts it back when we're done.
function popUpProblem(title, state) {
// Find the div for the problem based on its title.
var problemDiv = $('h3:contains(' + title + ')').closest('.vert');
var problemID = $('h3:contains(' + title + ')')
.parent()
.attr('id');
var nextDiv, tempDiv;
var dialogDiv = problemDiv;
var includenext = false;
// Sometimes we can't find an h3 to latch onto.
// We put <span style="display:none" class="includer">includenext</span> into an HTML bit before it.
// The dialog then displays the next item, and appends a clone of the HTML before it.
if (problemDiv.find('span.includer').text() == 'includenext') {
nextDiv = problemDiv.next();
includenext = true;
}
if (includenext) {
dialogDiv = nextDiv;
}
Logger.log('harvardx.video_embedded_problems', {
display_problem: title,
problem_id: problemID,
time: time
});
console.log('displaying problem: ' + title + ' ' + problemID);
// Make a modal dialog out of the chosen problem.
dialogDiv.dialog({
modal: true,
dialogClass: 'no-close',
resizable: true,
width: popWidth,
show: {
effect: popEffect,
duration: popEffectLength
},
position: {
my: popMyPosition,
at: popAtPosition,
of: popOfTarget
},
buttons: {
Skip: function() {
if (includenext) {
tempDiv.remove();
} // We added it, we should erase it.
dialogDestroyed('skip_problem');
$(this).dialog('destroy'); // Put the problem back when we're done.
},
Done: function() {
if (includenext) {
tempDiv.remove();
} // We added it, we should erase it.
dialogDestroyed('mark_done');
$(this).dialog('destroy'); // Put the problem back when we're done.
}
},
open: function() {
// If we're including two divs, append a clone of the first one above.
if (includenext) {
tempDiv = problemDiv.clone();
dialogDiv.prepend(tempDiv);
console.log('pushing in front');
}
// Highlight various controls.
$('span.ui-button-text:contains("Done")').addClass('answeredButton');
$('input.check.Check').attr(
'style',
' background: linear-gradient(to top, #9df 0%,#7bd 20%,#adf 100%); background-color:#ACF; text-shadow: none;'
);
problemsBeingShown++;
},
close: function() {
state.videoPlayer.play();
if (includenext) {
tempDiv.remove();
} // We added it, we should erase it.
Logger.log('harvardx.video_embedded_problems', {
unusual_event: 'dialog_closed_unmarked'
});
console.log('dialog closed'); // Should be pretty rare. I took out the 'close' button.
}
});
}
// Log the destruction of the dialog and play the video if there are no more dialogs up.
function dialogDestroyed(message) {
Logger.log('harvardx.video_embedded_problems', { control_event: message });
console.log(message);
$('input.check.Check').removeAttr('style'); // un-blue the check button.
problemsBeingShown--;
if (problemsBeingShown < 1) {
state.videoPlayer.play();
}
}
// This resets the problem counter to match the time.
function clearOlderPopUps(soughtTime) {
Logger.log('harvardx.video_embedded_problems', {
control_event: 'seek_to_' + soughtTime
});
console.log('sought to time ' + soughtTime);
updateProblemCounter(0); // Resetting fresh.
for (var i = 0; i < popUpProblemTimer.length; i++) {
if (soughtTime > popUpProblemTimer[i].time) {
updateProblemCounter(i + 1);
console.log('new problem counter: ' + problemCounter);
} else {
break;
}
}
}
// I blame multiple Javascript timing issues.
function ISaidGoTo(thisTime) {
time = thisTime;
state.videoPlayer.seekTo(thisTime);
console.log('I said go to ' + thisTime);
}
// Keep the counter and the local storage in sync.
function updateProblemCounter(number) {
problemCounter = number;
localStorage[state.id + '-counter'] = number.toString();
console.log('counter set to ' + problemCounter);
}
// Converts hh:mm:ss to a number of seconds for time-based problems.
// If it's passed a number, it just spits that back out as seconds.
function hmsToTime(hms) {
hms = hms.toString();
var hmsArray = hms.split(':');
var time = 0;
if (hmsArray.length == 3) {
time =
3600 * parseInt(hmsArray[0]) +
60 * parseInt(hmsArray[1]) +
Number(hmsArray[2]);
} else if (hmsArray.length == 2) {
time = 60 * parseInt(hmsArray[0]) + Number(hmsArray[1]);
} else if (hmsArray.length == 1) {
time = Number(hmsArray[0]);
}
return time;
}
// This is a sorting function for my timer.
function timeCompare(a, b) {
if (a.time < b.time) return -1;
if (a.time > b.time) return 1;
return 0;
}
});