blob: 44882d26e333fcc47bf4f6e9ba3491de7e8b651e [file] [log] [blame]
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001/*****************************************************************
2 ** Author: Asvin Goel, goel@telematique.eu
3 **
4 ** A plugin for reveal.js adding a chalkboard.
5 **
Marc Kupietz09b75752023-10-07 09:32:19 +02006 ** Version: 2.3.2
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02007 **
8 ** License: MIT license (see LICENSE.md)
9 **
10 ** Credits:
11 ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard
12 ** Multi color support initially added by Kurt Rinnert https://github.com/rinnert
13 ** Compatibility with reveal.js v4 by Hakim El Hattab https://github.com/hakimel
14 ******************************************************************/
15
Marc Kupietz09b75752023-10-07 09:32:19 +020016"use strict";
17
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +020018window.RevealChalkboard = window.RevealChalkboard || {
19 id: 'RevealChalkboard',
20 init: function ( deck ) {
Marc Kupietz09b75752023-10-07 09:32:19 +020021 initChalkboard.call(this, deck );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +020022 },
23 configure: function ( config ) {
24 configure( config );
25 },
26 toggleNotesCanvas: function () {
27 toggleNotesCanvas();
28 },
29 toggleChalkboard: function () {
30 toggleChalkboard();
31 },
32 colorIndex: function () {
33 colorIndex();
34 },
35 colorNext: function () {
36 colorNext();
37 },
38 colorPrev: function () {
39 colorPrev();
40 },
41 clear: function () {
42 clear();
43 },
44 reset: function () {
45 reset();
46 },
47 resetAll: function () {
48 resetAll();
49 },
50 updateStorage: function () {
51 updateStorage();
52 },
53 getData: function () {
54 return getData();
55 },
56 download: function () {
57 download();
58 },
59};
60
61function scriptPath() {
62 // obtain plugin path from the script element
63 var src;
64 if ( document.currentScript ) {
65 src = document.currentScript.src;
66 } else {
67 var sel = document.querySelector( 'script[src$="/chalkboard/plugin.js"]' )
68 if ( sel ) {
69 src = sel.src;
70 }
71 }
72 var path = ( src === undefined ) ? "" : src.slice( 0, src.lastIndexOf( "/" ) + 1 );
73//console.log("Path: " + path);
74 return path;
75}
76var path = scriptPath();
77
78const initChalkboard = function ( Reveal ) {
79//console.warn(path);
80 /* Feature detection for passive event handling*/
81 var passiveSupported = false;
82
83 try {
84 window.addEventListener( 'test', null, Object.defineProperty( {}, 'passive', {
85 get: function () {
86 passiveSupported = true;
87 }
88 } ) );
89 } catch ( err ) {}
90
91
92/*****************************************************************
93 ** Configuration
94 ******************************************************************/
Marc Kupietz09b75752023-10-07 09:32:19 +020095 var background, pens, draw, color;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +020096 var grid = false;
97 var boardmarkerWidth = 3;
98 var chalkWidth = 7;
99 var chalkEffect = 1.0;
100 var rememberColor = [ true, false ];
101 var eraser = {
102 src: path + 'img/sponge.png',
103 radius: 20
104 };
105 var boardmarkers = [ {
106 color: 'rgba(100,100,100,1)',
107 cursor: 'url(' + path + 'img/boardmarker-black.png), auto'
108 },
109 {
110 color: 'rgba(30,144,255, 1)',
111 cursor: 'url(' + path + 'img/boardmarker-blue.png), auto'
112 },
113 {
114 color: 'rgba(220,20,60,1)',
115 cursor: 'url(' + path + 'img/boardmarker-red.png), auto'
116 },
117 {
118 color: 'rgba(50,205,50,1)',
119 cursor: 'url(' + path + 'img/boardmarker-green.png), auto'
120 },
121 {
122 color: 'rgba(255,140,0,1)',
123 cursor: 'url(' + path + 'img/boardmarker-orange.png), auto'
124 },
125 {
126 color: 'rgba(150,0,20150,1)',
127 cursor: 'url(' + path + 'img/boardmarker-purple.png), auto'
128 },
129 {
130 color: 'rgba(255,220,0,1)',
131 cursor: 'url(' + path + 'img/boardmarker-yellow.png), auto'
132 }
133 ];
134 var chalks = [ {
135 color: 'rgba(255,255,255,0.5)',
136 cursor: 'url(' + path + 'img/chalk-white.png), auto'
137 },
138 {
139 color: 'rgba(96, 154, 244, 0.5)',
140 cursor: 'url(' + path + 'img/chalk-blue.png), auto'
141 },
142 {
143 color: 'rgba(237, 20, 28, 0.5)',
144 cursor: 'url(' + path + 'img/chalk-red.png), auto'
145 },
146 {
147 color: 'rgba(20, 237, 28, 0.5)',
148 cursor: 'url(' + path + 'img/chalk-green.png), auto'
149 },
150 {
151 color: 'rgba(220, 133, 41, 0.5)',
152 cursor: 'url(' + path + 'img/chalk-orange.png), auto'
153 },
154 {
155 color: 'rgba(220,0,220,0.5)',
156 cursor: 'url(' + path + 'img/chalk-purple.png), auto'
157 },
158 {
159 color: 'rgba(255,220,0,0.5)',
160 cursor: 'url(' + path + 'img/chalk-yellow.png), auto'
161 }
162 ];
Marc Kupietz09b75752023-10-07 09:32:19 +0200163
164 var sponge = {
165 cursor: 'url(' + path + 'img/sponge.png), auto'
166 }
167
168
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200169 var keyBindings = {
170 toggleNotesCanvas: {
171 keyCode: 67,
172 key: 'C',
173 description: 'Toggle notes canvas'
174 },
175 toggleChalkboard: {
176 keyCode: 66,
177 key: 'B',
178 description: 'Toggle chalkboard'
179 },
180 clear: {
181 keyCode: 46,
182 key: 'DEL',
183 description: 'Clear drawings on slide'
184 },
185/*
186 reset: {
187 keyCode: 173,
188 key: '-',
189 description: 'Reset drawings on slide'
190 },
191*/
192 resetAll: {
193 keyCode: 8,
194 key: 'BACKSPACE',
195 description: 'Reset all drawings'
196 },
197 colorNext: {
198 keyCode: 88,
199 key: 'X',
200 description: 'Next color'
201 },
202 colorPrev: {
203 keyCode: 89,
204 key: 'Y',
205 description: 'Previous color'
206 },
207 download: {
208 keyCode: 68,
209 key: 'D',
210 description: 'Download drawings'
211 }
212 };
213
214
215 var theme = 'chalkboard';
216 var color = [ 0, 0 ];
217 var toggleChalkboardButton = false;
218 var toggleNotesButton = false;
219 var colorButtons = true;
220 var boardHandle = true;
221 var transition = 800;
222
223 var readOnly = false;
224 var messageType = 'broadcast';
225
226 var config = configure( Reveal.getConfig().chalkboard || {} );
227 if ( config.keyBindings ) {
228 for ( var key in config.keyBindings ) {
229 keyBindings[ key ] = config.keyBindings[ key ];
230 };
231 }
232
233 function configure( config ) {
234
235 if ( config.boardmarkerWidth || config.penWidth ) boardmarkerWidth = config.boardmarkerWidth || config.penWidth;
236 if ( config.chalkWidth ) chalkWidth = config.chalkWidth;
237 if ( config.chalkEffect ) chalkEffect = config.chalkEffect;
238 if ( config.rememberColor ) rememberColor = config.rememberColor;
239 if ( config.eraser ) eraser = config.eraser;
240 if ( config.boardmarkers ) boardmarkers = config.boardmarkers;
241 if ( config.chalks ) chalks = config.chalks;
242
243 if ( config.theme ) theme = config.theme;
244 switch ( theme ) {
245 case 'whiteboard':
246 background = [ 'rgba(127,127,127,.1)', path + 'img/whiteboard.png' ];
247 draw = [ drawWithBoardmarker, drawWithBoardmarker ];
248 pens = [ boardmarkers, boardmarkers ];
249 grid = {
250 color: 'rgb(127,127,255,0.1)',
251 distance: 40,
252 width: 2
253 };
254 break;
255 case 'chalkboard':
256 default:
257 background = [ 'rgba(127,127,127,.1)', path + 'img/blackboard.png' ];
258 draw = [ drawWithBoardmarker, drawWithChalk ];
259 pens = [ boardmarkers, chalks ];
260 grid = {
261 color: 'rgb(50,50,10,0.5)',
262 distance: 80,
263 width: 2
264 };
265 }
266
267 if ( config.background ) background = config.background;
268 if ( config.grid != undefined ) grid = config.grid;
269
270 if ( config.toggleChalkboardButton != undefined ) toggleChalkboardButton = config.toggleChalkboardButton;
271 if ( config.toggleNotesButton != undefined ) toggleNotesButton = config.toggleNotesButton;
272 if ( config.colorButtons != undefined ) colorButtons = config.colorButtons;
273 if ( config.boardHandle != undefined ) boardHandle = config.boardHandle;
274 if ( config.transition ) transition = config.transition;
275
276 if ( config.readOnly != undefined ) readOnly = config.readOnly;
277 if ( config.messageType ) messageType = config.messageType;
278
279 if ( drawingCanvas && ( config.theme || config.background || config.grid ) ) {
280 var canvas = document.getElementById( drawingCanvas[ 1 ].id );
281 canvas.style.background = 'url("' + background[ 1 ] + '") repeat';
282 clearCanvas( 1 );
283 drawGrid();
284 }
285
286 return config;
287 }
288/*****************************************************************
289 ** Setup
290 ******************************************************************/
291
292 function whenReady( callback ) {
293 // wait for markdown to be parsed and code to be highlighted
294 if ( !document.querySelector( 'section[data-markdown]:not([data-markdown-parsed])' )
Marc Kupietz09b75752023-10-07 09:32:19 +0200295 && !document.querySelector( '[data-load]:not([data-loaded])')
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200296 && !document.querySelector( 'code[data-line-numbers*="|"]')
297 ) {
298 callback();
299 } else {
Marc Kupietz09b75752023-10-07 09:32:19 +0200300 console.log( "Wait for external sources to be loaded and code to be highlighted" );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200301 setTimeout( whenReady, 500, callback )
302 }
303 }
304
305 function whenLoaded( callback ) {
306 // wait for drawings to be loaded and markdown to be parsed
307 if ( loaded !== null ) {
308 callback();
309 } else {
310 console.log( "Wait for drawings to be loaded" );
311 setTimeout( whenLoaded, 500, callback )
312 }
313 }
314
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200315 var drawingCanvas = [ {
316 id: 'notescanvas'
317 }, {
318 id: 'chalkboard'
319 } ];
320 setupDrawingCanvas( 0 );
321 setupDrawingCanvas( 1 );
322
323 var mode = 0; // 0: notes canvas, 1: chalkboard
324 var board = 0; // board index (only for chalkboard)
325
326 var mouseX = 0;
327 var mouseY = 0;
328 var lastX = null;
329 var lastY = null;
330
331 var drawing = false;
332 var erasing = false;
333
334 var slideStart = Date.now();
335 var slideIndices = {
336 h: 0,
337 v: 0
338 };
339
340 var timeouts = [
341 [],
342 []
343 ];
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200344 var slidechangeTimeout = null;
345 var updateStorageTimeout = null;
346 var playback = false;
347
Marc Kupietz09b75752023-10-07 09:32:19 +0200348 function changeCursor( element, tool ) {
349 element.style.cursor = tool.cursor;
350 var palette = document.querySelector('.palette[data-mode="' + mode + '"]');
351 if ( palette ) {
352 palette.style.cursor = tool.cursor;
353 }
354 }
355
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200356 function createPalette( colors, length ) {
357 if ( length === true || length > colors.length ) {
358 length = colors.length;
359 }
360 var palette = document.createElement( 'div' );
361 palette.classList.add( 'palette' );
362 var list = document.createElement( 'ul' );
363 // color pickers
364 for ( var i = 0; i < length; i++ ) {
365 var colorButton = document.createElement( 'li' );
366 colorButton.setAttribute( 'data-color', i );
367 colorButton.innerHTML = '<i class="fa fa-square"></i>';
368 colorButton.style.color = colors[ i ].color;
369 colorButton.addEventListener( 'click', function ( e ) {
370 var element = e.target;
371 while ( !element.hasAttribute( 'data-color' ) ) {
372 element = element.parentElement;
373 }
374 colorIndex( parseInt( element.getAttribute( 'data-color' ) ) );
375 } );
376 colorButton.addEventListener( 'touchstart', function ( e ) {
377 var element = e.target;
378 while ( !element.hasAttribute( 'data-color' ) ) {
379 element = element.parentElement;
380 }
381 colorIndex( parseInt( element.getAttribute( 'data-color' ) ) );
382 } );
383 list.appendChild( colorButton );
384 }
Marc Kupietz09b75752023-10-07 09:32:19 +0200385 // eraser
386 var eraserButton = document.createElement( 'li' );
387 eraserButton.setAttribute( 'data-eraser', 'true' );
388 var spongeImg = document.createElement( 'img' );
389 spongeImg.src = eraser.src;
390 spongeImg.height = "24";
391 spongeImg.width = "24";
392 spongeImg.style.marginTop = '10px';
393 spongeImg.style.marginRight = '0';
394 spongeImg.style.marginBottom = '0';
395 spongeImg.style.marginLeft = '0';
396 eraserButton.appendChild(spongeImg);
397 eraserButton.addEventListener( 'click', function ( e ) {
398 colorIndex( -1 );
399 } );
400 eraserButton.addEventListener( 'touchstart', function ( e ) {
401 colorIndex( -1 );
402 } );
403 list.appendChild( eraserButton );
404
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200405 palette.appendChild( list );
406 return palette;
407 };
408
409 function switchBoard( boardIdx ) {
410 selectBoard( boardIdx, true );
411 // broadcast
412 var message = new CustomEvent( messageType );
413 message.content = {
414 sender: 'chalkboard-plugin',
415 type: 'selectboard',
416 timestamp: Date.now() - slideStart,
417 mode,
418 board
419 };
420 document.dispatchEvent( message );
421 }
422
423 function setupDrawingCanvas( id ) {
424 var container = document.createElement( 'div' );
425 container.id = drawingCanvas[ id ].id;
426 container.classList.add( 'overlay' );
427 container.setAttribute( 'data-prevent-swipe', 'true' );
428 container.oncontextmenu = function () {
429 return false;
430 }
Marc Kupietz09b75752023-10-07 09:32:19 +0200431
432 changeCursor( container, pens[ id ][ color[ id ] ] );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200433
434 drawingCanvas[ id ].width = window.innerWidth;
435 drawingCanvas[ id ].height = window.innerHeight;
436 drawingCanvas[ id ].scale = 1;
437 drawingCanvas[ id ].xOffset = 0;
438 drawingCanvas[ id ].yOffset = 0;
439
440 if ( id == "0" ) {
441 container.style.background = 'rgba(0,0,0,0)';
442 container.style.zIndex = 24;
443 container.style.opacity = 1;
444 container.style.visibility = 'visible';
445 container.style.pointerEvents = 'none';
446
447 var slides = document.querySelector( '.slides' );
448 var aspectRatio = Reveal.getConfig().width / Reveal.getConfig().height;
449 if ( drawingCanvas[ id ].width > drawingCanvas[ id ].height * aspectRatio ) {
450 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - drawingCanvas[ id ].height * aspectRatio ) / 2;
451 } else if ( drawingCanvas[ id ].height > drawingCanvas[ id ].width / aspectRatio ) {
452 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - drawingCanvas[ id ].width / aspectRatio ) / 2;
453 }
454
455 if ( colorButtons ) {
456 var palette = createPalette( boardmarkers, colorButtons );
Marc Kupietz09b75752023-10-07 09:32:19 +0200457 palette.dataset.mode = id;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200458 palette.style.visibility = 'hidden'; // only show palette in drawing mode
459 container.appendChild( palette );
460 }
461 } else {
462 container.style.background = 'url("' + background[ id ] + '") repeat';
463 container.style.zIndex = 26;
464 container.style.opacity = 0;
465 container.style.visibility = 'hidden';
466
467 if ( colorButtons ) {
468 var palette = createPalette( chalks, colorButtons );
Marc Kupietz09b75752023-10-07 09:32:19 +0200469 palette.dataset.mode = id;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200470 container.appendChild( palette );
471 }
472 if ( boardHandle ) {
473 var handle = document.createElement( 'div' );
474 handle.classList.add( 'boardhandle' );
475 handle.innerHTML = '<ul><li><a id="previousboard" href="#" title="Previous board"><i class="fas fa-chevron-up"></i></a></li><li><a id="nextboard" href="#" title="Next board"><i class="fas fa-chevron-down"></i></a></li></ul>';
476 handle.querySelector( '#previousboard' ).addEventListener( 'click', function ( e ) {
477 e.preventDefault();
478 switchBoard( board - 1 );
479 } );
480 handle.querySelector( '#nextboard' ).addEventListener( 'click', function ( e ) {
481 e.preventDefault();
482 switchBoard( board + 1 );
483 } );
484 handle.querySelector( '#previousboard' ).addEventListener( 'touchstart', function ( e ) {
485 e.preventDefault();
486 switchBoard( board - 1 );
487 } );
488 handle.querySelector( '#nextboard' ).addEventListener( 'touchstart', function ( e ) {
489 e.preventDefault();
490 switchBoard( board + 1 );
491 } );
492
493 container.appendChild( handle );
494 }
495 }
496
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200497 var canvas = document.createElement( 'canvas' );
498 canvas.width = drawingCanvas[ id ].width;
499 canvas.height = drawingCanvas[ id ].height;
500 canvas.setAttribute( 'data-chalkboard', id );
Marc Kupietz09b75752023-10-07 09:32:19 +0200501 changeCursor( canvas, pens[ id ][ color[ id ] ] );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200502 container.appendChild( canvas );
503 drawingCanvas[ id ].canvas = canvas;
504
505 drawingCanvas[ id ].context = canvas.getContext( '2d' );
506
507 setupCanvasEvents( container );
508
509 document.querySelector( '.reveal' ).appendChild( container );
510 drawingCanvas[ id ].container = container;
511 }
512
513
514/*****************************************************************
515 ** Storage
516 ******************************************************************/
517
518 var storage = [ {
519 width: Reveal.getConfig().width,
520 height: Reveal.getConfig().height,
521 data: []
522 },
523 {
524 width: Reveal.getConfig().width,
525 height: Reveal.getConfig().height,
526 data: []
527 }
528 ];
529
530 var loaded = null;
531
532 if ( config.storage ) {
533 // Get chalkboard drawings from session storage
534 loaded = initStorage( sessionStorage.getItem( config.storage ) );
535 }
536
537 if ( !loaded && config.src != null ) {
538 // Get chalkboard drawings from the given file
539 loadData( config.src );
540 }
541
542 /**
543 * Initialize storage.
544 */
545 function initStorage( json ) {
546 var success = false;
547 try {
548 var data = JSON.parse( json );
549 for ( var id = 0; id < data.length; id++ ) {
550 if ( drawingCanvas[ id ].width != data[ id ].width || drawingCanvas[ id ].height != data[ id ].height ) {
551 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / data[ id ].width, drawingCanvas[ id ].height / data[ id ].height );
552 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - data[ id ].width * drawingCanvas[ id ].scale ) / 2;
553 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - data[ id ].height * drawingCanvas[ id ].scale ) / 2;
554 }
555 if ( config.readOnly ) {
556 drawingCanvas[ id ].container.style.cursor = 'default';
557 drawingCanvas[ id ].canvas.style.cursor = 'default';
558 }
559 }
560 success = true;
561 storage = data;
562 } catch ( err ) {
563 console.warn( "Cannot initialise storage!" );
564 }
565 return success;
566 }
567
568
569 /**
570 * Load data.
571 */
572 function loadData( filename ) {
573 var xhr = new XMLHttpRequest();
574 xhr.onload = function () {
575 if ( xhr.readyState === 4 && xhr.status != 404 ) {
576 loaded = initStorage( xhr.responseText );
577 updateStorage();
578 console.log( "Drawings loaded from file" );
579 } else {
580 config.readOnly = undefined;
581 readOnly = undefined;
582 console.warn( 'Failed to get file ' + filename + '. ReadyState: ' + xhr.readyState + ', Status: ' + xhr.status );
583 loaded = false;
584 }
585 };
586
587 xhr.open( 'GET', filename, true );
588 try {
589 xhr.send();
590 } catch ( error ) {
591 config.readOnly = undefined;
592 readOnly = undefined;
593 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 );
594 loaded = false;
595 }
596 }
597
598
599 function storageChanged( now ) {
600 if ( !now ) {
601 // create or update timer
602 if ( updateStorageTimeout ) {
603 clearTimeout( updateStorageTimeout );
604 }
605 updateStorageTimeout = setTimeout( storageChanged, 1000, true);
606 }
607 else {
608// console.log("Update storage", updateStorageTimeout, Date.now());
609 updateStorage();
610 updateStorageTimeout = null;
611 }
612 }
613
614 function updateStorage() {
615 var json = JSON.stringify( storage )
616 if ( config.storage ) {
617 sessionStorage.setItem( config.storage, json )
618 }
619 return json;
620 }
621
622 function recordEvent( event ) {
623//console.log(event);
624 event.time = Date.now() - slideStart;
625 if ( mode == 1 ) event.board = board;
626 var slideData = getSlideData();
627 var i = slideData.events.length;
628 while ( i > 0 && event.time < slideData.events[ i - 1 ].time ) {
629 i--;
630 }
631 slideData.events.splice( i, 0, event );
632 slideData.duration = Math.max( slideData.duration, Date.now() - slideStart ) + 1;
633
634 storageChanged();
635 }
636
637 /**
638 * Get data as json string.
639 */
640 function getData() {
641 // cleanup slide data without events
642 for ( var id = 0; id < 2; id++ ) {
643 for ( var i = storage[ id ].data.length - 1; i >= 0; i-- ) {
644 if ( storage[ id ].data[ i ].events.length == 0 ) {
645 storage[ id ].data.splice( i, 1 );
646 }
647 }
648 }
649
650 return updateStorage();
651 }
652
653 /**
654 * Download data.
655 */
656 function downloadData() {
657 var a = document.createElement( 'a' );
658 document.body.appendChild( a );
659 try {
660 a.download = 'chalkboard.json';
661 var blob = new Blob( [ getData() ], {
662 type: 'application/json'
663 } );
664 a.href = window.URL.createObjectURL( blob );
665 } catch ( error ) {
666 a.innerHTML += ' (' + error + ')';
667 }
668 a.click();
669 document.body.removeChild( a );
670 }
671
672 /**
673 * Returns data object for the slide with the given indices.
674 */
675 function getSlideData( indices, id ) {
676 if ( id == undefined ) id = mode;
677 if ( !indices ) indices = slideIndices;
678 var data;
679 for ( var i = 0; i < storage[ id ].data.length; i++ ) {
680 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 ) {
681 data = storage[ id ].data[ i ];
682 return data;
683 }
684 }
685 var page = Number( Reveal.getCurrentSlide().getAttribute('data-pdf-page-number') );
686//console.log( indices, Reveal.getCurrentSlide() );
687 storage[ id ].data.push( {
688 slide: indices,
689 page,
690 events: [],
691 duration: 0
692 } );
693 data = storage[ id ].data[ storage[ id ].data.length - 1 ];
694 return data;
695 }
696
697 /**
698 * Returns maximum duration of slide playback for both modes
699 */
700 function getSlideDuration( indices ) {
701 if ( !indices ) indices = slideIndices;
702 var duration = 0;
703 for ( var id = 0; id < 2; id++ ) {
704 for ( var i = 0; i < storage[ id ].data.length; i++ ) {
705 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 ) {
706 duration = Math.max( duration, storage[ id ].data[ i ].duration );
707 break;
708 }
709 }
710 }
711//console.log( duration );
712 return duration;
713 }
714
715/*****************************************************************
716 ** Print
717 ******************************************************************/
718 var printMode = ( /print-pdf/gi ).test( window.location.search );
719//console.log("createPrintout" + printMode)
720
721 function addPageNumbers() {
722 // determine page number for printouts with fragments serialised
723 var slides = Reveal.getSlides();
724 var page = 0;
725 for ( var i=0; i < slides.length; i++) {
726 slides[i].setAttribute('data-pdf-page-number',page.toString());
727 // add number of fragments without fragment indices
728 var count = slides[i].querySelectorAll('.fragment:not([data-fragment-index])').length;
729 var fragments = slides[i].querySelectorAll('.fragment[data-fragment-index]');
730 for ( var j=0; j < fragments.length; j++) {
731 // increasenumber of fragments by highest fragment index (which start at 0)
732 if ( Number(fragments[j].getAttribute('data-fragment-index')) + 1 > count ) {
733 count = Number(fragments[j].getAttribute('data-fragment-index')) + 1;
734 }
735 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200736 page += count + 1;
737 }
738 }
739
740 function createPrintout() {
741 //console.warn(Reveal.getTotalSlides(),Reveal.getSlidesElement());
742 if ( storage[ 1 ].data.length == 0 ) return;
743 console.log( 'Create printout(s) for ' + storage[ 1 ].data.length + " slides" );
744 drawingCanvas[ 0 ].container.style.opacity = 0; // do not print notes canvas
745 drawingCanvas[ 0 ].container.style.visibility = 'hidden';
746
747 var patImg = new Image();
748 patImg.onload = function () {
749 var slides = Reveal.getSlides();
750//console.log(slides);
751 for ( var i = storage[ 1 ].data.length - 1; i >= 0; i-- ) {
752 console.log( 'Create printout for slide ' + storage[ 1 ].data[ i ].slide.h + '.' + storage[ 1 ].data[ i ].slide.v );
753 var slideData = getSlideData( storage[ 1 ].data[ i ].slide, 1 );
754 var drawings = createDrawings( slideData, patImg );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200755 addDrawings( slides[storage[ 1 ].data[ i ].page], drawings );
756
757 }
758// Reveal.sync();
759 };
760 patImg.src = background[ 1 ];
761 }
762
763
764 function cloneCanvas( oldCanvas ) {
765 //create a new canvas
766 var newCanvas = document.createElement( 'canvas' );
767 var context = newCanvas.getContext( '2d' );
768 //set dimensions
769 newCanvas.width = oldCanvas.width;
770 newCanvas.height = oldCanvas.height;
771 //apply the old canvas to the new one
772 context.drawImage( oldCanvas, 0, 0 );
773 //return the new canvas
774 return newCanvas;
775 }
776
777 function getCanvas( template, container, board ) {
778 var idx = container.findIndex( element => element.board === board );
779 if ( idx === -1 ) {
780 var canvas = cloneCanvas( template );
781 if ( !container.length ) {
782 idx = 0;
783 container.push( {
784 board,
785 canvas
786 } );
787 } else if ( board < container[ 0 ].board ) {
788 idx = 0;
789 container.unshift( {
790 board,
791 canvas
792 } );
793 } else if ( board > container[ container.length - 1 ].board ) {
794 idx = container.length;
795 container.push( {
796 board,
797 canvas
798 } );
799 }
800 }
801
802 return container[ idx ].canvas;
803 }
804
805 function createDrawings( slideData, patImg ) {
806 var width = Reveal.getConfig().width;
807 var height = Reveal.getConfig().height;
808 var scale = 1;
809 var xOffset = 0;
810 var yOffset = 0;
811 if ( width != storage[ 1 ].width || height != storage[ 1 ].height ) {
812 scale = Math.min( width / storage[ 1 ].width, height / storage[ 1 ].height );
813 xOffset = ( width - storage[ 1 ].width * scale ) / 2;
814 yOffset = ( height - storage[ 1 ].height * scale ) / 2;
815 }
816 mode = 1;
817 board = 0;
818// console.log( 'Create printout(s) for slide ', slideData );
819
820 var drawings = [];
821 var template = document.createElement( 'canvas' );
822 template.width = width;
823 template.height = height;
824
825 var imgCtx = template.getContext( '2d' );
826 imgCtx.fillStyle = imgCtx.createPattern( patImg, 'repeat' );
827 imgCtx.rect( 0, 0, width, height );
828 imgCtx.fill();
829
830 for ( var j = 0; j < slideData.events.length; j++ ) {
831 switch ( slideData.events[ j ].type ) {
832 case 'draw':
833 draw[ 1 ]( getCanvas( template, drawings, board ).getContext( '2d' ),
834 xOffset + slideData.events[ j ].x1 * scale,
835 yOffset + slideData.events[ j ].y1 * scale,
836 xOffset + slideData.events[ j ].x2 * scale,
837 yOffset + slideData.events[ j ].y2 * scale,
838 yOffset + slideData.events[ j ].color
839 );
840 break;
841 case 'erase':
842 eraseWithSponge( getCanvas( template, drawings, board ).getContext( '2d' ),
843 xOffset + slideData.events[ j ].x * scale,
844 yOffset + slideData.events[ j ].y * scale
845 );
846 break;
847 case 'selectboard':
848 selectBoard( slideData.events[ j ].board );
849 break;
850 case 'clear':
851 getCanvas( template, drawings, board ).getContext( '2d' ).clearRect( 0, 0, width, height );
852 getCanvas( template, drawings, board ).getContext( '2d' ).fill();
853 break;
854 default:
855 break;
856 }
857 }
858
859 drawings = drawings.sort( ( a, b ) => a.board > b.board && 1 || -1 );
860
861 mode = 0;
862
863 return drawings;
864 }
865
866 function addDrawings( slide, drawings ) {
867 var parent = slide.parentElement.parentElement;
868 var nextSlide = slide.parentElement.nextElementSibling;
869
870 for ( var i = 0; i < drawings.length; i++ ) {
871 var newPDFPage = document.createElement( 'div' );
872 newPDFPage.classList.add( 'pdf-page' );
873 newPDFPage.style.height = Reveal.getConfig().height;
874 newPDFPage.append( drawings[ i ].canvas );
875//console.log("Add drawing", newPDFPage);
876 if ( nextSlide != null ) {
877 parent.insertBefore( newPDFPage, nextSlide );
878 } else {
879 parent.append( newPDFPage );
880 }
881 }
882 }
883
884 /*****************************************************************
885 ** Drawings
886 ******************************************************************/
887
888 function drawWithBoardmarker( context, fromX, fromY, toX, toY, colorIdx ) {
889 if ( colorIdx == undefined ) colorIdx = color[ mode ];
890 context.lineWidth = boardmarkerWidth;
891 context.lineCap = 'round';
892 context.strokeStyle = boardmarkers[ colorIdx ].color;
893 context.beginPath();
894 context.moveTo( fromX, fromY );
895 context.lineTo( toX, toY );
896 context.stroke();
897 }
898
899 function drawWithChalk( context, fromX, fromY, toX, toY, colorIdx ) {
900 if ( colorIdx == undefined ) colorIdx = color[ mode ];
901 var brushDiameter = chalkWidth;
902 context.lineWidth = brushDiameter;
903 context.lineCap = 'round';
904 context.fillStyle = chalks[ colorIdx ].color; // 'rgba(255,255,255,0.5)';
905 context.strokeStyle = chalks[ colorIdx ].color;
Marc Kupietz09b75752023-10-07 09:32:19 +0200906
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200907 var opacity = 1.0;
908 context.strokeStyle = context.strokeStyle.replace( /[\d\.]+\)$/g, opacity + ')' );
909 context.beginPath();
910 context.moveTo( fromX, fromY );
911 context.lineTo( toX, toY );
912 context.stroke();
913 // Chalk Effect
914 var length = Math.round( Math.sqrt( Math.pow( toX - fromX, 2 ) + Math.pow( toY - fromY, 2 ) ) / ( 5 / brushDiameter ) );
915 var xUnit = ( toX - fromX ) / length;
916 var yUnit = ( toY - fromY ) / length;
917 for ( var i = 0; i < length; i++ ) {
918 if ( chalkEffect > ( Math.random() * 0.9 ) ) {
919 var xCurrent = fromX + ( i * xUnit );
920 var yCurrent = fromY + ( i * yUnit );
921 var xRandom = xCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
922 var yRandom = yCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
923 context.clearRect( xRandom, yRandom, Math.random() * 2 + 2, Math.random() + 1 );
924 }
925 }
926 }
927
928 function eraseWithSponge( context, x, y ) {
929 context.save();
930 context.beginPath();
Marc Kupietz09b75752023-10-07 09:32:19 +0200931 context.arc( x + eraser.radius, y + eraser.radius, eraser.radius, 0, 2 * Math.PI, false );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200932 context.clip();
Marc Kupietz09b75752023-10-07 09:32:19 +0200933 context.clearRect( x - 1, y - 1, eraser.radius * 2 + 2, eraser.radius * 2 + 2 );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200934 context.restore();
935 if ( mode == 1 && grid ) {
Marc Kupietz09b75752023-10-07 09:32:19 +0200936 redrawGrid( x + eraser.radius, y + eraser.radius, eraser.radius );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200937 }
938 }
939
940
941 /**
942 * Show an overlay for the chalkboard.
943 */
944 function showChalkboard() {
945//console.log("showChalkboard");
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200946 drawingCanvas[ 1 ].container.style.opacity = 1;
947 drawingCanvas[ 1 ].container.style.visibility = 'visible';
948 mode = 1;
949 }
950
951
952 /**
953 * Closes open chalkboard.
954 */
955 function closeChalkboard() {
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200956 drawingCanvas[ 1 ].container.style.opacity = 0;
957 drawingCanvas[ 1 ].container.style.visibility = 'hidden';
958 lastX = null;
959 lastY = null;
960 mode = 0;
961 }
962
963 /**
964 * Clear current canvas.
965 */
966 function clearCanvas( id ) {
967 if ( id == 0 ) clearTimeout( slidechangeTimeout );
968 drawingCanvas[ id ].context.clearRect( 0, 0, drawingCanvas[ id ].width, drawingCanvas[ id ].height );
969 if ( id == 1 && grid ) drawGrid();
970 }
971
972 /**
973 * Draw grid on background
974 */
975 function drawGrid() {
976 var context = drawingCanvas[ 1 ].context;
977
978 drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
979 drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
980 drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
981
982 var scale = drawingCanvas[ 1 ].scale;
983 var xOffset = drawingCanvas[ 1 ].xOffset;
984 var yOffset = drawingCanvas[ 1 ].yOffset;
985
986 var distance = grid.distance * scale;
987
988 var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
989 for ( var x = fromX; x < drawingCanvas[ 1 ].width; x += distance ) {
990 context.beginPath();
991 context.lineWidth = grid.width * scale;
992 context.lineCap = 'round';
993 context.fillStyle = grid.color;
994 context.strokeStyle = grid.color;
995 context.moveTo( x, 0 );
996 context.lineTo( x, drawingCanvas[ 1 ].height );
997 context.stroke();
998 }
999 var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
1000
1001 for ( var y = fromY; y < drawingCanvas[ 1 ].height; y += distance ) {
1002 context.beginPath();
1003 context.lineWidth = grid.width * scale;
1004 context.lineCap = 'round';
1005 context.fillStyle = grid.color;
1006 context.strokeStyle = grid.color;
1007 context.moveTo( 0, y );
1008 context.lineTo( drawingCanvas[ 1 ].width, y );
1009 context.stroke();
1010 }
1011 }
1012
1013 function redrawGrid( centerX, centerY, diameter ) {
1014 var context = drawingCanvas[ 1 ].context;
1015
1016 drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
1017 drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
1018 drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
1019
1020 var scale = drawingCanvas[ 1 ].scale;
1021 var xOffset = drawingCanvas[ 1 ].xOffset;
1022 var yOffset = drawingCanvas[ 1 ].yOffset;
1023
1024 var distance = grid.distance * scale;
1025
1026 var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
1027
1028 for ( var x = fromX + distance * Math.ceil( ( centerX - diameter - fromX ) / distance ); x <= fromX + distance * Math.floor( ( centerX + diameter - fromX ) / distance ); x += distance ) {
1029 context.beginPath();
1030 context.lineWidth = grid.width * scale;
1031 context.lineCap = 'round';
1032 context.fillStyle = grid.color;
1033 context.strokeStyle = grid.color;
1034 context.moveTo( x, centerY - Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
1035 context.lineTo( x, centerY + Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
1036 context.stroke();
1037 }
1038 var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
1039 for ( var y = fromY + distance * Math.ceil( ( centerY - diameter - fromY ) / distance ); y <= fromY + distance * Math.floor( ( centerY + diameter - fromY ) / distance ); y += distance ) {
1040 context.beginPath();
1041 context.lineWidth = grid.width * scale;
1042 context.lineCap = 'round';
1043 context.fillStyle = grid.color;
1044 context.strokeStyle = grid.color;
1045 context.moveTo( centerX - Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
1046 context.lineTo( centerX + Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
1047 context.stroke();
1048 }
1049 }
1050
1051 /**
1052 * Set the color
1053 */
Marc Kupietz09b75752023-10-07 09:32:19 +02001054 function setColor( index, record ) {
1055 // protect against out of bounds (this could happen when
1056 // replaying events recorded with different color settings).
1057 if ( index >= pens[ mode ].length ) index = 0;
1058
1059 color[ mode ] = index;
1060
1061 if ( color[ mode ] < 0 ) {
1062 // use eraser
1063 changeCursor( drawingCanvas[ mode ].canvas, sponge );
1064 }
1065 else {
1066 changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
1067 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001068 }
1069
1070 /**
1071 * Set the board
1072 */
1073 function selectBoard( boardIdx, record ) {
1074//console.log("Set board",boardIdx);
1075 if ( board == boardIdx ) return;
1076
1077 board = boardIdx;
1078 redrawChalkboard( boardIdx );
1079 if ( record ) {
1080 recordEvent( { type: 'selectboard' } );
1081 }
1082 }
1083
1084 function redrawChalkboard( boardIdx ) {
1085 clearCanvas( 1 );
1086 var slideData = getSlideData( slideIndices, 1 );
1087 var index = 0;
1088 var play = ( boardIdx == 0 );
1089 while ( index < slideData.events.length && slideData.events[ index ].time < Date.now() - slideStart ) {
1090 if ( boardIdx == slideData.events[ index ].board ) {
1091 playEvent( 1, slideData.events[ index ], Date.now() - slideStart );
1092 }
1093
1094 index++;
1095 }
1096 }
1097
1098
1099 /**
1100 * Forward cycle color
1101 */
1102 function cycleColorNext() {
1103 color[ mode ] = ( color[ mode ] + 1 ) % pens[ mode ].length;
1104 return color[ mode ];
1105 }
1106
1107 /**
1108 * Backward cycle color
1109 */
1110 function cycleColorPrev() {
1111 color[ mode ] = ( color[ mode ] + ( pens[ mode ].length - 1 ) ) % pens[ mode ].length;
1112 return color[ mode ];
1113 }
1114
1115/*****************************************************************
1116 ** Broadcast
1117 ******************************************************************/
1118
1119 var eventQueue = [];
1120
1121 document.addEventListener( 'received', function ( message ) {
1122 if ( message.content && message.content.sender == 'chalkboard-plugin' ) {
1123 // add message to queue
1124 eventQueue.push( message );
1125 console.log( JSON.stringify( message ) );
1126 }
1127 if ( eventQueue.length == 1 ) processQueue();
1128 } );
1129
1130 function processQueue() {
1131 // take first message from queue
1132 var message = eventQueue.shift();
1133
1134 // synchronize time with seminar host
1135 slideStart = Date.now() - message.content.timestamp;
1136 // set status
1137 if ( mode < message.content.mode ) {
1138 // open chalkboard
1139 showChalkboard();
1140 } else if ( mode > message.content.mode ) {
1141 // close chalkboard
1142 closeChalkboard();
1143 }
1144 if ( board != message.content.board ) {
1145 board = message.content.board;
1146 redrawChalkboard( board );
1147 };
1148
1149 switch ( message.content.type ) {
1150 case 'showChalkboard':
1151 showChalkboard();
1152 break;
1153 case 'closeChalkboard':
1154 closeChalkboard();
1155 break;
1156 case 'erase':
1157 erasePoint( message.content.x, message.content.y );
1158 break;
1159 case 'draw':
1160 drawSegment( message.content.fromX, message.content.fromY, message.content.toX, message.content.toY, message.content.color );
1161 break;
1162 case 'clear':
1163 clearSlide();
1164 break;
1165 case 'selectboard':
1166 selectBoard( message.content.board, true );
1167 break;
1168 case 'resetSlide':
1169 resetSlideDrawings();
1170 break;
1171 case 'init':
1172 storage = message.content.storage;
1173 for ( var id = 0; id < 2; id++ ) {
1174 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
1175 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
1176 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
1177 }
1178 clearCanvas( 0 );
1179 clearCanvas( 1 );
1180 if ( !playback ) {
1181 slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
1182 }
1183 if ( mode == 1 && message.content.mode == 0 ) {
1184 setTimeout( closeChalkboard, transition + 50 );
1185 }
1186 if ( mode == 0 && message.content.mode == 1 ) {
1187 setTimeout( showChalkboard, transition + 50 );
1188 }
1189 mode = message.content.mode;
1190 board = message.content.board;
1191 break;
1192 default:
1193 break;
1194 }
1195
1196 // continue with next message if queued
1197 if ( eventQueue.length > 0 ) {
1198 processQueue();
1199 } else {
1200 storageChanged();
1201 }
1202 }
1203
1204 document.addEventListener( 'welcome', function ( user ) {
1205 // broadcast storage
1206 var message = new CustomEvent( messageType );
1207 message.content = {
1208 sender: 'chalkboard-plugin',
1209 recipient: user.id,
1210 type: 'init',
1211 timestamp: Date.now() - slideStart,
1212 storage: storage,
1213 mode,
1214 board
1215 };
1216 document.dispatchEvent( message );
1217 } );
1218
1219 /*****************************************************************
1220 ** Playback
1221 ******************************************************************/
1222
1223 document.addEventListener( 'seekplayback', function ( event ) {
1224//console.log('event seekplayback ' + event.timestamp);
1225 stopPlayback();
1226 if ( !playback || event.timestamp == 0 ) {
1227 // in other cases startplayback fires after seeked
1228 startPlayback( event.timestamp );
1229 }
1230 //console.log('seeked');
1231 } );
1232
1233
1234 document.addEventListener( 'startplayback', function ( event ) {
1235//console.log('event startplayback ' + event.timestamp);
1236 stopPlayback();
1237 playback = true;
1238 startPlayback( event.timestamp );
1239 } );
1240
1241 document.addEventListener( 'stopplayback', function ( event ) {
1242//console.log('event stopplayback ' + (Date.now() - slideStart) );
1243 playback = false;
1244 stopPlayback();
1245 } );
1246
1247 document.addEventListener( 'startrecording', function ( event ) {
1248//console.log('event startrecording ' + event.timestamp);
1249 startRecording();
1250 } );
1251
1252
1253 function startRecording() {
1254 resetSlide( true );
1255 slideStart = Date.now();
1256 }
1257
1258 function startPlayback( timestamp, finalMode ) {
1259//console.log("playback " + timestamp );
1260 slideStart = Date.now() - timestamp;
1261 closeChalkboard();
1262 mode = 0;
1263 board = 0;
1264 for ( var id = 0; id < 2; id++ ) {
1265 clearCanvas( id );
1266 var slideData = getSlideData( slideIndices, id );
1267//console.log( timestamp +" / " + JSON.stringify(slideData));
1268 var index = 0;
1269 while ( index < slideData.events.length && slideData.events[ index ].time < ( Date.now() - slideStart ) ) {
1270 playEvent( id, slideData.events[ index ], timestamp );
1271 index++;
1272 }
1273
1274 while ( playback && index < slideData.events.length ) {
1275 timeouts[ id ].push( setTimeout( playEvent, slideData.events[ index ].time - ( Date.now() - slideStart ), id, slideData.events[ index ], timestamp ) );
1276 index++;
1277 }
1278 }
1279//console.log("Mode: " + finalMode + "/" + mode );
1280 if ( finalMode != undefined ) {
1281 mode = finalMode;
1282 }
1283 if ( mode == 1 ) showChalkboard();
1284//console.log("playback (ok)");
1285
1286 };
1287
1288 function stopPlayback() {
1289//console.log("stopPlayback");
1290//console.log("Timeouts: " + timeouts[0].length + "/"+ timeouts[1].length);
1291 for ( var id = 0; id < 2; id++ ) {
1292 for ( var i = 0; i < timeouts[ id ].length; i++ ) {
1293 clearTimeout( timeouts[ id ][ i ] );
1294 }
1295 timeouts[ id ] = [];
1296 }
1297 };
1298
1299 function playEvent( id, event, timestamp ) {
1300//console.log( timestamp +" / " + JSON.stringify(event));
1301//console.log( id + ": " + timestamp +" / " + event.time +" / " + event.type +" / " + mode );
1302 switch ( event.type ) {
1303 case 'open':
1304 if ( timestamp <= event.time ) {
1305 showChalkboard();
1306 } else {
1307 mode = 1;
1308 }
1309
1310 break;
1311 case 'close':
1312 if ( timestamp < event.time ) {
1313 closeChalkboard();
1314 } else {
1315 mode = 0;
1316 }
1317 break;
1318 case 'clear':
1319 clearCanvas( id );
1320 break;
1321 case 'selectboard':
1322 selectBoard( event.board );
1323 break;
1324 case 'draw':
1325 drawLine( id, event, timestamp );
1326 break;
1327 case 'erase':
1328 eraseCircle( id, event, timestamp );
1329 break;
1330 }
1331 };
1332
1333 function drawLine( id, event, timestamp ) {
1334 var ctx = drawingCanvas[ id ].context;
1335 var scale = drawingCanvas[ id ].scale;
1336 var xOffset = drawingCanvas[ id ].xOffset;
1337 var yOffset = drawingCanvas[ id ].yOffset;
1338 draw[ id ]( ctx, xOffset + event.x1 * scale, yOffset + event.y1 * scale, xOffset + event.x2 * scale, yOffset + event.y2 * scale, event.color );
1339 };
1340
1341 function eraseCircle( id, event, timestamp ) {
1342 var ctx = drawingCanvas[ id ].context;
1343 var scale = drawingCanvas[ id ].scale;
1344 var xOffset = drawingCanvas[ id ].xOffset;
1345 var yOffset = drawingCanvas[ id ].yOffset;
1346
1347 eraseWithSponge( ctx, xOffset + event.x * scale, yOffset + event.y * scale );
1348 };
1349
1350 function startErasing( x, y ) {
1351 drawing = false;
1352 erasing = true;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001353 erasePoint( x, y );
1354 }
1355
1356 function erasePoint( x, y ) {
1357 var ctx = drawingCanvas[ mode ].context;
1358 var scale = drawingCanvas[ mode ].scale;
1359 var xOffset = drawingCanvas[ mode ].xOffset;
1360 var yOffset = drawingCanvas[ mode ].yOffset;
1361
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001362 recordEvent( {
1363 type: 'erase',
1364 x,
1365 y
1366 } );
1367
1368 if (
1369 x * scale + xOffset > 0 &&
1370 y * scale + yOffset > 0 &&
1371 x * scale + xOffset < drawingCanvas[ mode ].width &&
1372 y * scale + yOffset < drawingCanvas[ mode ].height
1373 ) {
1374 eraseWithSponge( ctx, x * scale + xOffset, y * scale + yOffset );
1375 }
1376 }
1377
1378 function stopErasing() {
1379 erasing = false;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001380 }
1381
1382 function startDrawing( x, y ) {
1383 drawing = true;
1384
1385 var ctx = drawingCanvas[ mode ].context;
1386 var scale = drawingCanvas[ mode ].scale;
1387 var xOffset = drawingCanvas[ mode ].xOffset;
1388 var yOffset = drawingCanvas[ mode ].yOffset;
1389 lastX = x * scale + xOffset;
1390 lastY = y * scale + yOffset;
1391 }
1392
1393 function drawSegment( fromX, fromY, toX, toY, colorIdx ) {
1394 var ctx = drawingCanvas[ mode ].context;
1395 var scale = drawingCanvas[ mode ].scale;
1396 var xOffset = drawingCanvas[ mode ].xOffset;
1397 var yOffset = drawingCanvas[ mode ].yOffset;
1398
1399 recordEvent( {
1400 type: 'draw',
1401 color: colorIdx,
1402 x1: fromX,
1403 y1: fromY,
1404 x2: toX,
1405 y2: toY
1406 } );
1407
1408 if (
1409 fromX * scale + xOffset > 0 &&
1410 fromY * scale + yOffset > 0 &&
1411 fromX * scale + xOffset < drawingCanvas[ mode ].width &&
1412 fromY * scale + yOffset < drawingCanvas[ mode ].height &&
1413 toX * scale + xOffset > 0 &&
1414 toY * scale + yOffset > 0 &&
1415 toX * scale + xOffset < drawingCanvas[ mode ].width &&
1416 toY * scale + yOffset < drawingCanvas[ mode ].height
1417 ) {
1418 draw[ mode ]( ctx, fromX * scale + xOffset, fromY * scale + yOffset, toX * scale + xOffset, toY * scale + yOffset, colorIdx );
1419 }
1420 }
1421
1422 function stopDrawing() {
1423 drawing = false;
1424 }
1425
1426
1427/*****************************************************************
1428 ** User interface
1429 ******************************************************************/
1430
1431 function setupCanvasEvents( canvas ) {
1432// TODO: check all touchevents
1433 canvas.addEventListener( 'touchstart', function ( evt ) {
1434 evt.preventDefault();
1435//console.log("Touch start");
1436 if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
1437 var scale = drawingCanvas[ mode ].scale;
1438 var xOffset = drawingCanvas[ mode ].xOffset;
1439 var yOffset = drawingCanvas[ mode ].yOffset;
1440
1441 var touch = evt.touches[ 0 ];
1442 mouseX = touch.pageX;
1443 mouseY = touch.pageY;
Marc Kupietz09b75752023-10-07 09:32:19 +02001444 if ( color[ mode ] < 0 ) {
1445 startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale);
1446 }
1447 else {
1448 startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1449 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001450 }
1451 }, passiveSupported ? {
1452 passive: false
1453 } : false );
1454
1455 canvas.addEventListener( 'touchmove', function ( evt ) {
1456 evt.preventDefault();
1457//console.log("Touch move");
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001458 if ( drawing || erasing ) {
1459 var scale = drawingCanvas[ mode ].scale;
1460 var xOffset = drawingCanvas[ mode ].xOffset;
1461 var yOffset = drawingCanvas[ mode ].yOffset;
1462
1463 var touch = evt.touches[ 0 ];
1464 mouseX = touch.pageX;
1465 mouseY = touch.pageY;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001466
1467 if ( drawing ) {
1468 drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
1469 // broadcast
1470 var message = new CustomEvent( messageType );
1471 message.content = {
1472 sender: 'chalkboard-plugin',
1473 type: 'draw',
1474 timestamp: Date.now() - slideStart,
1475 mode,
1476 board,
1477 fromX: ( lastX - xOffset ) / scale,
1478 fromY: ( lastY - yOffset ) / scale,
1479 toX: ( mouseX - xOffset ) / scale,
1480 toY: ( mouseY - yOffset ) / scale,
1481 color: color[ mode ]
1482 };
1483 document.dispatchEvent( message );
1484
1485 lastX = mouseX;
1486 lastY = mouseY;
1487 } else {
1488 erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1489 // broadcast
1490 var message = new CustomEvent( messageType );
1491 message.content = {
1492 sender: 'chalkboard-plugin',
1493 type: 'erase',
1494 timestamp: Date.now() - slideStart,
1495 mode,
1496 board,
1497 x: ( mouseX - xOffset ) / scale,
1498 y: ( mouseY - yOffset ) / scale
1499 };
1500 document.dispatchEvent( message );
1501 }
1502
1503 }
1504 }, false );
1505
1506
1507 canvas.addEventListener( 'touchend', function ( evt ) {
1508 evt.preventDefault();
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001509 stopDrawing();
Marc Kupietz09b75752023-10-07 09:32:19 +02001510 stopErasing();
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001511 }, false );
1512
1513 canvas.addEventListener( 'mousedown', function ( evt ) {
1514 evt.preventDefault();
1515 if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
1516//console.log( "mousedown: " + evt.button );
1517 var scale = drawingCanvas[ mode ].scale;
1518 var xOffset = drawingCanvas[ mode ].xOffset;
1519 var yOffset = drawingCanvas[ mode ].yOffset;
1520
1521 mouseX = evt.pageX;
1522 mouseY = evt.pageY;
1523
Marc Kupietz09b75752023-10-07 09:32:19 +02001524 if ( color[ mode ] < 0 || evt.button == 2 || evt.button == 1 ) {
1525 if ( color[ mode ] >= 0 ) {
1526 // show sponge
1527 changeCursor( drawingCanvas[ mode ].canvas, sponge );
1528 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001529 startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1530 // broadcast
1531 var message = new CustomEvent( messageType );
1532 message.content = {
1533 sender: 'chalkboard-plugin',
1534 type: 'erase',
1535 timestamp: Date.now() - slideStart,
1536 mode,
1537 board,
1538 x: ( mouseX - xOffset ) / scale,
1539 y: ( mouseY - yOffset ) / scale
1540 };
1541 document.dispatchEvent( message );
1542 } else {
1543 startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1544 }
1545 }
1546 } );
1547
1548 canvas.addEventListener( 'mousemove', function ( evt ) {
1549 evt.preventDefault();
1550//console.log("Mouse move");
Marc Kupietz09b75752023-10-07 09:32:19 +02001551
1552 var scale = drawingCanvas[ mode ].scale;
1553 var xOffset = drawingCanvas[ mode ].xOffset;
1554 var yOffset = drawingCanvas[ mode ].yOffset;
1555
1556 mouseX = evt.pageX;
1557 mouseY = evt.pageY;
1558
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001559 if ( drawing || erasing ) {
1560 var scale = drawingCanvas[ mode ].scale;
1561 var xOffset = drawingCanvas[ mode ].xOffset;
1562 var yOffset = drawingCanvas[ mode ].yOffset;
1563
1564 mouseX = evt.pageX;
1565 mouseY = evt.pageY;
1566
1567 if ( drawing ) {
1568 drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
1569 // broadcast
1570 var message = new CustomEvent( messageType );
1571 message.content = {
1572 sender: 'chalkboard-plugin',
1573 type: 'draw',
1574 timestamp: Date.now() - slideStart,
1575 mode,
1576 board,
1577 fromX: ( lastX - xOffset ) / scale,
1578 fromY: ( lastY - yOffset ) / scale,
1579 toX: ( mouseX - xOffset ) / scale,
1580 toY: ( mouseY - yOffset ) / scale,
1581 color: color[ mode ]
1582 };
1583 document.dispatchEvent( message );
1584
1585 lastX = mouseX;
1586 lastY = mouseY;
1587 } else {
1588 erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1589 // broadcast
1590 var message = new CustomEvent( messageType );
1591 message.content = {
1592 sender: 'chalkboard-plugin',
1593 type: 'erase',
1594 timestamp: Date.now() - slideStart,
1595 mode,
1596 board,
1597 x: ( mouseX - xOffset ) / scale,
1598 y: ( mouseY - yOffset ) / scale
1599 };
1600 document.dispatchEvent( message );
1601 }
1602
1603 }
1604 } );
1605
1606
1607 canvas.addEventListener( 'mouseup', function ( evt ) {
1608 evt.preventDefault();
Marc Kupietz09b75752023-10-07 09:32:19 +02001609 if ( color[ mode ] >= 0 ) {
1610 changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
1611 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001612 if ( drawing || erasing ) {
1613 stopDrawing();
1614 stopErasing();
1615 }
1616 } );
1617 }
1618
1619 function resize() {
1620//console.log("resize");
1621 // Resize the canvas and draw everything again
1622 var timestamp = Date.now() - slideStart;
1623 if ( !playback ) {
1624 timestamp = getSlideDuration();
1625 }
1626
1627//console.log( drawingCanvas[0].scale + "/" + drawingCanvas[0].xOffset + "/" +drawingCanvas[0].yOffset );
1628 for ( var id = 0; id < 2; id++ ) {
1629 drawingCanvas[ id ].width = window.innerWidth;
1630 drawingCanvas[ id ].height = window.innerHeight;
1631 drawingCanvas[ id ].canvas.width = drawingCanvas[ id ].width;
1632 drawingCanvas[ id ].canvas.height = drawingCanvas[ id ].height;
1633 drawingCanvas[ id ].context.canvas.width = drawingCanvas[ id ].width;
1634 drawingCanvas[ id ].context.canvas.height = drawingCanvas[ id ].height;
1635
1636 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
1637 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
1638 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
1639//console.log( drawingCanvas[id].scale + "/" + drawingCanvas[id].xOffset + "/" +drawingCanvas[id].yOffset );
1640 }
1641//console.log( window.innerWidth + "/" + window.innerHeight);
1642 startPlayback( timestamp, mode, true );
1643 }
1644
1645 Reveal.addEventListener( 'pdf-ready', function ( evt ) {
1646// console.log( "Create printouts when ready" );
1647 whenLoaded( createPrintout );
1648 });
1649
1650 Reveal.addEventListener( 'ready', function ( evt ) {
1651//console.log('ready');
1652 if ( !printMode ) {
1653 window.addEventListener( 'resize', resize );
1654
1655 slideStart = Date.now() - getSlideDuration();
1656 slideIndices = Reveal.getIndices();
1657 if ( !playback ) {
1658 startPlayback( getSlideDuration(), 0 );
1659 }
1660 if ( Reveal.isAutoSliding() ) {
1661 var event = new CustomEvent( 'startplayback' );
1662 event.timestamp = 0;
1663 document.dispatchEvent( event );
1664 }
1665 updateStorage();
1666 whenReady( addPageNumbers );
1667 }
1668 } );
1669 Reveal.addEventListener( 'slidechanged', function ( evt ) {
1670// clearTimeout( slidechangeTimeout );
1671//console.log('slidechanged');
1672 if ( !printMode ) {
1673 slideStart = Date.now() - getSlideDuration();
1674 slideIndices = Reveal.getIndices();
1675 closeChalkboard();
1676 board = 0;
1677 clearCanvas( 0 );
1678 clearCanvas( 1 );
1679 if ( !playback ) {
1680 slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
1681 }
1682 if ( Reveal.isAutoSliding() ) {
1683 var event = new CustomEvent( 'startplayback' );
1684 event.timestamp = 0;
1685 document.dispatchEvent( event );
1686 }
1687 }
1688 } );
1689 Reveal.addEventListener( 'fragmentshown', function ( evt ) {
1690// clearTimeout( slidechangeTimeout );
1691//console.log('fragmentshown');
1692 if ( !printMode ) {
1693 slideStart = Date.now() - getSlideDuration();
1694 slideIndices = Reveal.getIndices();
1695 closeChalkboard();
1696 board = 0;
1697 clearCanvas( 0 );
1698 clearCanvas( 1 );
1699 if ( Reveal.isAutoSliding() ) {
1700 var event = new CustomEvent( 'startplayback' );
1701 event.timestamp = 0;
1702 document.dispatchEvent( event );
1703 } else if ( !playback ) {
1704 startPlayback( getSlideDuration(), 0 );
1705// closeChalkboard();
1706 }
1707 }
1708 } );
1709 Reveal.addEventListener( 'fragmenthidden', function ( evt ) {
1710// clearTimeout( slidechangeTimeout );
1711//console.log('fragmenthidden');
1712 if ( !printMode ) {
1713 slideStart = Date.now() - getSlideDuration();
1714 slideIndices = Reveal.getIndices();
1715 closeChalkboard();
1716 board = 0;
1717 clearCanvas( 0 );
1718 clearCanvas( 1 );
1719 if ( Reveal.isAutoSliding() ) {
1720 document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
1721 } else if ( !playback ) {
1722 startPlayback( getSlideDuration() );
1723 closeChalkboard();
1724 }
1725 }
1726 } );
1727
1728 Reveal.addEventListener( 'autoslideresumed', function ( evt ) {
1729//console.log('autoslideresumed');
1730 var event = new CustomEvent( 'startplayback' );
1731 event.timestamp = 0;
1732 document.dispatchEvent( event );
1733 } );
1734 Reveal.addEventListener( 'autoslidepaused', function ( evt ) {
1735//console.log('autoslidepaused');
1736 document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
1737
1738 // advance to end of slide
1739// closeChalkboard();
1740 startPlayback( getSlideDuration(), 0 );
1741 } );
1742
1743 function toggleNotesCanvas() {
1744 if ( !readOnly ) {
1745 if ( mode == 1 ) {
1746 toggleChalkboard();
1747 notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
1748 notescanvas.style.pointerEvents = 'auto';
1749 }
1750 else {
1751 if ( notescanvas.style.pointerEvents != 'none' ) {
1752 // hide notes canvas
1753 if ( colorButtons ) {
1754 notescanvas.querySelector( '.palette' ).style.visibility = 'hidden';
1755 }
1756 notescanvas.style.background = 'rgba(0,0,0,0)';
1757 notescanvas.style.pointerEvents = 'none';
1758 }
1759 else {
1760 // show notes canvas
1761 if ( colorButtons ) {
1762 notescanvas.querySelector( '.palette' ).style.visibility = 'visible';
1763 }
1764 notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
1765 notescanvas.style.pointerEvents = 'auto';
1766
1767 var idx = 0;
1768 if ( color[ mode ] ) {
1769 idx = color[ mode ];
1770 }
1771
1772 setColor( idx, true );
1773 }
1774 }
1775 }
1776 };
1777
1778 function toggleChalkboard() {
1779//console.log("toggleChalkboard " + mode);
1780 if ( mode == 1 ) {
1781 if ( !readOnly ) {
1782 recordEvent( { type: 'close' } );
1783 }
1784 closeChalkboard();
1785
1786 // broadcast
1787 var message = new CustomEvent( messageType );
1788 message.content = {
1789 sender: 'chalkboard-plugin',
1790 type: 'closeChalkboard',
1791 timestamp: Date.now() - slideStart,
1792 mode: 0,
1793 board
1794 };
1795 document.dispatchEvent( message );
1796
1797
1798 } else {
1799 showChalkboard();
1800 if ( !readOnly ) {
1801 recordEvent( { type: 'open' } );
1802 // broadcast
1803 var message = new CustomEvent( messageType );
1804 message.content = {
1805 sender: 'chalkboard-plugin',
1806 type: 'showChalkboard',
1807 timestamp: Date.now() - slideStart,
1808 mode: 1,
1809 board
1810 };
1811 document.dispatchEvent( message );
1812
1813 var idx = 0;
1814
1815 if ( rememberColor[ mode ] ) {
1816 idx = color[ mode ];
1817 }
1818
1819 setColor( idx, true );
1820 }
1821 }
1822 };
1823
1824 function clearSlide() {
1825 recordEvent( { type: 'clear' } );
1826 clearCanvas( mode );
1827 }
1828
1829 function clear() {
1830 if ( !readOnly ) {
1831 clearSlide();
1832 // broadcast
1833 var message = new CustomEvent( messageType );
1834 message.content = {
1835 sender: 'chalkboard-plugin',
1836 type: 'clear',
1837 timestamp: Date.now() - slideStart,
1838 mode,
1839 board
1840 };
1841 document.dispatchEvent( message );
1842 }
1843 };
1844
1845 function colorIndex( idx ) {
1846 if ( !readOnly ) {
1847 setColor( idx, true );
1848 }
1849 }
1850
1851 function colorNext() {
1852 if ( !readOnly ) {
1853 let idx = cycleColorNext();
1854 setColor( idx, true );
1855 }
1856 }
1857
1858 function colorPrev() {
1859 if ( !readOnly ) {
1860 let idx = cycleColorPrev();
1861 setColor( idx, true );
1862 }
1863 }
1864
1865 function resetSlideDrawings() {
1866 slideStart = Date.now();
1867 closeChalkboard();
1868
1869 clearCanvas( 0 );
1870 clearCanvas( 1 );
1871
1872 mode = 1;
1873 var slideData = getSlideData();
1874 slideData.duration = 0;
1875 slideData.events = [];
1876 mode = 0;
1877 var slideData = getSlideData();
1878 slideData.duration = 0;
1879 slideData.events = [];
1880
1881 updateStorage();
1882 }
1883
1884 function resetSlide( force ) {
1885 var ok = force || confirm( "Please confirm to delete chalkboard drawings on this slide!" );
1886 if ( ok ) {
1887//console.log("resetSlide ");
1888 stopPlayback();
1889 resetSlideDrawings();
1890 // broadcast
1891 var message = new CustomEvent( messageType );
1892 message.content = {
1893 sender: 'chalkboard-plugin',
1894 type: 'resetSlide',
1895 timestamp: Date.now() - slideStart,
1896 mode,
1897 board
1898 };
1899 document.dispatchEvent( message );
1900 }
1901 };
1902
1903 function resetStorage( force ) {
1904 var ok = force || confirm( "Please confirm to delete all chalkboard drawings!" );
1905 if ( ok ) {
1906 stopPlayback();
1907 slideStart = Date.now();
1908 clearCanvas( 0 );
1909 clearCanvas( 1 );
1910 if ( mode == 1 ) {
1911 closeChalkboard();
1912 }
1913
1914 storage = [ {
1915 width: Reveal.getConfig().width,
1916 height: Reveal.getConfig().height,
1917 data: []
1918 },
1919 {
1920 width: Reveal.getConfig().width,
1921 height: Reveal.getConfig().height,
1922 data: []
1923 }
1924 ];
1925
1926 if ( config.storage ) {
1927 sessionStorage.setItem( config.storage, null )
1928 }
1929 // broadcast
1930 var message = new CustomEvent( messageType );
1931 message.content = {
1932 sender: 'chalkboard-plugin',
1933 type: 'init',
1934 timestamp: Date.now() - slideStart,
1935 storage,
1936 mode,
1937 board
1938 };
1939 document.dispatchEvent( message );
1940 }
1941 };
1942
1943 this.toggleNotesCanvas = toggleNotesCanvas;
1944 this.toggleChalkboard = toggleChalkboard;
1945 this.colorIndex = colorIndex;
1946 this.colorNext = colorNext;
1947 this.colorPrev = colorPrev;
1948 this.clear = clear;
1949 this.reset = resetSlide;
1950 this.resetAll = resetStorage;
1951 this.download = downloadData;
1952 this.updateStorage = updateStorage;
1953 this.getData = getData;
1954 this.configure = configure;
1955
1956
1957 for ( var key in keyBindings ) {
1958 if ( keyBindings[ key ] ) {
1959 Reveal.addKeyBinding( keyBindings[ key ], RevealChalkboard[ key ] );
1960 }
1961 };
1962
1963 return this;
1964};