-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstoryScripts.js
548 lines (482 loc) · 23 KB
/
storyScripts.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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
//Scripts for MoToLi story pages
//Page global variables
var pgOn; //The number of the page to be displayed
var pgMax; //The total number of pages in the story
//Progress timing global variables
var timerInt = 0; //handle for interval timer updating a progress bar
var timerOut = 0; //handle of timeout timer if running
var timerIDs; //An array of all timeout timer IDs set when a progress bar is run
var pInterval; //length of a pause. Its value is extracted from the story container div in the HTML file
var initStatus = false; //Set to true if initialisation has already occurred. Needed to overcome multiple onload events in the XOs
var newPlay = false; //Flags when a play button has been clicked. When true, all updates on a progress bar will be ignored
var Key = {
PAGEUP: 33,
PAGEDOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
};
function initialise() {
onloadCount = parseInt(document.getElementById("onloads").innerHTML) + 1;
document.getElementById("onloads").innerHTML = onloadCount;
//Only process if this is the first call. Subsequent calls are ignored until the file is reloaded.
if (initStatus === false) {
initStatus = true;
//Set the page related global variables
pgOn = 0;
pgMax = document.getElementsByClassName("page").length;
//Get the story name for the footers
for (i = 0; i < document.getElementsByClassName("title").length; i++) {
document.getElementsByClassName("title")[i].innerHTML = document.getElementsByTagName("title")[0].innerHTML;
}
//Modify styles specified in the HTML data.
document.getElementsByTagName("body")[0].style.backgroundColor = document.getElementById("backColor").innerHTML;
var diagnosticState = document.getElementById("diagnostic").innerHTML;
document.getElementById("platform").innerHTML = navigator.platform;
var pauseNodes = document.getElementsByClassName("pause");
if (diagnosticState === "on"){
document.getElementsByClassName("data")[0].style.fontSize = "1em";
for (i = 0; i < pauseNodes.length; i++) {
pauseNodes[i].style.fontSize = "0.5em";
pauseNodes[i].style.visibility = "visible";
}
} else {
document.getElementsByClassName("data")[0].style.fontSize = "0.1em";
for (i = 0; i < pauseNodes.length; i++) {
pauseNodes[i].style.fontSize = "0.1em";
pauseNodes[i].style.visibility = "hidden";
}
}
addNavKeyEvt(); //To listen for arrow, Page up/down, and Home/End key events
setFormat(); //Sets the story format - horizontal or vertical
limitPictureSize(); //Width for 'sidepic' format, height for 'underpic' format
timerIDs = [];
showLanguages(); //Hide all text lines for any language not required
showPage(pgOn, "load");
autoPlay(pgOn); //Play the audio automatically for page 0 on first load
resetEndChangeButtons(); //At the extremes - start and end of the story
//Display the fully styled first page
if (document.getElementById("loadmsg")) document.getElementById("loadmsg").style.display = "none";
document.getElementById("story").style.visibility = "visible";
}
}
// LAYOUT & FORMATTING FUNCTIONS
function setFormat() { //Establishes text-beside-pic or text-under-pic format, according the story container div class
// Class "underpic" represents text under main picture, class "sidepic" means text beside picture
if (document.getElementById("story").className === "under") {
for (i = 0; i < document.getElementsByClassName("underpic").length; i++){
document.getElementsByClassName("underpic")[i].style.display = "block";
}
for (i = 0; i < document.getElementsByClassName("sidepic").length; i++){
document.getElementsByClassName("sidepic")[i].style.display = "none";
}
} else {
for (i = 0; i < document.getElementsByClassName("underpic").length; i++){
document.getElementsByClassName("underpic")[i].style.display = "none";
}
for (i = 0; i < document.getElementsByClassName("sidepic").length; i++){
document.getElementsByClassName("sidepic")[i].style.display = "block";
}
}
}
function showLanguages() {
var i, j;
var visLangs = [];
var hashIndex = window.location.href.indexOf('#');
var textLineNodes;
var textRowNodes;
var visLangClass;
if (hashIndex > 0) {
visLangs = window.location.href.slice(hashIndex + 1).split(',');
}
if (!visLangs[0]) return;
textLineNodes = document.getElementsByClassName("textLine");
for (i = 0; i < textLineNodes.length; i++) {
textLineNodes[i].parentNode.style.display = "none";
}
for (i = 0; i < visLangs.length; i++){
visLangClass = visLangs[i]
textRowNodes = document.getElementsByClassName(visLangClass);
for (j = 0; j < textRowNodes.length; j++){
textRowNodes[j].style.display = ""; //Don't set to block, so Chrome resets properly.
}
}
}
function limitPictureSize() {
//Limits the width or height of the main picture, depending on the story format.
//The limits are a percentage of screen size, defined in the HTML file.
//This function is called at initialisation, and also at any subsequent change in window size.
if (document.getElementById("story").className === "side") { //Limit the 'side' picture size
var w = window.innerWidth;
var scale = parseInt(document.getElementById("pScaleSide").innerHTML);
var picSpace = parseInt(w * scale / 100);
var sidePicNodes = document.getElementsByClassName("sidepic");
for (i = 0; i < sidePicNodes.length; i++) {
var picNodes = sidePicNodes[i].getElementsByClassName("mainpic");
for (j = 0; j < picNodes.length; j++) {
picNodes[j].width = picSpace;
}
}
} else if (document.getElementById("story").className === "under") { //Limit the 'under' picture size
var h = window.innerHeight;
var scale = parseInt(document.getElementById("pScaleTop").innerHTML);
var picSpace = parseInt(h * scale / 100);
var topPicNodes = document.getElementsByClassName("underpic");
for (i = 0; i < topPicNodes.length; i++) {
var picNodes = topPicNodes[i].getElementsByClassName("mainpic");
for (j = 0; j < picNodes.length; j++) {
picNodes[j].height = picSpace;
}
}
}
}
function resetEndChangeButtons() {
//There are 2 of each back and fwd buttons on every page -
//one set for 'text beside' format, and one set for 'text under' format
//reset the back button icons on page 0 to Home
var startPageNode = document.getElementById("p0");
for (i = 0; i < startPageNode.getElementsByClassName("btnback").length; i++) {
startPageNode.getElementsByClassName("btnback")[i].src = "../../common/home.png";
}
//reset the forward button icons on the last page to Home
var endpID = "p" + (pgMax - 1);
var endPageNode = document.getElementById(endpID);
for (i = 0; i < endPageNode.getElementsByClassName("btnfwd").length; i++) {
endPageNode.getElementsByClassName("btnfwd")[i].src = "../../common/home.png";
}
}
function newStory() {
stopAllTimers();
window.history.back();
}
function hideAllPages(){
var pageID;
for (i = 0; i < pgMax; i++) {
pageID = "p" + i;
document.getElementById(pageID).style.display = "none";
}
}
function showPage(i, evnt) {
//Function does the basics of making the page visible, and inserting the correct page number.
//However, its main purpose is to set the lengths (widths) of any progress bars on the page
//to align exactly with the lengths of the corresponding text lines.
var pagenumber;
var pageID;
var textNode; //the <span> element containing a line of text
var textLength; //the length of the text in the textNode, in px
var progNode; //the <progress> element corresponding to the textNode
var lineNodes; //the set of <td> nodes on the page containing a single line of text
var wrapNodes; //the set of <td> nodes on the page containing multiple (wrapped) lines of text
var wrapLines; //the number of text lines in each single wrapNode
var player = document.getElementById("AudioPlayer");
player.pause(); //In case it's running
pageID = "p" + i;
pagenumber = " ";
if (i > 0) {pagenumber = " Page " + i}; //i.e. don't display it on the title page
for (j = 0; j < document.getElementsByClassName("pgnum").length; j++) {
document.getElementsByClassName("pgnum")[j].innerHTML = pagenumber;
}
hideAllPages() //and then display the desired one
document.getElementById(pageID).style.display = "block"; //enable the page to be displayed
//Now set the progress bar lengths for each text line of the page
lineNodes = document.getElementById(pageID).getElementsByClassName("textLine");
//This will also set the first line of a wrapped set, but it will be overwritten in the following for loop.
for (j = 0; j < lineNodes.length; j++) {
textNode = lineNodes[j].getElementsByClassName("line")[0]; //This is the text whose length we want
textLength = textNode.offsetWidth;
progNode = lineNodes[j].getElementsByTagName("progress")[0]; //This is the progress element to be set
progNode.style.width = (textLength + "px");
progNode.value = 0;
}
//Set the progress bar lengths for each line of any wrapped sets of lines
wrapNodes = document.getElementById(pageID).getElementsByClassName("wrap");
for (j = 0; j < wrapNodes.length; j++) {
//get number of lines for each node
wrapLines = wrapNodes[j].getElementsByClassName("line").length;
for (k = 0; k < wrapLines; k++) {
textNode = wrapNodes[j].getElementsByClassName("line")[k];
textLength = textNode.offsetWidth;
progNode = wrapNodes[j].getElementsByTagName("progress")[k];
progNode.style.width = (textLength + "px");
progNode.value = 0;
}
}
//At this point all progress bars for the page have been set to the correct length and initialised to zero.
//This is true for the XOs, although they do not display the new status, nor respond when activated.
//The following action seems to be necessary to kick-start them back to life!
document.getElementById(pageID).style.display = "block"; //enable the page to be displayed
if (evnt === "load") {
//Use brute force (page fwd and back) to force page 0 buttons to position correctly
document.getElementById(pageID).getElementsByClassName("btnfwd")[0].click();
document.getElementById(pageID).getElementsByClassName("btnback")[0].click();
}
}
function autoPlay(i) {
var platform = navigator.platform;
if (platform === "iPad") {return; //function doesn't work on iPad/Android mobile
} else if (platform === "Linux armv7l") {return;
} else {
// Finds the first visible text line on the page, and clicks its play button
var pageID = "p" + i;
var textLineNodes = document.getElementById(pageID).getElementsByClassName("textLine");
var rowNode; //the parent <tr> node of the text line
for (i = 0; i < textLineNodes.length; i++) {
rowNode = textLineNodes[i].parentNode;
if (rowNode.style.display != "none") {
rowNode.getElementsByClassName("button")[0].childNodes[0].click();
return;
}
}
}
}
//PAGE NAVIGATION FUNCTIONS
function pageFwd() {
stopAllTimers();
pgOn += 1;
if (pgOn > pgMax - 1){
newStory();
} else {
showPage(pgOn, "change");
}
}
function pageBack() {
stopAllTimers();
if (pgOn > 0){
pgOn += - 1;
showPage(pgOn, "change");
} else {
newStory();
}
}
//Add a listener for keyboard events and define the keys to act on
// IE: attachEvent, Firefox & Chrome: addEventListener
function _addEventListener(evt, element, fn) {
if (window.addEventListener) {element.addEventListener(evt, fn, false);}
else {element.attachEvent('on'+evt, fn);}
}
function onInputKeydown(evt) {
if (!evt) {evt = window.event;} // for IE compatible
var keycode = evt.keyCode || evt.which; // also for cross-browser compatible
if (keycode == Key.LEFT) {pageBack();}
else if (keycode == Key.RIGHT) {pageFwd();}
else if (keycode == Key.UP) {pageBack();}
else if (keycode == Key.DOWN) {pageFwd();}
else if (keycode == Key.PAGEUP) {pageBack();}
else if (keycode == Key.PAGEDOWN) {pageFwd();}
else if (keycode == Key.HOME) {pgOn = 0; showPage(pgOn, "change");}
else if (keycode == Key.END) {pgOn = pgMax - 1; showPage(pgOn, "change");}
else {//do nothing
}
}
function addNavKeyEvt() {
_addEventListener('keydown', document, onInputKeydown);
}
//FUNCTIONS TO INITIALISE PROGRESS BARS WHEN 'PLAY' BUTTONS ARE CLICKED
function playAudio(file, time, pageNo, lineNo) {
var player; //The node for the audio player
var line; //index No. of the text cell matching the audio file
var audioTime;
var textNode; //The text node (single or multi line) matching the audio track
var progNodes; //the set of <progress> nodes on a page
var wrapLineCount; //the number of lines of text in a wrapped set (i.e. a paragraph)
var paragraphLength; //the length in px of all the text in a paragraph
var partLength; //the length in px of a line in a paragraph
var progNode; //the specific <progress> element corresponding to a single line in a paragraph
var lineTime; //Max time for an individual progress bar (in a wrapped lines paragraph)
var lineNode; //the specific <span> element enclosing the full text to match progNode and lineTime
var linePauseList; //Array of pause times for a single line (i.e. a single progress bar)
var wrapPauseList; //An array of linePauseLists - only one entry if the text is a
//single line, multiple entries for wrapped lines.
var pauseCount; //Number of pauses in a text line.
var pauseInterval = parseInt(document.getElementById("barPause").innerHTML);
var pauseTimeSum; //product of count and interval
newPlay = true; //This prevents the incProgBar() function from updating anything until newPlay is reset to false
player = document.getElementById("AudioPlayer");
player.play();
// player.src = file;
line = lineNo - 1;
audioTime = (time * 1000); //turn it to msec
stopAllTimers();
// Clear the interval timer after a delay long enough to ensure all the timeout timers have been cleared,
// but not too long or it will interfere with the start of the new line. Keep well short of the standard
// delay at the start of a line. 10 msec seems to quite OK.
var timer = setTimeout(function() {clearInterval(timerInt);}, 10);
//zero all the progress bars on the page
progNodes = document.getElementById(pageNo).getElementsByTagName("progress");
for (i = 0; i < progNodes.length; i++) {
progNodes[i].value = 0;
}
//Clear any pause time data
linePauseList = [];
wrapPauseList = [];
textNode = document.getElementById(pageNo).getElementsByClassName("textLine")[line];
//If it's a set of wrapped lines, we need to set the max times for each progress bar. The full audio time is known,
//so each line's proportion of this is simply the ratio of its width to the total widths of all the lines.
if (textNode.className === "textLine wrap") {
//Add the widths of all the lines, i.e. get the paragraph length
wrapLineCount = textNode.getElementsByTagName("progress").length;
paragraphLength = 0;
for (i = 0; i < wrapLineCount; i++){
partLength = parseInt(textNode.getElementsByTagName("progress")[i].style.width);
paragraphLength += partLength;
}
// Set the proportional times (max value for the progress bars) for each line of text
for (j = 0; j < wrapLineCount; j++){
progNode = textNode.getElementsByTagName("progress")[j]
lineTime = parseInt(parseInt(progNode.style.width) / paragraphLength * audioTime);
progNode.max = lineTime; //Sets the maximum value (in msec) for the line
//Find No. of pauses in line, and reduce progress bar maximum value accordingly
lineNode = textNode.getElementsByClassName("line")[j];
pauseCount = lineNode.getElementsByClassName("pause").length;
pauseTimeSum = pauseCount*pauseInterval;
progNode.max -= pauseTimeSum;
//Build a list of data about the line, mainly its nodes and pause times
linePauseList = getPauseList(progNode, lineNode, j);
//Wrap the line data into a list of line data sets.
wrapPauseList.push(linePauseList);
}
} else {
//No wrapped lines so just set the single progress bar node
progNode = textNode.getElementsByTagName("progress")[0];
progNode.max = audioTime; //Sets the maximum value (in msec) for the line
//Find No. of pauses in line, and reduce progress bar maximum value accordingly
lineNode = textNode.getElementsByClassName("line")[0];
pauseCount = lineNode.getElementsByClassName("pause").length;
pauseTimeSum = pauseCount*pauseInterval;
progNode.max -= pauseTimeSum;
//Build a list of data about the line, mainly its nodes and pause times
linePauseList = getPauseList(progNode, lineNode, 0);
//Wrap the line data into a list of line data sets - although in this case it will be the only entry
wrapPauseList.push(linePauseList);
}
//Start the audio file and the progress bar, once the file is loaded
player.src = file;
player.oncanplaythrough = startAudioBar(player, wrapPauseList);
}
function startAudioBar(player, wrapPauseList) {
//wait 1 sec before starting everything - allows additional time for the audio file to load
var timer = setTimeout(function() {player.play(); runProgBar(wrapPauseList);}, 1000);
}
function getPauseList(progNode, lineNode, index) {
//Function is called by playAudio() to build an array of objects for a line of text, in order:
//line index (starts at 0 for a wrapped set), progress bar node, list of times when pauses will be triggered.
//This provides all the info needed to run a particular progress bar.
//The calling function [playAudio()] assembles all line arrays for a single audio file into a wrap
//array - even if it's only a single line.
var lineLength;
var pNodes; //the set of <span> elements defining pauses in the line
var pList; //An array that contains the index, progNode, plus any pause times
var pTrigTime; //time before pause is triggered
var numPauses;
var lineTime; //audio time for the progress bar
var prevPauseTime; //Need to track it to add to each successive pause interval
//i.e. all pause times are referenced to the start of the bar
pInterval = parseInt(document.getElementById("barPause").innerHTML);
pList = [];
lineLength = lineNode.offsetWidth;
pNodes = lineNode.getElementsByTagName("span");
lineTime = progNode.max;
pList.push(index); //line index if it's one of a wrapped set
pList.push(progNode); //Second item in the array
prevPauseTime = 0 - pInterval; //to cancel it out when it's added to the first pause in the for loop.
//Now add the pause times to the array
numPauses = pNodes.length - 1; //don't want to count the last one
if (numPauses > 0) {
for (i = 0; i < numPauses; i++) {
pause = pNodes[i].offsetWidth;
//Only want to trigger a pause if the class="pause", but we need to accumulate the time of *all* the <span> elements
pTrigTime = prevPauseTime + parseInt(pause / lineLength * lineTime);
if (pNodes[i].className === "pause") {
pTrigTime = pTrigTime + pInterval; //pInterval allows for a previous pause, but is cancelled for the first because we started with -pInterval
pList.push(pTrigTime);
}
prevPauseTime = pTrigTime;
}
}
return pList;
}
//FUNCTIONS TO CONTROL AND UPDATE THE PROGRESS BARS
function runProgBar(wrapPauseList) {
var time; //time until a pause
var linePauseList = [];
var startDelay = 1000; //applied to first line
var defaultDelay = 200; //applied to lines other than first
var delay;
var lineIndex;
var wrapID; //the ID of the wrapPauseList object requested to be run.
var wrapItems; //the No. of objects in wrapPauseList - wrapID will be the last
var timer; //ID for any timer when it is set
var platform; //time delays when starting the progress bars will depend on the OS platform.
//This function sets the timer to increment a progress bar and all the timeout timers to trigger pauses
//along the bar. All the info for this is contained in the linePauseList array, which itself is an object
//in the wrapPauseList array. If we are running a multi-line set of progress bars, the incProgBar() function
//will detect when a progress bar is full, and recall runProgBar() to set up the new timers for the next
//line (if one exists in the wrapPauseList array).
//Therefore we need to clear all timers before setting up a new line. However, there can be a variable number
//of timeout timers, depending on the pauses in the line. Their IDs are stored in the global timerIDs array
//to enable them all to be cleared in the stopAllTimers() function.
//Stop all timers before continuing
stopAllTimers();
clearInterval(timerInt);
var platform = document.getElementById("platform").innerHTML;
//Different delay times seem to be desirable for different platforms
if (platform.search("inux arm") > 0) {
startDelay = 1500;
defaultDelay = 300;
} else {
startDelay = 1000;
defaultDelay = 200;
}
wrapItems = wrapPauseList.length;
linePauseList = wrapPauseList.shift();
lineIndex = linePauseList.shift();
if (lineIndex == 0) {delay = startDelay} else {delay = defaultDelay};
progNode = linePauseList.shift(); //Only pause times will now remain in this array
progNode.value = 0;
maxTime = progNode.max;
//Start the interval timer after the appropriate delay
//The global newPlay flag *must not* be reset to false until after the setTimeout delay
//clearInterval must always precede setInterval to ensure only one instance of the timer is ever running.
timer = setTimeout(function() {clearInterval(timerInt); newPlay = false; timerInt = setInterval(function() {incProgBar(progNode, maxTime, wrapPauseList);}, 50)}, delay);
timerIDs.push(timer);
//Loop - for each time on linePauseList
var numPauses = linePauseList.length;
if (numPauses > 0) {
for (i = 0; i < numPauses; i++) {
time = linePauseList[i] + delay;
timer = setTimeout(function() {pauseBar(progNode, maxTime, wrapPauseList);}, time);
timerIDs.push(timer);
}
}
}
function stopAllTimers() {
var timer;
for (i = 0; i < timerIDs.length; i++) {
timer = timerIDs.pop;
clearTimeout(timer);
}
timerIDs = [];
clearInterval(timerInt); //there should only ever be one instance of the interval timer running, and its ID is the global timerInt
}
function incProgBar(progNode, maxTime, wrapPauseList) {
var barTime;
if (newPlay == true) return; //This means a Play button has been clicked, and we are not yet cleared to update anything.
barTime = progNode.value;
barTime += 50;
progNode.value = barTime;
if (barTime >= maxTime) {
clearInterval(timerInt); //immediately stops the bar
if (wrapPauseList.length > 0) { // If it isn't, there's no more to do
runProgBar(wrapPauseList);
}
}
}
function pauseBar(pNode, maxTime, wrapPauseList) {
clearInterval(timerInt); //Stops the bar immediately
//clearInterval must always precede setInterval in the following function call to ensure only one instance of the timer is running.
timer = setTimeout(function() {clearInterval(timerInt); timerInt = setInterval(function() {incProgBar(pNode, maxTime, wrapPauseList);}, 50);}, pInterval);
timerIDs.push(timer);
}