blob: 238363f979bf3753e3b7803a074d5787de188fbb [file] [log] [blame]
JJ Allaire375805c2016-11-15 08:56:43 -05001/*****************************************************************
2** Author: Asvin Goel, goel@telematique.eu
3**
4** A plugin for reveal.js adding a chalkboard.
5**
6** Version: 0.5
7**
8** License: MIT license (see LICENSE.md)
9**
10** Credits:
11** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard
12******************************************************************/
13
14var RevealChalkboard = window.RevealChalkboard || (function(){
15 var path = scriptPath();
16 function scriptPath() {
17 // obtain plugin path from the script element
18 var src;
19 if (document.currentScript) {
20 src = document.currentScript.src;
21 } else {
22 var sel = document.querySelector('script[src$="/chalkboard.js"]')
23 if (sel) {
24 src = sel.src;
25 }
26 }
27
28 var path = typeof src === undefined ? src
29 : src.slice(0, src.lastIndexOf("/") + 1);
30//console.log("Path: " + path);
31 return path;
32}
33
34/*****************************************************************
35** Configuration
36******************************************************************/
37 var config = Reveal.getConfig().chalkboard || {};
38
39 var background, pen, draw, color;
40 var theme = config.theme || "chalkboard";
41 switch ( theme ) {
42 case "whiteboard":
43 background = [ 'rgba(127,127,127,.1)' , path + 'img/whiteboard.png' ];
44 pen = [ 'url(' + path + 'img/boardmarker.png), auto',
45 'url(' + path + 'img/boardmarker.png), auto' ];
46 draw = [ drawWithPen , drawWithPen ];
47 color = [ 'rgba(0,0,255,1)', 'rgba(0,0,255,1)' ];
48 break;
49 default:
50 background = [ 'rgba(127,127,127,.1)' , path + 'img/blackboard.png' ];
51 pen = [ 'url(' + path + 'img/boardmarker.png), auto',
52 'url(' + path + 'img/chalk.png), auto' ];
53 draw = [ drawWithPen , drawWithChalk ];
54 color = [ 'rgba(0,0,255,1)', 'rgba(255,255,255,0.5)' ];
55 }
56
57 if ( config.background ) background = config.background;
58 if ( config.pen ) pen = config.pen;
59 if ( config.draw ) draw = config.draw;
60 if ( config.color ) color = config.color;
61
62 var toggleChalkboardButton = config.toggleChalkboardButton == undefined ? true : config.toggleChalkboardButton;
63 var toggleNotesButton = config.toggleNotesButton == undefined ? true : config.toggleNotesButton;
64 var transition = config.transition || 800;
65
66 var readOnly = config.readOnly;
67
68 var legacyFileSupport = config.legacyFileSupport;
69 if ( legacyFileSupport ) { console.warn("Legacy file support is deprecated and may be removed in future versions!") }
70
71/*****************************************************************
72** Setup
73******************************************************************/
74
75 var eraserDiameter = 20;
76
77 if ( toggleChalkboardButton ) {
78//console.log("toggleChalkboardButton")
79 var button = document.createElement( 'div' );
80 button.className = "chalkboard-button";
81 button.id = "toggle-chalkboard";
82 button.style.vivibility = "visible";
83 button.style.position = "absolute";
84 button.style.zIndex = 30;
85 button.style.fontSize = "24px";
86
87 button.style.left = toggleChalkboardButton.left || "30px";
88 button.style.bottom = toggleChalkboardButton.bottom || "30px";
89 button.style.top = toggleChalkboardButton.top || "auto";
90 button.style.right = toggleChalkboardButton.right || "auto";
91
92 button.innerHTML = '<a href="#" onclick="RevealChalkboard.toggleChalkboard(); return false;"><i class="fa fa-pencil-square-o"></i></a>'
93 document.querySelector(".reveal").appendChild( button );
94 }
95 if ( toggleNotesButton ) {
96//console.log("toggleNotesButton")
97 var button = document.createElement( 'div' );
98 button.className = "chalkboard-button";
99 button.id = "toggle-notes";
100 button.style.position = "absolute";
101 button.style.zIndex = 30;
102 button.style.fontSize = "24px";
103
104 button.style.left = toggleNotesButton.left || "70px";
105 button.style.bottom = toggleNotesButton.bottom || "30px";
106 button.style.top = toggleNotesButton.top || "auto";
107 button.style.right = toggleNotesButton.right || "auto";
108
109 button.innerHTML = '<a href="#" onclick="RevealChalkboard.toggleNotesCanvas(); return false;"><i class="fa fa-pencil"></i></a>'
110 document.querySelector(".reveal").appendChild( button );
111 }
112//alert("Buttons");
113
114 var drawingCanvas = [ {id: "notescanvas" }, {id: "chalkboard" } ];
115 setupDrawingCanvas(0);
116 setupDrawingCanvas(1);
117
118 var mode = 0; // 0: notes canvas, 1: chalkboard
119
120 var mouseX = 0;
121 var mouseY = 0;
122 var xLast = null;
123 var yLast = null;
124
125 var slideStart = Date.now();
126 var slideIndices = { h:0, v:0 };
127 var event = null;
128 var timeouts = [ [], [] ];
129 var touchTimeout = null;
130 var slidechangeTimeout = null;
131 var playback = false;
132
133 function setupDrawingCanvas( id ) {
134 var container = document.createElement( 'div' );
135 container.id = drawingCanvas[id].id;
136 container.classList.add( 'overlay' );
137 container.setAttribute( 'data-prevent-swipe', '' );
138 container.oncontextmenu = function() { return false; }
139 container.style.cursor = pen[ id ];
140
141 drawingCanvas[id].width = window.innerWidth;
142 drawingCanvas[id].height = window.innerHeight;
143 drawingCanvas[id].scale = 1;
144 drawingCanvas[id].xOffset = 0;
145 drawingCanvas[id].yOffset = 0;
146
147
148 if ( id == "0" ) {
149 container.style.background = 'rgba(0,0,0,0)';
150 container.style.zIndex = "24";
151 container.classList.add( 'visible' )
152 container.style.pointerEvents = "none";
153
154 var slides = document.querySelector(".slides");
155 var aspectRatio = Reveal.getConfig().width / Reveal.getConfig().height;
156 if ( drawingCanvas[id].width > drawingCanvas[id].height*aspectRatio ) {
157 drawingCanvas[id].xOffset = (drawingCanvas[id].width - drawingCanvas[id].height*aspectRatio) / 2;
158 }
159 else if ( drawingCanvas[id].height > drawingCanvas[id].width/aspectRatio ) {
160 drawingCanvas[id].yOffset = ( drawingCanvas[id].height - drawingCanvas[id].width/aspectRatio ) / 2;
161 }
162 }
163 else {
164 container.style.background = 'url("' + background[id] + '") repeat';
165 container.style.zIndex = "26";
166 }
167
168 var sponge = document.createElement( 'img' );
169 sponge.src = path + 'img/sponge.png';
170 sponge.id = "sponge";
171 sponge.style.visibility = "hidden";
172 sponge.style.position = "absolute";
173 container.appendChild( sponge );
174 drawingCanvas[id].sponge = sponge;
175
176 var canvas = document.createElement( 'canvas' );
177 canvas.width = drawingCanvas[id].width;
178 canvas.height = drawingCanvas[id].height;
179 canvas.setAttribute( 'data-chalkboard', id );
180 canvas.style.cursor = pen[ id ];
181 container.appendChild( canvas );
182 drawingCanvas[id].canvas = canvas;
183
184 drawingCanvas[id].context = canvas.getContext("2d");
185
186
187 document.querySelector( '.reveal' ).appendChild( container );
188 drawingCanvas[id].container = container;
189 }
190
191
192/*****************************************************************
193** Storage
194******************************************************************/
195 var storage = [
196 { width: drawingCanvas[0].width - 2 * drawingCanvas[0].xOffset, height: drawingCanvas[0].height - 2 * drawingCanvas[0].yOffset, data: []},
197 { width: drawingCanvas[1].width, height: drawingCanvas[1].height, data: []}
198 ];
199//console.log( JSON.stringify(storage));
200
201 if ( config.src != null ) {
202 loadData( config.src );
203 }
204
205
206 /**
207 * Load data.
208 */
209 function loadData( filename ) {
210 var xhr = new XMLHttpRequest();
211 xhr.onload = function() {
212 if (xhr.readyState === 4) {
213 storage = JSON.parse(xhr.responseText);
214 for (var id = 0; id < storage.length; id++) {
215 if ( drawingCanvas[id].width != storage[id].width || drawingCanvas[id].height != storage[id].height ) {
216 drawingCanvas[id].scale = Math.min( drawingCanvas[id].width/storage[id].width, drawingCanvas[id].height/storage[id].height);
217 drawingCanvas[id].xOffset = (drawingCanvas[id].width - storage[id].width * drawingCanvas[id].scale)/2;
218 drawingCanvas[id].yOffset = (drawingCanvas[id].height - storage[id].height * drawingCanvas[id].scale)/2;
219 }
220 if ( config.readOnly ) {
221 drawingCanvas[id].container.style.cursor = 'default';
222 drawingCanvas[id].canvas.style.cursor = 'default';
223 }
224 }
225//console.log("Drawings loaded");
226 }
227 else {
228 config.readOnly = undefined;
229 readOnly = undefined;
230 console.warn( 'Failed to get file ' + filename +". ReadyState: " + xhr.readyState + ", Status: " + xhr.status);
231 }
232 };
233
234 xhr.open( 'GET', filename, true );
235 try {
236 xhr.send();
237 }
238 catch ( error ) {
239 config.readOnly = undefined;
240 readOnly = undefined;
241 console.warn( 'Failed to get file ' + filename + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + error );
242 }
243 }
244
245 /**
246 * Download data.
247 */
248 function downloadData() {
249 var a = document.createElement('a');
250 document.body.appendChild(a);
251 try {
252 // cleanup slide data without events
253 for (var id = 0; id < 2; id++) {
254 for (var i = storage[id].data.length-1; i >= 0; i--) {
255 if (storage[id].data[i].events.length == 0) {
256 storage[id].data.splice(i, 1);
257 }
258 }
259 }
260 a.download = "chalkboard.json";
261 var blob = new Blob( [ JSON.stringify( storage ) ], { type: "application/json"} );
262 a.href = window.URL.createObjectURL( blob );
263 } catch( error ) {
264 a.innerHTML += " (" + error + ")";
265 }
266 a.click();
267 document.body.removeChild(a);
268 }
269
270 /**
271 * Returns data object for the slide with the given indices.
272 */
273 function getSlideData( indices, id ) {
274 if ( id == undefined ) id = mode;
275 if (!indices) indices = slideIndices;
276 var data;
277 for (var i = 0; i < storage[id].data.length; i++) {
278 if (storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v === indices.v && storage[id].data[i].slide.f === indices.f ) {
279 data = storage[id].data[i];
280 return data;
281 }
282 if ( !legacyFileSupport &&
283 ( storage[id].data[i].slide.h > indices.h ||
284 ( storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v > indices.v ) ||
285 ( storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v === indices.v && storage[id].data[i].slide.f > indices.f )
286 )
287 ) {
288 storage[id].data.splice( i, 0, { slide: indices, events: [], duration: 0 } );
289 data = storage[id].data[i];
290 return data;
291 }
292 }
293 storage[id].data.push( { slide: indices, events: [], duration: 0 } );
294 data = storage[id].data[storage[id].data.length-1];
295 return data;
296 }
297
298 /**
299 * Returns maximum duration of slide playback for both modes
300 */
301 function getSlideDuration( indices ) {
302 if (!indices) indices = slideIndices;
303 var duration = 0;
304 for (var id = 0; id < 2; id++) {
305 for (var i = 0; i < storage[id].data.length; i++) {
306 if (storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v === indices.v && storage[id].data[i].slide.f === indices.f ) {
307 duration = Math.max( duration, storage[id].data[i].duration );
308 break;
309 }
310 }
311 }
312//console.log( duration );
313 return duration;
314 }
315
316/*****************************************************************
317** Print
318******************************************************************/
319 var printMode = ( /print-pdf/gi ).test( window.location.search );
320//console.log("createPrintout" + printMode)
321
322 function createPrintout( ) {
323//console.log( 'Create printout for ' + storage[1].data.length + " slides");
324 drawingCanvas[0].container.classList.remove( 'visible' ); // do not print notes canvas
325
326 var patImg = new Image();
327 patImg.onload = function () {
328 var nextSlide = [];
329 var width = Reveal.getConfig().width;
330 var height = Reveal.getConfig().height;
331 var scale = 1;
332 var xOffset = 0;
333 var yOffset = 0;
334 if ( width != storage[1].width || height != storage[1].height ) {
335 scale = Math.min( width/storage[1].width, height/storage[1].height);
336 xOffset = (width - storage[1].width * scale)/2;
337 yOffset = (height - storage[1].height * scale)/2;
338 }
339
340 for (var i = 0; i < storage[1].data.length; i++) {
341 var slide = Reveal.getSlide( storage[1].data[i].slide.h, storage[1].data[i].slide.v );
342 nextSlide.push( slide.nextSibling );
343 }
344 for (var i = 0; i < storage[1].data.length; i++) {
345 var slideData = getSlideData( storage[1].data[i].slide, 1 );
346
347 var imgCanvas = document.createElement('canvas');
348 imgCanvas.width = width;
349 imgCanvas.height = height;
350
351 var imgCtx = imgCanvas.getContext("2d");
352 imgCtx.fillStyle = imgCtx.createPattern( patImg ,'repeat');
353 imgCtx.rect(0,0,imgCanvas.width,imgCanvas.height);
354 imgCtx.fill();
355
356 for (var j = 0; j < slideData.events.length; j++) {
357 switch ( slideData.events[j].type ) {
358 case "draw":
359 for (var k = 1; k < slideData.events[j].curve.length; k++) {
360 draw[1]( imgCtx,
361 xOffset + slideData.events[j].curve[k-1].x*scale,
362 yOffset + slideData.events[j].curve[k-1].y*scale,
363 xOffset + slideData.events[j].curve[k].x*scale,
364 yOffset + slideData.events[j].curve[k].y*scale
365 );
366 }
367 break;
368 case "erase":
369 for (var k = 0; k < slideData.events[j].curve.length; k++) {
370 erase( imgCtx,
371 xOffset + slideData.events[j].curve[k].x*scale,
372 yOffset + slideData.events[j].curve[k].y*scale
373 );
374 }
375 break;
376 case "clear":
377 addPrintout( nextSlide[i], imgCanvas, patImg );
378 imgCtx.clearRect(0,0,imgCanvas.width,imgCanvas.height);
379 imgCtx.fill();
380 break;
381 default:
382 break;
383 }
384 }
385 if ( slideData.events.length ) {
386 addPrintout( nextSlide[i], imgCanvas, patImg );
387 }
388 }
389 Reveal.sync();
390 };
391 patImg.src = background[1];
392 }
393
394 function addPrintout( nextSlide, imgCanvas, patImg ) {
395 var slideCanvas = document.createElement('canvas');
396 slideCanvas.width = Reveal.getConfig().width;
397 slideCanvas.height = Reveal.getConfig().height;
398 var ctx = slideCanvas.getContext("2d");
399 ctx.fillStyle = ctx.createPattern( patImg ,'repeat');
400 ctx.rect(0,0,slideCanvas.width,slideCanvas.height);
401 ctx.fill();
402 ctx.drawImage(imgCanvas, 0, 0);
403
404 var newSlide = document.createElement( 'section' );
405 newSlide.classList.add( 'present' );
406 newSlide.innerHTML = '<h1 style="visibility:hidden">Drawing</h1>';
407 newSlide.setAttribute("data-background-size", '100% 100%' );
408 newSlide.setAttribute("data-background-repeat", 'norepeat' );
409 newSlide.setAttribute("data-background", 'url("' + slideCanvas.toDataURL("image/png") +'")' );
410 nextSlide.parentElement.insertBefore( newSlide, nextSlide );
411 }
412
413/*****************************************************************
414** Drawings
415******************************************************************/
416
417 function drawWithPen(context,fromX,fromY,toX,toY){
418 context.lineWidth = 3;
419 context.lineCap = 'round';
420 context.strokeStyle = color[0];
421 context.beginPath();
422 context.moveTo(fromX, fromY);
423 context.lineTo(toX, toY);
424 context.stroke();
425 }
426
427 function drawWithChalk(context,fromX,fromY,toX,toY){
428 var brushDiameter = 7;
429 context.lineWidth = brushDiameter;
430 context.lineCap = 'round';
431 context.fillStyle = color[1]; // 'rgba(255,255,255,0.5)';
432 context.strokeStyle = color[1];
433 var opacity = Math.min(0.8, Math.max(0,color[1].replace(/^.*,(.+)\)/,'$1') - 0.1)) + Math.random()*0.2;
434 context.strokeStyle = context.strokeStyle.replace(/[\d\.]+\)$/g, opacity + ')');
435 context.beginPath();
436 context.moveTo(fromX, fromY);
437 context.lineTo(toX, toY);
438 context.stroke();
439 // Chalk Effect
440 var length = Math.round(Math.sqrt(Math.pow(toX-fromX,2)+Math.pow(toY-fromY,2))/(5/brushDiameter));
441 var xUnit = (toX-fromX)/length;
442 var yUnit = (toY-fromY)/length;
443 for(var i=0; i<length; i++ ){
444 var xCurrent = fromX+(i*xUnit);
445 var yCurrent = fromY+(i*yUnit);
446 var xRandom = xCurrent+(Math.random()-0.5)*brushDiameter*1.2;
447 var yRandom = yCurrent+(Math.random()-0.5)*brushDiameter*1.2;
448 context.clearRect( xRandom, yRandom, Math.random()*2+2, Math.random()+1);
449 }
450 }
451 function erase(context,x,y){
452 context.save();
453 context.beginPath();
454 context.arc(x, y, eraserDiameter, 0, 2 * Math.PI, false);
455 context.clip();
456 context.clearRect(x - eraserDiameter - 1, y - eraserDiameter - 1, eraserDiameter * 2 + 2, eraserDiameter * 2 + 2);
457 context.restore();
458
459 }
460
461
462 /**
463 * Opens an overlay for the chalkboard.
464 */
465 function showChalkboard() {
466//console.log("showChalkboard");
467 clearTimeout(touchTimeout);
468 touchTimeout = null;
469 drawingCanvas[0].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden
470 drawingCanvas[1].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden
471 drawingCanvas[1].container.classList.add( 'visible' );
472 mode = 1;
473 }
474
475
476 /**
477 * Closes open chalkboard.
478 */
479 function closeChalkboard() {
480 clearTimeout(touchTimeout);
481 touchTimeout = null;
482 drawingCanvas[0].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden
483 drawingCanvas[1].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden
484 drawingCanvas[1].container.classList.remove( 'visible' );
485 xLast = null;
486 yLast = null;
487 event = null;
488 mode = 0;
489 }
490
491 /**
492 * Clear current canvas.
493 */
494 function clearCanvas( id ) {
495 if ( id == 0 ) clearTimeout( slidechangeTimeout );
496 drawingCanvas[id].context.clearRect(0,0,drawingCanvas[id].width,drawingCanvas[id].height);
497 }
498
499/*****************************************************************
500** Playback
501******************************************************************/
502
503 document.addEventListener('seekplayback', function( event ) {
504//console.log('event seekplayback ' + event.timestamp);
505 stopPlayback();
506 if ( !playback || event.timestamp == 0) {
507 // in other cases startplayback fires after seeked
508 startPlayback( event.timestamp );
509 }
510//console.log('seeked');
511 });
512
513
514 document.addEventListener('startplayback', function( event ) {
515//console.log('event startplayback ' + event.timestamp);
516 stopPlayback();
517 playback = true;
518 startPlayback( event.timestamp );
519 });
520
521 document.addEventListener('stopplayback', function( event ) {
522//console.log('event stopplayback ' + (Date.now() - slideStart) );
523 playback = false;
524 stopPlayback();
525 });
526
527 document.addEventListener('startrecording', function( event ) {
528//console.log('event startrecording ' + event.timestamp);
529 startRecording();
530 });
531
532 function recordEvent( event ) {
533 var slideData = getSlideData();
534 var i = slideData.events.length;
535 while ( i > 0 && event.begin < slideData.events[i-1].begin ) {
536 i--;
537 }
538 slideData.events.splice( i, 0, event);
539 slideData.duration = Math.max( slideData.duration, Date.now() - slideStart ) + 1;
540 }
541
542 function startRecording() {
543 resetSlide( true );
544 updateReadOnlyMode();
545 slideStart = Date.now();
546 }
547
548 function startPlayback( timestamp, finalMode, resized ) {
549//console.log("playback " + timestamp );
550 if ( resized == undefined ) {
551 updateReadOnlyMode();
552 }
553 slideStart = Date.now() - timestamp;
554 closeChalkboard();
555 mode = 0;
556 for ( var id = 0; id < 2; id++ ) {
557 clearCanvas( id );
558 var slideData = getSlideData( slideIndices, id );
559//console.log( timestamp +" / " + JSON.stringify(slideData));
560 var index = 0;
561 while ( index < slideData.events.length && slideData.events[index].begin < (Date.now() - slideStart) ) {
562 playEvent( id, slideData.events[index], timestamp );
563 index++;
564 }
565
566 while ( playback && index < slideData.events.length ) {
567 timeouts[id].push( setTimeout( playEvent, slideData.events[index].begin - (Date.now() - slideStart), id, slideData.events[index], timestamp ) );
568 index++;
569 }
570 }
571//console.log("Mode: " + finalMode + "/" + mode );
572 if ( finalMode != undefined ) {
573 mode = finalMode;
574 }
575 if( mode == 1 ) showChalkboard();
576//console.log("playback (ok)");
577
578 };
579
580 function stopPlayback() {
581//console.log("stopPlayback");
582//console.log("Timeouts: " + timeouts[0].length + "/"+ timeouts[1].length);
583 for ( var id = 0; id < 2; id++ ) {
584 for (var i = 0; i < timeouts[id].length; i++) {
585 clearTimeout(timeouts[id][i]);
586 }
587 timeouts[id] = [];
588 }
589 };
590
591 function playEvent( id, event, timestamp ) {
592//console.log( timestamp +" / " + JSON.stringify(event));
593//console.log( id + ": " + timestamp +" / " + event.begin +" / " + event.type +" / " + mode );
594 switch ( event.type ) {
595 case "open":
596 if ( timestamp <= event.begin ) {
597 showChalkboard();
598 }
599 else {
600 mode = 1;
601 }
602
603 break;
604 case "close":
605 if ( timestamp < event.begin ) {
606 closeChalkboard();
607 }
608 else {
609 mode = 0;
610 }
611 break;
612 case "clear":
613 clearCanvas( id );
614 break;
615 case "draw":
616 drawCurve( id, event, timestamp );
617 break;
618 case "erase":
619 eraseCurve( id, event, timestamp );
620 break;
621
622 }
623 };
624
625 function drawCurve( id, event, timestamp ) {
626 if ( event.curve.length > 1 ) {
627 var ctx = drawingCanvas[id].context;
628 var scale = drawingCanvas[id].scale;
629 var xOffset = drawingCanvas[id].xOffset;
630 var yOffset = drawingCanvas[id].yOffset;
631
632 var stepDuration = ( event.end - event.begin )/ ( event.curve.length - 1 );
633//console.log("---");
634 for (var i = 1; i < event.curve.length; i++) {
635 if (event.begin + i * stepDuration <= (Date.now() - slideStart)) {
636//console.log( "Draw " + timestamp +" / " + event.begin + " + " + i + " * " + stepDuration );
637 draw[id](ctx, xOffset + event.curve[i-1].x*scale, yOffset + event.curve[i-1].y*scale, xOffset + event.curve[i].x*scale, yOffset + event.curve[i].y*scale);
638 }
639 else if ( playback ) {
640//console.log( "Cue " + timestamp +" / " + (Date.now() - slideStart) +" / " + event.begin + " + " + i + " * " + stepDuration + " = " + Math.max(0,event.begin + i * stepDuration - timestamp) );
641 timeouts.push( setTimeout(
642 draw[id], Math.max(0,event.begin + i * stepDuration - (Date.now() - slideStart)), ctx,
643 xOffset + event.curve[i-1].x*scale,
644 yOffset + event.curve[i-1].y*scale,
645 xOffset + event.curve[i].x*scale,
646 yOffset + event.curve[i].y*scale
647 )
648 );
649 }
650 }
651 }
652
653 };
654
655 function eraseCurve( id, event, timestamp ) {
656 if ( event.curve.length > 1 ) {
657 var ctx = drawingCanvas[id].context;
658 var scale = drawingCanvas[id].scale;
659 var xOffset = drawingCanvas[id].xOffset;
660 var yOffset = drawingCanvas[id].yOffset;
661
662 var stepDuration = ( event.end - event.begin )/ event.curve.length;
663 for (var i = 0; i < event.curve.length; i++) {
664 if (event.begin + i * stepDuration <= (Date.now() - slideStart)) {
665 erase(ctx, xOffset + event.curve[i].x*scale, yOffset + event.curve[i].y*scale);
666 }
667 else if ( playback ) {
668 timeouts.push( setTimeout(
669 erase, Math.max(0,event.begin + i * stepDuration - (Date.now() - slideStart)), ctx,
670 xOffset + event.curve[i].x * scale,
671 yOffset + event.curve[i].y * scale
672 )
673 );
674 }
675 }
676 }
677
678 };
679
680/*****************************************************************
681** User interface
682******************************************************************/
683
684
685// TODO: check all touchevents
686 document.addEventListener('touchstart', function(evt) {
687 if ( !readOnly && evt.target.getAttribute('data-chalkboard') == mode ) {
688 var ctx = drawingCanvas[mode].context;
689 var scale = drawingCanvas[mode].scale;
690 var xOffset = drawingCanvas[mode].xOffset;
691 var yOffset = drawingCanvas[mode].yOffset;
692
693 evt.preventDefault();
694 var touch = evt.touches[0];
695 mouseX = touch.pageX;
696 mouseY = touch.pageY;
697 xLast = mouseX;
698 yLast = mouseY;
699 event = { type: "draw", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}] };
700 touchTimeout = setTimeout( showSponge, 500, mouseX, mouseY );
701 }
702 }, false);
703
704 document.addEventListener('touchmove', function(evt) {
705 clearTimeout( touchTimeout );
706 touchTimeout = null;
707 if ( event ) {
708 var ctx = drawingCanvas[mode].context;
709 var scale = drawingCanvas[mode].scale;
710 var xOffset = drawingCanvas[mode].xOffset;
711 var yOffset = drawingCanvas[mode].yOffset;
712
713 var touch = evt.touches[0];
714 mouseX = touch.pageX;
715 mouseY = touch.pageY;
716 if (mouseY < drawingCanvas[mode].height && mouseX < drawingCanvas[mode].width) {
717 evt.preventDefault();
718 event.curve.push({x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale});
719 if ( event.type == "erase" ) {
720 drawingCanvas[mode].sponge.style.left = (mouseX - eraserDiameter) +"px" ;
721 drawingCanvas[mode].sponge.style.top = (mouseY - eraserDiameter) +"px" ;
722 erase(ctx, mouseX, mouseY);
723 }
724 else {
725 draw[mode](ctx, xLast, yLast, mouseX, mouseY);
726 }
727 xLast = mouseX;
728 yLast = mouseY;
729 }
730 }
731 }, false);
732
733 document.addEventListener('touchend', function(evt) {
734 clearTimeout( touchTimeout );
735 touchTimeout = null;
736 // hide sponge image
737 drawingCanvas[mode].sponge.style.visibility = "hidden";
738 if ( event ) {
739 event.end = Date.now() - slideStart;
740 if ( event.type == "erase" || event.curve.length > 1 ) {
741 // do not save a line with a single point only
742 recordEvent( event );
743 }
744 event = null;
745 }
746 }, false);
747
748 function showSponge(x,y) {
749 if ( event ) {
750 event.type = "erase";
751 event.begin = Date.now() - slideStart;
752 // show sponge image
753 drawingCanvas[mode].sponge.style.left = (x - eraserDiameter) +"px" ;
754 drawingCanvas[mode].sponge.style.top = (y - eraserDiameter) +"px" ;
755 drawingCanvas[mode].sponge.style.visibility = "visible";
756 erase(drawingCanvas[mode].context,x,y);
757 }
758 }
759
760 document.addEventListener( 'mousedown', function( evt ) {
761//console.log( "Read only: " + readOnly );
762 if ( !readOnly && evt.target.getAttribute('data-chalkboard') == mode ) {
763//console.log( "mousedown: " + evt.target.getAttribute('data-chalkboard') );
764 var ctx = drawingCanvas[mode].context;
765 var scale = drawingCanvas[mode].scale;
766 var xOffset = drawingCanvas[mode].xOffset;
767 var yOffset = drawingCanvas[mode].yOffset;
768
769 mouseX = evt.pageX;
770 mouseY = evt.pageY;
771 xLast = mouseX;
772 yLast = mouseY;
773 if ( evt.button == 2) {
774 event = { type: "erase", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}]};
775 drawingCanvas[mode].canvas.style.cursor = 'url("' + path + 'img/sponge.png") ' + eraserDiameter + ' ' + eraserDiameter + ', auto';
776 erase(ctx,mouseX,mouseY);
777 }
778 else {
779 event = { type: "draw", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}] };
780 }
781 }
782 } );
783
784 document.addEventListener( 'mousemove', function( evt ) {
785 if ( event ) {
786 var ctx = drawingCanvas[mode].context;
787 var scale = drawingCanvas[mode].scale;
788 var xOffset = drawingCanvas[mode].xOffset;
789 var yOffset = drawingCanvas[mode].yOffset;
790
791 mouseX = evt.pageX;
792 mouseY = evt.pageY;
793 event.curve.push({x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale});
794 if(mouseY < drawingCanvas[mode].height && mouseX < drawingCanvas[mode].width) {
795 if ( event.type == "erase" ) {
796 erase(ctx,mouseX,mouseY);
797 }
798 else {
799 draw[mode](ctx, xLast, yLast, mouseX,mouseY);
800 }
801 xLast = mouseX;
802 yLast = mouseY;
803 }
804 }
805 } );
806
807 document.addEventListener( 'mouseup', function( evt ) {
808 drawingCanvas[mode].canvas.style.cursor = pen[mode];
809 if ( event ) {
810 if(evt.button == 2){
811 }
812 event.end = Date.now() - slideStart;
813 if ( event.type == "erase" || event.curve.length > 1 ) {
814 // do not save a line with a single point only
815 recordEvent( event );
816 }
817 event = null;
818 }
819 } );
820
821 window.addEventListener( "resize", function() {
822//console.log("resize");
823 // Resize the canvas and draw everything again
824 var timestamp = Date.now() - slideStart;
825 if ( !playback ) {
826 timestamp = getSlideDuration();
827 }
828
829//console.log( drawingCanvas[0].scale + "/" + drawingCanvas[0].xOffset + "/" +drawingCanvas[0].yOffset );
830 for (var id = 0; id < 2; id++ ) {
831 drawingCanvas[id].width = window.innerWidth;
832 drawingCanvas[id].height = window.innerHeight;
833 drawingCanvas[id].canvas.width = drawingCanvas[id].width;
834 drawingCanvas[id].canvas.height = drawingCanvas[id].height;
835 drawingCanvas[id].context.canvas.width = drawingCanvas[id].width;
836 drawingCanvas[id].context.canvas.height = drawingCanvas[id].height;
837
838 drawingCanvas[id].scale = Math.min( drawingCanvas[id].width/storage[id].width, drawingCanvas[id].height/storage[id].height );
839 drawingCanvas[id].xOffset = (drawingCanvas[id].width - storage[id].width * drawingCanvas[id].scale)/2;
840 drawingCanvas[id].yOffset = (drawingCanvas[id].height - storage[id].height * drawingCanvas[id].scale)/2;
841//console.log( drawingCanvas[id].scale + "/" + drawingCanvas[id].xOffset + "/" +drawingCanvas[id].yOffset );
842 }
843//console.log( window.innerWidth + "/" + window.innerHeight);
844 startPlayback( timestamp, mode, true );
845
846 } );
847
848 function updateReadOnlyMode() {
849//console.log("updateReadOnlyMode");
850 if ( config.readOnly == undefined ) {
851 readOnly = ( getSlideDuration() > 0 );
852 if ( readOnly ) {
853 drawingCanvas[0].container.style.cursor = 'default';
854 drawingCanvas[1].container.style.cursor = 'default';
855 drawingCanvas[0].canvas.style.cursor = 'default';
856 drawingCanvas[1].canvas.style.cursor = 'default';
857 if ( notescanvas.style.pointerEvents != "none" ) {
858 event = null;
859 notescanvas.style.background = 'rgba(0,0,0,0)';
860 notescanvas.style.pointerEvents = "none";
861 }
862
863 }
864 else {
865 drawingCanvas[0].container.style.cursor = pen[0];
866 drawingCanvas[1].container.style.cursor = pen[1];
867 drawingCanvas[0].canvas.style.cursor = pen[0];
868 drawingCanvas[1].canvas.style.cursor = pen[1];
869 }
870 }
871 }
872
873 Reveal.addEventListener( 'ready', function( evt ) {
874//console.log('ready');
875 if ( !printMode ) {
876 slideStart = Date.now();
877 slideIndices = Reveal.getIndices();
878 if ( !playback ) {
879 startPlayback( getSlideDuration(), 0 );
880 }
881 if ( Reveal.isAutoSliding() ) {
882 var event = new CustomEvent('startplayback');
883 event.timestamp = 0;
884 document.dispatchEvent( event );
885 }
886 updateReadOnlyMode();
887 }
888 else {
889 createPrintout();
890 }
891 });
892 Reveal.addEventListener( 'slidechanged', function( evt ) {
893// clearTimeout( slidechangeTimeout );
894//console.log('slidechanged');
895 if ( !printMode ) {
896 slideStart = Date.now();
897 slideIndices = Reveal.getIndices();
898 closeChalkboard();
899 clearCanvas( 0 );
900 clearCanvas( 1 );
901 if ( !playback ) {
902 slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
903 }
904 if ( Reveal.isAutoSliding() ) {
905 var event = new CustomEvent('startplayback');
906 event.timestamp = 0;
907 document.dispatchEvent( event );
908 }
909
910 updateReadOnlyMode();
911 }
912 });
913 Reveal.addEventListener( 'fragmentshown', function( evt ) {
914// clearTimeout( slidechangeTimeout );
915//console.log('fragmentshown');
916 if ( !printMode ) {
917 slideStart = Date.now();
918 slideIndices = Reveal.getIndices();
919 closeChalkboard();
920 clearCanvas( 0 );
921 clearCanvas( 1 );
922 if ( Reveal.isAutoSliding() ) {
923 var event = new CustomEvent('startplayback');
924 event.timestamp = 0;
925 document.dispatchEvent( event );
926 }
927 else if ( !playback ) {
928 //
929 startPlayback( getSlideDuration(), 0 );
930// closeChalkboard();
931 }
932 updateReadOnlyMode();
933 }
934 });
935 Reveal.addEventListener( 'fragmenthidden', function( evt ) {
936// clearTimeout( slidechangeTimeout );
937//console.log('fragmenthidden');
938 if ( !printMode ) {
939 slideStart = Date.now();
940 slideIndices = Reveal.getIndices();
941 closeChalkboard();
942 clearCanvas( 0 );
943 clearCanvas( 1 );
944 if ( Reveal.isAutoSliding() ) {
945 document.dispatchEvent( new CustomEvent('stopplayback') );
946 }
947 else if ( !playback ) {
948 startPlayback( getSlideDuration() );
949 closeChalkboard();
950 }
951 updateReadOnlyMode();
952 }
953 });
954
955 Reveal.addEventListener( 'autoslideresumed', function( evt ) {
956//console.log('autoslideresumed');
957 var event = new CustomEvent('startplayback');
958 event.timestamp = 0;
959 document.dispatchEvent( event );
960 });
961 Reveal.addEventListener( 'autoslidepaused', function( evt ) {
962//console.log('autoslidepaused');
963 document.dispatchEvent( new CustomEvent('stopplayback') );
964
965 // advance to end of slide
966// closeChalkboard();
967 startPlayback( getSlideDuration(), 0 );
968 });
969
970 function toggleNotesCanvas() {
971 if ( !readOnly ) {
972 if ( mode == 1 ) {
973 toggleChalkboard();
974 notescanvas.style.background = background[0]; //'rgba(255,0,0,0.5)';
975 notescanvas.style.pointerEvents = "auto";
976 }
977 else {
978 if ( notescanvas.style.pointerEvents != "none" ) {
979 event = null;
980 notescanvas.style.background = 'rgba(0,0,0,0)';
981 notescanvas.style.pointerEvents = "none";
982 }
983 else {
984 notescanvas.style.background = background[0]; //'rgba(255,0,0,0.5)';
985 notescanvas.style.pointerEvents = "auto";
986 }
987 }
988 }
989 };
990
991 function toggleChalkboard() {
992//console.log("toggleChalkboard " + mode);
993 if ( mode == 1 ) {
994 event = null;
995 if ( !readOnly ) recordEvent( { type:"close", begin: Date.now() - slideStart } );
996 closeChalkboard();
997 }
998 else {
999 showChalkboard();
1000 if ( !readOnly ) recordEvent( { type:"open", begin: Date.now() - slideStart } );
1001 }
1002 };
1003
1004 function clear() {
1005 if ( !readOnly ) {
1006 recordEvent( { type:"clear", begin: Date.now() - slideStart } );
1007 clearCanvas( mode );
1008 }
1009 };
1010
1011 function resetSlide( force ) {
1012 var ok = force || confirm("Please confirm to delete chalkboard drawings on this slide!");
1013 if ( ok ) {
1014//console.log("resetSlide ");
1015 stopPlayback();
1016 slideStart = Date.now();
1017 event = null;
1018 closeChalkboard();
1019
1020 clearCanvas( 0 );
1021 clearCanvas( 1 );
1022
1023 mode = 1;
1024 var slideData = getSlideData();
1025 slideData.duration = 0;
1026 slideData.events = [];
1027 mode = 0;
1028 var slideData = getSlideData();
1029 slideData.duration = 0;
1030 slideData.events = [];
1031
1032 updateReadOnlyMode();
1033 }
1034 };
1035
1036 function resetStorage( force ) {
1037 var ok = force || confirm("Please confirm to delete all chalkboard drawings!");
1038 if ( ok ) {
1039 stopPlayback();
1040 slideStart = Date.now();
1041 clearCanvas( 0 );
1042 clearCanvas( 1 );
1043 if ( mode == 1 ) {
1044 event = null;
1045 closeChalkboard();
1046 }
1047 storage = [
1048 { width: drawingCanvas[0].width - 2 * drawingCanvas[0].xOffset, height: drawingCanvas[0].height - 2 * drawingCanvas[0].yOffset, data: []},
1049 { width: drawingCanvas[1].width, height: drawingCanvas[1].height, data: []}
1050 ];
1051
1052 updateReadOnlyMode();
1053 }
1054 };
1055
1056 this.drawWithPen = drawWithPen;
1057 this.drawWithChalk = drawWithChalk;
1058 this.toggleNotesCanvas = toggleNotesCanvas;
1059 this.toggleChalkboard = toggleChalkboard;
1060 this.startRecording = startRecording;
1061 this.clear = clear;
1062 this.reset = resetSlide;
1063 this.resetAll = resetStorage;
1064 this.download = downloadData;
1065
1066 return this;
1067})();