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