blob: 2672110fd2ca362e5eb7adf65ff368813c475f10 [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 Kupietz9c036a42024-05-14 13:17:25 +02006 ** Version: 2.3.3
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 ) {
Marc Kupietz9c036a42024-05-14 13:17:25 +0200666 // https://stackoverflow.com/a/6234804
667 // escape data for proper handling of quotes and line breaks
668 // in case malicious user gets a chance to craft the exception message
669 error = String(error)
670 .replace(/&/g, "&amp;")
671 .replace(/</g, "&lt;")
672 .replace(/>/g, "&gt;")
673 .replace(/"/g, "&quot;")
674 .replace(/'/g, "&#039;");
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200675 a.innerHTML += ' (' + error + ')';
676 }
677 a.click();
678 document.body.removeChild( a );
679 }
680
681 /**
682 * Returns data object for the slide with the given indices.
683 */
684 function getSlideData( indices, id ) {
685 if ( id == undefined ) id = mode;
686 if ( !indices ) indices = slideIndices;
687 var data;
688 for ( var i = 0; i < storage[ id ].data.length; i++ ) {
689 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 ) {
690 data = storage[ id ].data[ i ];
691 return data;
692 }
693 }
694 var page = Number( Reveal.getCurrentSlide().getAttribute('data-pdf-page-number') );
695//console.log( indices, Reveal.getCurrentSlide() );
696 storage[ id ].data.push( {
697 slide: indices,
698 page,
699 events: [],
700 duration: 0
701 } );
702 data = storage[ id ].data[ storage[ id ].data.length - 1 ];
703 return data;
704 }
705
706 /**
707 * Returns maximum duration of slide playback for both modes
708 */
709 function getSlideDuration( indices ) {
710 if ( !indices ) indices = slideIndices;
711 var duration = 0;
712 for ( var id = 0; id < 2; id++ ) {
713 for ( var i = 0; i < storage[ id ].data.length; i++ ) {
714 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 ) {
715 duration = Math.max( duration, storage[ id ].data[ i ].duration );
716 break;
717 }
718 }
719 }
720//console.log( duration );
721 return duration;
722 }
723
724/*****************************************************************
725 ** Print
726 ******************************************************************/
727 var printMode = ( /print-pdf/gi ).test( window.location.search );
728//console.log("createPrintout" + printMode)
729
730 function addPageNumbers() {
731 // determine page number for printouts with fragments serialised
732 var slides = Reveal.getSlides();
733 var page = 0;
734 for ( var i=0; i < slides.length; i++) {
735 slides[i].setAttribute('data-pdf-page-number',page.toString());
736 // add number of fragments without fragment indices
737 var count = slides[i].querySelectorAll('.fragment:not([data-fragment-index])').length;
738 var fragments = slides[i].querySelectorAll('.fragment[data-fragment-index]');
739 for ( var j=0; j < fragments.length; j++) {
740 // increasenumber of fragments by highest fragment index (which start at 0)
741 if ( Number(fragments[j].getAttribute('data-fragment-index')) + 1 > count ) {
742 count = Number(fragments[j].getAttribute('data-fragment-index')) + 1;
743 }
744 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200745 page += count + 1;
746 }
747 }
748
749 function createPrintout() {
750 //console.warn(Reveal.getTotalSlides(),Reveal.getSlidesElement());
751 if ( storage[ 1 ].data.length == 0 ) return;
752 console.log( 'Create printout(s) for ' + storage[ 1 ].data.length + " slides" );
753 drawingCanvas[ 0 ].container.style.opacity = 0; // do not print notes canvas
754 drawingCanvas[ 0 ].container.style.visibility = 'hidden';
755
756 var patImg = new Image();
757 patImg.onload = function () {
758 var slides = Reveal.getSlides();
759//console.log(slides);
760 for ( var i = storage[ 1 ].data.length - 1; i >= 0; i-- ) {
761 console.log( 'Create printout for slide ' + storage[ 1 ].data[ i ].slide.h + '.' + storage[ 1 ].data[ i ].slide.v );
762 var slideData = getSlideData( storage[ 1 ].data[ i ].slide, 1 );
763 var drawings = createDrawings( slideData, patImg );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200764 addDrawings( slides[storage[ 1 ].data[ i ].page], drawings );
765
766 }
767// Reveal.sync();
768 };
769 patImg.src = background[ 1 ];
770 }
771
772
773 function cloneCanvas( oldCanvas ) {
774 //create a new canvas
775 var newCanvas = document.createElement( 'canvas' );
776 var context = newCanvas.getContext( '2d' );
777 //set dimensions
778 newCanvas.width = oldCanvas.width;
779 newCanvas.height = oldCanvas.height;
780 //apply the old canvas to the new one
781 context.drawImage( oldCanvas, 0, 0 );
782 //return the new canvas
783 return newCanvas;
784 }
785
786 function getCanvas( template, container, board ) {
787 var idx = container.findIndex( element => element.board === board );
788 if ( idx === -1 ) {
789 var canvas = cloneCanvas( template );
790 if ( !container.length ) {
791 idx = 0;
792 container.push( {
793 board,
794 canvas
795 } );
796 } else if ( board < container[ 0 ].board ) {
797 idx = 0;
798 container.unshift( {
799 board,
800 canvas
801 } );
802 } else if ( board > container[ container.length - 1 ].board ) {
803 idx = container.length;
804 container.push( {
805 board,
806 canvas
807 } );
808 }
809 }
810
811 return container[ idx ].canvas;
812 }
813
814 function createDrawings( slideData, patImg ) {
815 var width = Reveal.getConfig().width;
816 var height = Reveal.getConfig().height;
817 var scale = 1;
818 var xOffset = 0;
819 var yOffset = 0;
820 if ( width != storage[ 1 ].width || height != storage[ 1 ].height ) {
821 scale = Math.min( width / storage[ 1 ].width, height / storage[ 1 ].height );
822 xOffset = ( width - storage[ 1 ].width * scale ) / 2;
823 yOffset = ( height - storage[ 1 ].height * scale ) / 2;
824 }
825 mode = 1;
826 board = 0;
827// console.log( 'Create printout(s) for slide ', slideData );
828
829 var drawings = [];
830 var template = document.createElement( 'canvas' );
831 template.width = width;
832 template.height = height;
833
834 var imgCtx = template.getContext( '2d' );
835 imgCtx.fillStyle = imgCtx.createPattern( patImg, 'repeat' );
836 imgCtx.rect( 0, 0, width, height );
837 imgCtx.fill();
838
839 for ( var j = 0; j < slideData.events.length; j++ ) {
840 switch ( slideData.events[ j ].type ) {
841 case 'draw':
842 draw[ 1 ]( getCanvas( template, drawings, board ).getContext( '2d' ),
843 xOffset + slideData.events[ j ].x1 * scale,
844 yOffset + slideData.events[ j ].y1 * scale,
845 xOffset + slideData.events[ j ].x2 * scale,
846 yOffset + slideData.events[ j ].y2 * scale,
847 yOffset + slideData.events[ j ].color
848 );
849 break;
850 case 'erase':
851 eraseWithSponge( getCanvas( template, drawings, board ).getContext( '2d' ),
852 xOffset + slideData.events[ j ].x * scale,
853 yOffset + slideData.events[ j ].y * scale
854 );
855 break;
856 case 'selectboard':
857 selectBoard( slideData.events[ j ].board );
858 break;
859 case 'clear':
860 getCanvas( template, drawings, board ).getContext( '2d' ).clearRect( 0, 0, width, height );
861 getCanvas( template, drawings, board ).getContext( '2d' ).fill();
862 break;
863 default:
864 break;
865 }
866 }
867
868 drawings = drawings.sort( ( a, b ) => a.board > b.board && 1 || -1 );
869
870 mode = 0;
871
872 return drawings;
873 }
874
875 function addDrawings( slide, drawings ) {
876 var parent = slide.parentElement.parentElement;
877 var nextSlide = slide.parentElement.nextElementSibling;
878
879 for ( var i = 0; i < drawings.length; i++ ) {
880 var newPDFPage = document.createElement( 'div' );
881 newPDFPage.classList.add( 'pdf-page' );
882 newPDFPage.style.height = Reveal.getConfig().height;
883 newPDFPage.append( drawings[ i ].canvas );
884//console.log("Add drawing", newPDFPage);
885 if ( nextSlide != null ) {
886 parent.insertBefore( newPDFPage, nextSlide );
887 } else {
888 parent.append( newPDFPage );
889 }
890 }
891 }
892
893 /*****************************************************************
894 ** Drawings
895 ******************************************************************/
896
897 function drawWithBoardmarker( context, fromX, fromY, toX, toY, colorIdx ) {
898 if ( colorIdx == undefined ) colorIdx = color[ mode ];
899 context.lineWidth = boardmarkerWidth;
900 context.lineCap = 'round';
901 context.strokeStyle = boardmarkers[ colorIdx ].color;
902 context.beginPath();
903 context.moveTo( fromX, fromY );
904 context.lineTo( toX, toY );
905 context.stroke();
906 }
907
908 function drawWithChalk( context, fromX, fromY, toX, toY, colorIdx ) {
909 if ( colorIdx == undefined ) colorIdx = color[ mode ];
910 var brushDiameter = chalkWidth;
911 context.lineWidth = brushDiameter;
912 context.lineCap = 'round';
913 context.fillStyle = chalks[ colorIdx ].color; // 'rgba(255,255,255,0.5)';
914 context.strokeStyle = chalks[ colorIdx ].color;
Marc Kupietz09b75752023-10-07 09:32:19 +0200915
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200916 var opacity = 1.0;
917 context.strokeStyle = context.strokeStyle.replace( /[\d\.]+\)$/g, opacity + ')' );
918 context.beginPath();
919 context.moveTo( fromX, fromY );
920 context.lineTo( toX, toY );
921 context.stroke();
922 // Chalk Effect
923 var length = Math.round( Math.sqrt( Math.pow( toX - fromX, 2 ) + Math.pow( toY - fromY, 2 ) ) / ( 5 / brushDiameter ) );
924 var xUnit = ( toX - fromX ) / length;
925 var yUnit = ( toY - fromY ) / length;
926 for ( var i = 0; i < length; i++ ) {
927 if ( chalkEffect > ( Math.random() * 0.9 ) ) {
928 var xCurrent = fromX + ( i * xUnit );
929 var yCurrent = fromY + ( i * yUnit );
930 var xRandom = xCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
931 var yRandom = yCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
932 context.clearRect( xRandom, yRandom, Math.random() * 2 + 2, Math.random() + 1 );
933 }
934 }
935 }
936
937 function eraseWithSponge( context, x, y ) {
938 context.save();
939 context.beginPath();
Marc Kupietz09b75752023-10-07 09:32:19 +0200940 context.arc( x + eraser.radius, y + eraser.radius, eraser.radius, 0, 2 * Math.PI, false );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200941 context.clip();
Marc Kupietz09b75752023-10-07 09:32:19 +0200942 context.clearRect( x - 1, y - 1, eraser.radius * 2 + 2, eraser.radius * 2 + 2 );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200943 context.restore();
944 if ( mode == 1 && grid ) {
Marc Kupietz09b75752023-10-07 09:32:19 +0200945 redrawGrid( x + eraser.radius, y + eraser.radius, eraser.radius );
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200946 }
947 }
948
949
950 /**
951 * Show an overlay for the chalkboard.
952 */
953 function showChalkboard() {
954//console.log("showChalkboard");
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200955 drawingCanvas[ 1 ].container.style.opacity = 1;
956 drawingCanvas[ 1 ].container.style.visibility = 'visible';
957 mode = 1;
958 }
959
960
961 /**
962 * Closes open chalkboard.
963 */
964 function closeChalkboard() {
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +0200965 drawingCanvas[ 1 ].container.style.opacity = 0;
966 drawingCanvas[ 1 ].container.style.visibility = 'hidden';
967 lastX = null;
968 lastY = null;
969 mode = 0;
970 }
971
972 /**
973 * Clear current canvas.
974 */
975 function clearCanvas( id ) {
976 if ( id == 0 ) clearTimeout( slidechangeTimeout );
977 drawingCanvas[ id ].context.clearRect( 0, 0, drawingCanvas[ id ].width, drawingCanvas[ id ].height );
978 if ( id == 1 && grid ) drawGrid();
979 }
980
981 /**
982 * Draw grid on background
983 */
984 function drawGrid() {
985 var context = drawingCanvas[ 1 ].context;
986
987 drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
988 drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
989 drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
990
991 var scale = drawingCanvas[ 1 ].scale;
992 var xOffset = drawingCanvas[ 1 ].xOffset;
993 var yOffset = drawingCanvas[ 1 ].yOffset;
994
995 var distance = grid.distance * scale;
996
997 var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
998 for ( var x = fromX; x < drawingCanvas[ 1 ].width; x += distance ) {
999 context.beginPath();
1000 context.lineWidth = grid.width * scale;
1001 context.lineCap = 'round';
1002 context.fillStyle = grid.color;
1003 context.strokeStyle = grid.color;
1004 context.moveTo( x, 0 );
1005 context.lineTo( x, drawingCanvas[ 1 ].height );
1006 context.stroke();
1007 }
1008 var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
1009
1010 for ( var y = fromY; y < drawingCanvas[ 1 ].height; y += distance ) {
1011 context.beginPath();
1012 context.lineWidth = grid.width * scale;
1013 context.lineCap = 'round';
1014 context.fillStyle = grid.color;
1015 context.strokeStyle = grid.color;
1016 context.moveTo( 0, y );
1017 context.lineTo( drawingCanvas[ 1 ].width, y );
1018 context.stroke();
1019 }
1020 }
1021
1022 function redrawGrid( centerX, centerY, diameter ) {
1023 var context = drawingCanvas[ 1 ].context;
1024
1025 drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
1026 drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
1027 drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
1028
1029 var scale = drawingCanvas[ 1 ].scale;
1030 var xOffset = drawingCanvas[ 1 ].xOffset;
1031 var yOffset = drawingCanvas[ 1 ].yOffset;
1032
1033 var distance = grid.distance * scale;
1034
1035 var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
1036
1037 for ( var x = fromX + distance * Math.ceil( ( centerX - diameter - fromX ) / distance ); x <= fromX + distance * Math.floor( ( centerX + diameter - fromX ) / distance ); x += distance ) {
1038 context.beginPath();
1039 context.lineWidth = grid.width * scale;
1040 context.lineCap = 'round';
1041 context.fillStyle = grid.color;
1042 context.strokeStyle = grid.color;
1043 context.moveTo( x, centerY - Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
1044 context.lineTo( x, centerY + Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
1045 context.stroke();
1046 }
1047 var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
1048 for ( var y = fromY + distance * Math.ceil( ( centerY - diameter - fromY ) / distance ); y <= fromY + distance * Math.floor( ( centerY + diameter - fromY ) / distance ); y += distance ) {
1049 context.beginPath();
1050 context.lineWidth = grid.width * scale;
1051 context.lineCap = 'round';
1052 context.fillStyle = grid.color;
1053 context.strokeStyle = grid.color;
1054 context.moveTo( centerX - Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
1055 context.lineTo( centerX + Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
1056 context.stroke();
1057 }
1058 }
1059
1060 /**
1061 * Set the color
1062 */
Marc Kupietz09b75752023-10-07 09:32:19 +02001063 function setColor( index, record ) {
1064 // protect against out of bounds (this could happen when
1065 // replaying events recorded with different color settings).
1066 if ( index >= pens[ mode ].length ) index = 0;
1067
1068 color[ mode ] = index;
1069
1070 if ( color[ mode ] < 0 ) {
1071 // use eraser
1072 changeCursor( drawingCanvas[ mode ].canvas, sponge );
1073 }
1074 else {
1075 changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
1076 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001077 }
1078
1079 /**
1080 * Set the board
1081 */
1082 function selectBoard( boardIdx, record ) {
1083//console.log("Set board",boardIdx);
1084 if ( board == boardIdx ) return;
1085
1086 board = boardIdx;
1087 redrawChalkboard( boardIdx );
1088 if ( record ) {
1089 recordEvent( { type: 'selectboard' } );
1090 }
1091 }
1092
1093 function redrawChalkboard( boardIdx ) {
1094 clearCanvas( 1 );
1095 var slideData = getSlideData( slideIndices, 1 );
1096 var index = 0;
1097 var play = ( boardIdx == 0 );
1098 while ( index < slideData.events.length && slideData.events[ index ].time < Date.now() - slideStart ) {
1099 if ( boardIdx == slideData.events[ index ].board ) {
1100 playEvent( 1, slideData.events[ index ], Date.now() - slideStart );
1101 }
1102
1103 index++;
1104 }
1105 }
1106
1107
1108 /**
1109 * Forward cycle color
1110 */
1111 function cycleColorNext() {
1112 color[ mode ] = ( color[ mode ] + 1 ) % pens[ mode ].length;
1113 return color[ mode ];
1114 }
1115
1116 /**
1117 * Backward cycle color
1118 */
1119 function cycleColorPrev() {
1120 color[ mode ] = ( color[ mode ] + ( pens[ mode ].length - 1 ) ) % pens[ mode ].length;
1121 return color[ mode ];
1122 }
1123
1124/*****************************************************************
1125 ** Broadcast
1126 ******************************************************************/
1127
1128 var eventQueue = [];
1129
1130 document.addEventListener( 'received', function ( message ) {
1131 if ( message.content && message.content.sender == 'chalkboard-plugin' ) {
1132 // add message to queue
1133 eventQueue.push( message );
1134 console.log( JSON.stringify( message ) );
1135 }
1136 if ( eventQueue.length == 1 ) processQueue();
1137 } );
1138
1139 function processQueue() {
1140 // take first message from queue
1141 var message = eventQueue.shift();
1142
1143 // synchronize time with seminar host
1144 slideStart = Date.now() - message.content.timestamp;
1145 // set status
1146 if ( mode < message.content.mode ) {
1147 // open chalkboard
1148 showChalkboard();
1149 } else if ( mode > message.content.mode ) {
1150 // close chalkboard
1151 closeChalkboard();
1152 }
1153 if ( board != message.content.board ) {
1154 board = message.content.board;
1155 redrawChalkboard( board );
1156 };
1157
1158 switch ( message.content.type ) {
1159 case 'showChalkboard':
1160 showChalkboard();
1161 break;
1162 case 'closeChalkboard':
1163 closeChalkboard();
1164 break;
1165 case 'erase':
1166 erasePoint( message.content.x, message.content.y );
1167 break;
1168 case 'draw':
1169 drawSegment( message.content.fromX, message.content.fromY, message.content.toX, message.content.toY, message.content.color );
1170 break;
1171 case 'clear':
1172 clearSlide();
1173 break;
1174 case 'selectboard':
1175 selectBoard( message.content.board, true );
1176 break;
1177 case 'resetSlide':
1178 resetSlideDrawings();
1179 break;
1180 case 'init':
1181 storage = message.content.storage;
1182 for ( var id = 0; id < 2; id++ ) {
1183 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
1184 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
1185 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
1186 }
1187 clearCanvas( 0 );
1188 clearCanvas( 1 );
1189 if ( !playback ) {
1190 slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
1191 }
1192 if ( mode == 1 && message.content.mode == 0 ) {
1193 setTimeout( closeChalkboard, transition + 50 );
1194 }
1195 if ( mode == 0 && message.content.mode == 1 ) {
1196 setTimeout( showChalkboard, transition + 50 );
1197 }
1198 mode = message.content.mode;
1199 board = message.content.board;
1200 break;
1201 default:
1202 break;
1203 }
1204
1205 // continue with next message if queued
1206 if ( eventQueue.length > 0 ) {
1207 processQueue();
1208 } else {
1209 storageChanged();
1210 }
1211 }
1212
1213 document.addEventListener( 'welcome', function ( user ) {
1214 // broadcast storage
1215 var message = new CustomEvent( messageType );
1216 message.content = {
1217 sender: 'chalkboard-plugin',
1218 recipient: user.id,
1219 type: 'init',
1220 timestamp: Date.now() - slideStart,
1221 storage: storage,
1222 mode,
1223 board
1224 };
1225 document.dispatchEvent( message );
1226 } );
1227
1228 /*****************************************************************
1229 ** Playback
1230 ******************************************************************/
1231
1232 document.addEventListener( 'seekplayback', function ( event ) {
1233//console.log('event seekplayback ' + event.timestamp);
1234 stopPlayback();
1235 if ( !playback || event.timestamp == 0 ) {
1236 // in other cases startplayback fires after seeked
1237 startPlayback( event.timestamp );
1238 }
1239 //console.log('seeked');
1240 } );
1241
1242
1243 document.addEventListener( 'startplayback', function ( event ) {
1244//console.log('event startplayback ' + event.timestamp);
1245 stopPlayback();
1246 playback = true;
1247 startPlayback( event.timestamp );
1248 } );
1249
1250 document.addEventListener( 'stopplayback', function ( event ) {
1251//console.log('event stopplayback ' + (Date.now() - slideStart) );
1252 playback = false;
1253 stopPlayback();
1254 } );
1255
1256 document.addEventListener( 'startrecording', function ( event ) {
1257//console.log('event startrecording ' + event.timestamp);
1258 startRecording();
1259 } );
1260
1261
1262 function startRecording() {
1263 resetSlide( true );
1264 slideStart = Date.now();
1265 }
1266
1267 function startPlayback( timestamp, finalMode ) {
1268//console.log("playback " + timestamp );
1269 slideStart = Date.now() - timestamp;
1270 closeChalkboard();
1271 mode = 0;
1272 board = 0;
1273 for ( var id = 0; id < 2; id++ ) {
1274 clearCanvas( id );
1275 var slideData = getSlideData( slideIndices, id );
1276//console.log( timestamp +" / " + JSON.stringify(slideData));
1277 var index = 0;
1278 while ( index < slideData.events.length && slideData.events[ index ].time < ( Date.now() - slideStart ) ) {
1279 playEvent( id, slideData.events[ index ], timestamp );
1280 index++;
1281 }
1282
1283 while ( playback && index < slideData.events.length ) {
1284 timeouts[ id ].push( setTimeout( playEvent, slideData.events[ index ].time - ( Date.now() - slideStart ), id, slideData.events[ index ], timestamp ) );
1285 index++;
1286 }
1287 }
1288//console.log("Mode: " + finalMode + "/" + mode );
1289 if ( finalMode != undefined ) {
1290 mode = finalMode;
1291 }
1292 if ( mode == 1 ) showChalkboard();
1293//console.log("playback (ok)");
1294
1295 };
1296
1297 function stopPlayback() {
1298//console.log("stopPlayback");
1299//console.log("Timeouts: " + timeouts[0].length + "/"+ timeouts[1].length);
1300 for ( var id = 0; id < 2; id++ ) {
1301 for ( var i = 0; i < timeouts[ id ].length; i++ ) {
1302 clearTimeout( timeouts[ id ][ i ] );
1303 }
1304 timeouts[ id ] = [];
1305 }
1306 };
1307
1308 function playEvent( id, event, timestamp ) {
1309//console.log( timestamp +" / " + JSON.stringify(event));
1310//console.log( id + ": " + timestamp +" / " + event.time +" / " + event.type +" / " + mode );
1311 switch ( event.type ) {
1312 case 'open':
1313 if ( timestamp <= event.time ) {
1314 showChalkboard();
1315 } else {
1316 mode = 1;
1317 }
1318
1319 break;
1320 case 'close':
1321 if ( timestamp < event.time ) {
1322 closeChalkboard();
1323 } else {
1324 mode = 0;
1325 }
1326 break;
1327 case 'clear':
1328 clearCanvas( id );
1329 break;
1330 case 'selectboard':
1331 selectBoard( event.board );
1332 break;
1333 case 'draw':
1334 drawLine( id, event, timestamp );
1335 break;
1336 case 'erase':
1337 eraseCircle( id, event, timestamp );
1338 break;
1339 }
1340 };
1341
1342 function drawLine( id, event, timestamp ) {
1343 var ctx = drawingCanvas[ id ].context;
1344 var scale = drawingCanvas[ id ].scale;
1345 var xOffset = drawingCanvas[ id ].xOffset;
1346 var yOffset = drawingCanvas[ id ].yOffset;
1347 draw[ id ]( ctx, xOffset + event.x1 * scale, yOffset + event.y1 * scale, xOffset + event.x2 * scale, yOffset + event.y2 * scale, event.color );
1348 };
1349
1350 function eraseCircle( id, event, timestamp ) {
1351 var ctx = drawingCanvas[ id ].context;
1352 var scale = drawingCanvas[ id ].scale;
1353 var xOffset = drawingCanvas[ id ].xOffset;
1354 var yOffset = drawingCanvas[ id ].yOffset;
1355
1356 eraseWithSponge( ctx, xOffset + event.x * scale, yOffset + event.y * scale );
1357 };
1358
1359 function startErasing( x, y ) {
1360 drawing = false;
1361 erasing = true;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001362 erasePoint( x, y );
1363 }
1364
1365 function erasePoint( x, y ) {
1366 var ctx = drawingCanvas[ mode ].context;
1367 var scale = drawingCanvas[ mode ].scale;
1368 var xOffset = drawingCanvas[ mode ].xOffset;
1369 var yOffset = drawingCanvas[ mode ].yOffset;
1370
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001371 recordEvent( {
1372 type: 'erase',
1373 x,
1374 y
1375 } );
1376
1377 if (
1378 x * scale + xOffset > 0 &&
1379 y * scale + yOffset > 0 &&
1380 x * scale + xOffset < drawingCanvas[ mode ].width &&
1381 y * scale + yOffset < drawingCanvas[ mode ].height
1382 ) {
1383 eraseWithSponge( ctx, x * scale + xOffset, y * scale + yOffset );
1384 }
1385 }
1386
1387 function stopErasing() {
1388 erasing = false;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001389 }
1390
1391 function startDrawing( x, y ) {
1392 drawing = true;
1393
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 lastX = x * scale + xOffset;
1399 lastY = y * scale + yOffset;
1400 }
1401
1402 function drawSegment( fromX, fromY, toX, toY, colorIdx ) {
1403 var ctx = drawingCanvas[ mode ].context;
1404 var scale = drawingCanvas[ mode ].scale;
1405 var xOffset = drawingCanvas[ mode ].xOffset;
1406 var yOffset = drawingCanvas[ mode ].yOffset;
1407
1408 recordEvent( {
1409 type: 'draw',
1410 color: colorIdx,
1411 x1: fromX,
1412 y1: fromY,
1413 x2: toX,
1414 y2: toY
1415 } );
1416
1417 if (
1418 fromX * scale + xOffset > 0 &&
1419 fromY * scale + yOffset > 0 &&
1420 fromX * scale + xOffset < drawingCanvas[ mode ].width &&
1421 fromY * scale + yOffset < drawingCanvas[ mode ].height &&
1422 toX * scale + xOffset > 0 &&
1423 toY * scale + yOffset > 0 &&
1424 toX * scale + xOffset < drawingCanvas[ mode ].width &&
1425 toY * scale + yOffset < drawingCanvas[ mode ].height
1426 ) {
1427 draw[ mode ]( ctx, fromX * scale + xOffset, fromY * scale + yOffset, toX * scale + xOffset, toY * scale + yOffset, colorIdx );
1428 }
1429 }
1430
1431 function stopDrawing() {
1432 drawing = false;
1433 }
1434
1435
1436/*****************************************************************
1437 ** User interface
1438 ******************************************************************/
1439
1440 function setupCanvasEvents( canvas ) {
1441// TODO: check all touchevents
1442 canvas.addEventListener( 'touchstart', function ( evt ) {
1443 evt.preventDefault();
1444//console.log("Touch start");
1445 if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
1446 var scale = drawingCanvas[ mode ].scale;
1447 var xOffset = drawingCanvas[ mode ].xOffset;
1448 var yOffset = drawingCanvas[ mode ].yOffset;
1449
1450 var touch = evt.touches[ 0 ];
1451 mouseX = touch.pageX;
1452 mouseY = touch.pageY;
Marc Kupietz09b75752023-10-07 09:32:19 +02001453 if ( color[ mode ] < 0 ) {
1454 startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale);
1455 }
1456 else {
1457 startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1458 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001459 }
1460 }, passiveSupported ? {
1461 passive: false
1462 } : false );
1463
1464 canvas.addEventListener( 'touchmove', function ( evt ) {
1465 evt.preventDefault();
1466//console.log("Touch move");
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001467 if ( drawing || erasing ) {
1468 var scale = drawingCanvas[ mode ].scale;
1469 var xOffset = drawingCanvas[ mode ].xOffset;
1470 var yOffset = drawingCanvas[ mode ].yOffset;
1471
1472 var touch = evt.touches[ 0 ];
1473 mouseX = touch.pageX;
1474 mouseY = touch.pageY;
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001475
1476 if ( drawing ) {
1477 drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
1478 // broadcast
1479 var message = new CustomEvent( messageType );
1480 message.content = {
1481 sender: 'chalkboard-plugin',
1482 type: 'draw',
1483 timestamp: Date.now() - slideStart,
1484 mode,
1485 board,
1486 fromX: ( lastX - xOffset ) / scale,
1487 fromY: ( lastY - yOffset ) / scale,
1488 toX: ( mouseX - xOffset ) / scale,
1489 toY: ( mouseY - yOffset ) / scale,
1490 color: color[ mode ]
1491 };
1492 document.dispatchEvent( message );
1493
1494 lastX = mouseX;
1495 lastY = mouseY;
1496 } else {
1497 erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1498 // broadcast
1499 var message = new CustomEvent( messageType );
1500 message.content = {
1501 sender: 'chalkboard-plugin',
1502 type: 'erase',
1503 timestamp: Date.now() - slideStart,
1504 mode,
1505 board,
1506 x: ( mouseX - xOffset ) / scale,
1507 y: ( mouseY - yOffset ) / scale
1508 };
1509 document.dispatchEvent( message );
1510 }
1511
1512 }
1513 }, false );
1514
1515
1516 canvas.addEventListener( 'touchend', function ( evt ) {
1517 evt.preventDefault();
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001518 stopDrawing();
Marc Kupietz09b75752023-10-07 09:32:19 +02001519 stopErasing();
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001520 }, false );
1521
1522 canvas.addEventListener( 'mousedown', function ( evt ) {
1523 evt.preventDefault();
1524 if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
1525//console.log( "mousedown: " + evt.button );
1526 var scale = drawingCanvas[ mode ].scale;
1527 var xOffset = drawingCanvas[ mode ].xOffset;
1528 var yOffset = drawingCanvas[ mode ].yOffset;
1529
1530 mouseX = evt.pageX;
1531 mouseY = evt.pageY;
1532
Marc Kupietz09b75752023-10-07 09:32:19 +02001533 if ( color[ mode ] < 0 || evt.button == 2 || evt.button == 1 ) {
1534 if ( color[ mode ] >= 0 ) {
1535 // show sponge
1536 changeCursor( drawingCanvas[ mode ].canvas, sponge );
1537 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001538 startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1539 // broadcast
1540 var message = new CustomEvent( messageType );
1541 message.content = {
1542 sender: 'chalkboard-plugin',
1543 type: 'erase',
1544 timestamp: Date.now() - slideStart,
1545 mode,
1546 board,
1547 x: ( mouseX - xOffset ) / scale,
1548 y: ( mouseY - yOffset ) / scale
1549 };
1550 document.dispatchEvent( message );
1551 } else {
1552 startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1553 }
1554 }
1555 } );
1556
1557 canvas.addEventListener( 'mousemove', function ( evt ) {
1558 evt.preventDefault();
1559//console.log("Mouse move");
Marc Kupietz09b75752023-10-07 09:32:19 +02001560
1561 var scale = drawingCanvas[ mode ].scale;
1562 var xOffset = drawingCanvas[ mode ].xOffset;
1563 var yOffset = drawingCanvas[ mode ].yOffset;
1564
1565 mouseX = evt.pageX;
1566 mouseY = evt.pageY;
1567
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001568 if ( drawing || erasing ) {
1569 var scale = drawingCanvas[ mode ].scale;
1570 var xOffset = drawingCanvas[ mode ].xOffset;
1571 var yOffset = drawingCanvas[ mode ].yOffset;
1572
1573 mouseX = evt.pageX;
1574 mouseY = evt.pageY;
1575
1576 if ( drawing ) {
1577 drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
1578 // broadcast
1579 var message = new CustomEvent( messageType );
1580 message.content = {
1581 sender: 'chalkboard-plugin',
1582 type: 'draw',
1583 timestamp: Date.now() - slideStart,
1584 mode,
1585 board,
1586 fromX: ( lastX - xOffset ) / scale,
1587 fromY: ( lastY - yOffset ) / scale,
1588 toX: ( mouseX - xOffset ) / scale,
1589 toY: ( mouseY - yOffset ) / scale,
1590 color: color[ mode ]
1591 };
1592 document.dispatchEvent( message );
1593
1594 lastX = mouseX;
1595 lastY = mouseY;
1596 } else {
1597 erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
1598 // broadcast
1599 var message = new CustomEvent( messageType );
1600 message.content = {
1601 sender: 'chalkboard-plugin',
1602 type: 'erase',
1603 timestamp: Date.now() - slideStart,
1604 mode,
1605 board,
1606 x: ( mouseX - xOffset ) / scale,
1607 y: ( mouseY - yOffset ) / scale
1608 };
1609 document.dispatchEvent( message );
1610 }
1611
1612 }
1613 } );
1614
1615
1616 canvas.addEventListener( 'mouseup', function ( evt ) {
1617 evt.preventDefault();
Marc Kupietz09b75752023-10-07 09:32:19 +02001618 if ( color[ mode ] >= 0 ) {
1619 changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
1620 }
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001621 if ( drawing || erasing ) {
1622 stopDrawing();
1623 stopErasing();
1624 }
1625 } );
1626 }
1627
1628 function resize() {
1629//console.log("resize");
1630 // Resize the canvas and draw everything again
1631 var timestamp = Date.now() - slideStart;
1632 if ( !playback ) {
1633 timestamp = getSlideDuration();
1634 }
1635
1636//console.log( drawingCanvas[0].scale + "/" + drawingCanvas[0].xOffset + "/" +drawingCanvas[0].yOffset );
1637 for ( var id = 0; id < 2; id++ ) {
1638 drawingCanvas[ id ].width = window.innerWidth;
1639 drawingCanvas[ id ].height = window.innerHeight;
1640 drawingCanvas[ id ].canvas.width = drawingCanvas[ id ].width;
1641 drawingCanvas[ id ].canvas.height = drawingCanvas[ id ].height;
1642 drawingCanvas[ id ].context.canvas.width = drawingCanvas[ id ].width;
1643 drawingCanvas[ id ].context.canvas.height = drawingCanvas[ id ].height;
1644
1645 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
1646 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
1647 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
1648//console.log( drawingCanvas[id].scale + "/" + drawingCanvas[id].xOffset + "/" +drawingCanvas[id].yOffset );
1649 }
1650//console.log( window.innerWidth + "/" + window.innerHeight);
1651 startPlayback( timestamp, mode, true );
1652 }
1653
1654 Reveal.addEventListener( 'pdf-ready', function ( evt ) {
1655// console.log( "Create printouts when ready" );
1656 whenLoaded( createPrintout );
1657 });
1658
1659 Reveal.addEventListener( 'ready', function ( evt ) {
1660//console.log('ready');
1661 if ( !printMode ) {
1662 window.addEventListener( 'resize', resize );
1663
1664 slideStart = Date.now() - getSlideDuration();
1665 slideIndices = Reveal.getIndices();
1666 if ( !playback ) {
1667 startPlayback( getSlideDuration(), 0 );
1668 }
1669 if ( Reveal.isAutoSliding() ) {
1670 var event = new CustomEvent( 'startplayback' );
1671 event.timestamp = 0;
1672 document.dispatchEvent( event );
1673 }
1674 updateStorage();
1675 whenReady( addPageNumbers );
1676 }
1677 } );
1678 Reveal.addEventListener( 'slidechanged', function ( evt ) {
1679// clearTimeout( slidechangeTimeout );
1680//console.log('slidechanged');
1681 if ( !printMode ) {
1682 slideStart = Date.now() - getSlideDuration();
1683 slideIndices = Reveal.getIndices();
1684 closeChalkboard();
1685 board = 0;
1686 clearCanvas( 0 );
1687 clearCanvas( 1 );
1688 if ( !playback ) {
1689 slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
1690 }
1691 if ( Reveal.isAutoSliding() ) {
1692 var event = new CustomEvent( 'startplayback' );
1693 event.timestamp = 0;
1694 document.dispatchEvent( event );
1695 }
1696 }
1697 } );
1698 Reveal.addEventListener( 'fragmentshown', function ( evt ) {
1699// clearTimeout( slidechangeTimeout );
1700//console.log('fragmentshown');
1701 if ( !printMode ) {
1702 slideStart = Date.now() - getSlideDuration();
1703 slideIndices = Reveal.getIndices();
1704 closeChalkboard();
1705 board = 0;
1706 clearCanvas( 0 );
1707 clearCanvas( 1 );
1708 if ( Reveal.isAutoSliding() ) {
1709 var event = new CustomEvent( 'startplayback' );
1710 event.timestamp = 0;
1711 document.dispatchEvent( event );
1712 } else if ( !playback ) {
1713 startPlayback( getSlideDuration(), 0 );
1714// closeChalkboard();
1715 }
1716 }
1717 } );
1718 Reveal.addEventListener( 'fragmenthidden', function ( evt ) {
1719// clearTimeout( slidechangeTimeout );
1720//console.log('fragmenthidden');
1721 if ( !printMode ) {
1722 slideStart = Date.now() - getSlideDuration();
1723 slideIndices = Reveal.getIndices();
1724 closeChalkboard();
1725 board = 0;
1726 clearCanvas( 0 );
1727 clearCanvas( 1 );
1728 if ( Reveal.isAutoSliding() ) {
1729 document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
1730 } else if ( !playback ) {
1731 startPlayback( getSlideDuration() );
1732 closeChalkboard();
1733 }
1734 }
1735 } );
1736
1737 Reveal.addEventListener( 'autoslideresumed', function ( evt ) {
1738//console.log('autoslideresumed');
1739 var event = new CustomEvent( 'startplayback' );
1740 event.timestamp = 0;
1741 document.dispatchEvent( event );
1742 } );
1743 Reveal.addEventListener( 'autoslidepaused', function ( evt ) {
1744//console.log('autoslidepaused');
1745 document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
1746
1747 // advance to end of slide
1748// closeChalkboard();
1749 startPlayback( getSlideDuration(), 0 );
1750 } );
1751
1752 function toggleNotesCanvas() {
1753 if ( !readOnly ) {
1754 if ( mode == 1 ) {
1755 toggleChalkboard();
1756 notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
1757 notescanvas.style.pointerEvents = 'auto';
1758 }
1759 else {
1760 if ( notescanvas.style.pointerEvents != 'none' ) {
1761 // hide notes canvas
1762 if ( colorButtons ) {
1763 notescanvas.querySelector( '.palette' ).style.visibility = 'hidden';
1764 }
1765 notescanvas.style.background = 'rgba(0,0,0,0)';
1766 notescanvas.style.pointerEvents = 'none';
1767 }
1768 else {
1769 // show notes canvas
1770 if ( colorButtons ) {
1771 notescanvas.querySelector( '.palette' ).style.visibility = 'visible';
1772 }
1773 notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
1774 notescanvas.style.pointerEvents = 'auto';
1775
1776 var idx = 0;
1777 if ( color[ mode ] ) {
1778 idx = color[ mode ];
1779 }
1780
1781 setColor( idx, true );
1782 }
1783 }
1784 }
1785 };
1786
1787 function toggleChalkboard() {
1788//console.log("toggleChalkboard " + mode);
1789 if ( mode == 1 ) {
1790 if ( !readOnly ) {
1791 recordEvent( { type: 'close' } );
1792 }
1793 closeChalkboard();
1794
1795 // broadcast
1796 var message = new CustomEvent( messageType );
1797 message.content = {
1798 sender: 'chalkboard-plugin',
1799 type: 'closeChalkboard',
1800 timestamp: Date.now() - slideStart,
1801 mode: 0,
1802 board
1803 };
1804 document.dispatchEvent( message );
1805
1806
1807 } else {
1808 showChalkboard();
1809 if ( !readOnly ) {
1810 recordEvent( { type: 'open' } );
1811 // broadcast
1812 var message = new CustomEvent( messageType );
1813 message.content = {
1814 sender: 'chalkboard-plugin',
1815 type: 'showChalkboard',
1816 timestamp: Date.now() - slideStart,
1817 mode: 1,
1818 board
1819 };
1820 document.dispatchEvent( message );
1821
1822 var idx = 0;
1823
1824 if ( rememberColor[ mode ] ) {
1825 idx = color[ mode ];
1826 }
1827
1828 setColor( idx, true );
1829 }
1830 }
1831 };
1832
1833 function clearSlide() {
1834 recordEvent( { type: 'clear' } );
1835 clearCanvas( mode );
1836 }
1837
1838 function clear() {
1839 if ( !readOnly ) {
1840 clearSlide();
1841 // broadcast
1842 var message = new CustomEvent( messageType );
1843 message.content = {
1844 sender: 'chalkboard-plugin',
1845 type: 'clear',
1846 timestamp: Date.now() - slideStart,
1847 mode,
1848 board
1849 };
1850 document.dispatchEvent( message );
1851 }
1852 };
1853
1854 function colorIndex( idx ) {
1855 if ( !readOnly ) {
1856 setColor( idx, true );
1857 }
1858 }
1859
1860 function colorNext() {
1861 if ( !readOnly ) {
1862 let idx = cycleColorNext();
1863 setColor( idx, true );
1864 }
1865 }
1866
1867 function colorPrev() {
1868 if ( !readOnly ) {
1869 let idx = cycleColorPrev();
1870 setColor( idx, true );
1871 }
1872 }
1873
1874 function resetSlideDrawings() {
1875 slideStart = Date.now();
1876 closeChalkboard();
1877
1878 clearCanvas( 0 );
1879 clearCanvas( 1 );
1880
1881 mode = 1;
1882 var slideData = getSlideData();
1883 slideData.duration = 0;
1884 slideData.events = [];
1885 mode = 0;
1886 var slideData = getSlideData();
1887 slideData.duration = 0;
1888 slideData.events = [];
1889
1890 updateStorage();
1891 }
1892
1893 function resetSlide( force ) {
1894 var ok = force || confirm( "Please confirm to delete chalkboard drawings on this slide!" );
1895 if ( ok ) {
1896//console.log("resetSlide ");
1897 stopPlayback();
1898 resetSlideDrawings();
1899 // broadcast
1900 var message = new CustomEvent( messageType );
1901 message.content = {
1902 sender: 'chalkboard-plugin',
1903 type: 'resetSlide',
1904 timestamp: Date.now() - slideStart,
1905 mode,
1906 board
1907 };
1908 document.dispatchEvent( message );
1909 }
1910 };
1911
1912 function resetStorage( force ) {
1913 var ok = force || confirm( "Please confirm to delete all chalkboard drawings!" );
1914 if ( ok ) {
1915 stopPlayback();
1916 slideStart = Date.now();
1917 clearCanvas( 0 );
1918 clearCanvas( 1 );
1919 if ( mode == 1 ) {
1920 closeChalkboard();
1921 }
1922
1923 storage = [ {
1924 width: Reveal.getConfig().width,
1925 height: Reveal.getConfig().height,
1926 data: []
1927 },
1928 {
1929 width: Reveal.getConfig().width,
1930 height: Reveal.getConfig().height,
1931 data: []
1932 }
1933 ];
1934
1935 if ( config.storage ) {
1936 sessionStorage.setItem( config.storage, null )
1937 }
1938 // broadcast
1939 var message = new CustomEvent( messageType );
1940 message.content = {
1941 sender: 'chalkboard-plugin',
1942 type: 'init',
1943 timestamp: Date.now() - slideStart,
1944 storage,
1945 mode,
1946 board
1947 };
1948 document.dispatchEvent( message );
1949 }
1950 };
1951
1952 this.toggleNotesCanvas = toggleNotesCanvas;
1953 this.toggleChalkboard = toggleChalkboard;
1954 this.colorIndex = colorIndex;
1955 this.colorNext = colorNext;
1956 this.colorPrev = colorPrev;
1957 this.clear = clear;
1958 this.reset = resetSlide;
1959 this.resetAll = resetStorage;
1960 this.download = downloadData;
1961 this.updateStorage = updateStorage;
1962 this.getData = getData;
1963 this.configure = configure;
1964
1965
1966 for ( var key in keyBindings ) {
1967 if ( keyBindings[ key ] ) {
1968 Reveal.addKeyBinding( keyBindings[ key ], RevealChalkboard[ key ] );
1969 }
1970 };
1971
1972 return this;
1973};