| /***************************************************************** |
| ** Author: Asvin Goel, goel@telematique.eu |
| ** |
| ** A plugin for reveal.js adding a chalkboard. |
| ** |
| ** Version: 2.3.3 |
| ** |
| ** License: MIT license (see LICENSE.md) |
| ** |
| ** Credits: |
| ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard |
| ** Multi color support initially added by Kurt Rinnert https://github.com/rinnert |
| ** Compatibility with reveal.js v4 by Hakim El Hattab https://github.com/hakimel |
| ******************************************************************/ |
| |
| "use strict"; |
| |
| window.RevealChalkboard = window.RevealChalkboard || { |
| id: 'RevealChalkboard', |
| init: function ( deck ) { |
| initChalkboard.call(this, deck ); |
| }, |
| configure: function ( config ) { |
| configure( config ); |
| }, |
| toggleNotesCanvas: function () { |
| toggleNotesCanvas(); |
| }, |
| toggleChalkboard: function () { |
| toggleChalkboard(); |
| }, |
| colorIndex: function () { |
| colorIndex(); |
| }, |
| colorNext: function () { |
| colorNext(); |
| }, |
| colorPrev: function () { |
| colorPrev(); |
| }, |
| clear: function () { |
| clear(); |
| }, |
| reset: function () { |
| reset(); |
| }, |
| resetAll: function () { |
| resetAll(); |
| }, |
| updateStorage: function () { |
| updateStorage(); |
| }, |
| getData: function () { |
| return getData(); |
| }, |
| download: function () { |
| download(); |
| }, |
| }; |
| |
| function scriptPath() { |
| // obtain plugin path from the script element |
| var src; |
| if ( document.currentScript ) { |
| src = document.currentScript.src; |
| } else { |
| var sel = document.querySelector( 'script[src$="/chalkboard/plugin.js"]' ) |
| if ( sel ) { |
| src = sel.src; |
| } |
| } |
| var path = ( src === undefined ) ? "" : src.slice( 0, src.lastIndexOf( "/" ) + 1 ); |
| //console.log("Path: " + path); |
| return path; |
| } |
| var path = scriptPath(); |
| |
| const initChalkboard = function ( Reveal ) { |
| //console.warn(path); |
| /* Feature detection for passive event handling*/ |
| var passiveSupported = false; |
| |
| try { |
| window.addEventListener( 'test', null, Object.defineProperty( {}, 'passive', { |
| get: function () { |
| passiveSupported = true; |
| } |
| } ) ); |
| } catch ( err ) {} |
| |
| |
| /***************************************************************** |
| ** Configuration |
| ******************************************************************/ |
| var background, pens, draw, color; |
| var grid = false; |
| var boardmarkerWidth = 3; |
| var chalkWidth = 7; |
| var chalkEffect = 1.0; |
| var rememberColor = [ true, false ]; |
| var eraser = { |
| src: path + 'img/sponge.png', |
| radius: 20 |
| }; |
| var boardmarkers = [ { |
| color: 'rgba(100,100,100,1)', |
| cursor: 'url(' + path + 'img/boardmarker-black.png), auto' |
| }, |
| { |
| color: 'rgba(30,144,255, 1)', |
| cursor: 'url(' + path + 'img/boardmarker-blue.png), auto' |
| }, |
| { |
| color: 'rgba(220,20,60,1)', |
| cursor: 'url(' + path + 'img/boardmarker-red.png), auto' |
| }, |
| { |
| color: 'rgba(50,205,50,1)', |
| cursor: 'url(' + path + 'img/boardmarker-green.png), auto' |
| }, |
| { |
| color: 'rgba(255,140,0,1)', |
| cursor: 'url(' + path + 'img/boardmarker-orange.png), auto' |
| }, |
| { |
| color: 'rgba(150,0,20150,1)', |
| cursor: 'url(' + path + 'img/boardmarker-purple.png), auto' |
| }, |
| { |
| color: 'rgba(255,220,0,1)', |
| cursor: 'url(' + path + 'img/boardmarker-yellow.png), auto' |
| } |
| ]; |
| var chalks = [ { |
| color: 'rgba(255,255,255,0.5)', |
| cursor: 'url(' + path + 'img/chalk-white.png), auto' |
| }, |
| { |
| color: 'rgba(96, 154, 244, 0.5)', |
| cursor: 'url(' + path + 'img/chalk-blue.png), auto' |
| }, |
| { |
| color: 'rgba(237, 20, 28, 0.5)', |
| cursor: 'url(' + path + 'img/chalk-red.png), auto' |
| }, |
| { |
| color: 'rgba(20, 237, 28, 0.5)', |
| cursor: 'url(' + path + 'img/chalk-green.png), auto' |
| }, |
| { |
| color: 'rgba(220, 133, 41, 0.5)', |
| cursor: 'url(' + path + 'img/chalk-orange.png), auto' |
| }, |
| { |
| color: 'rgba(220,0,220,0.5)', |
| cursor: 'url(' + path + 'img/chalk-purple.png), auto' |
| }, |
| { |
| color: 'rgba(255,220,0,0.5)', |
| cursor: 'url(' + path + 'img/chalk-yellow.png), auto' |
| } |
| ]; |
| |
| var sponge = { |
| cursor: 'url(' + path + 'img/sponge.png), auto' |
| } |
| |
| |
| var keyBindings = { |
| toggleNotesCanvas: { |
| keyCode: 67, |
| key: 'C', |
| description: 'Toggle notes canvas' |
| }, |
| toggleChalkboard: { |
| keyCode: 66, |
| key: 'B', |
| description: 'Toggle chalkboard' |
| }, |
| clear: { |
| keyCode: 46, |
| key: 'DEL', |
| description: 'Clear drawings on slide' |
| }, |
| /* |
| reset: { |
| keyCode: 173, |
| key: '-', |
| description: 'Reset drawings on slide' |
| }, |
| */ |
| resetAll: { |
| keyCode: 8, |
| key: 'BACKSPACE', |
| description: 'Reset all drawings' |
| }, |
| colorNext: { |
| keyCode: 88, |
| key: 'X', |
| description: 'Next color' |
| }, |
| colorPrev: { |
| keyCode: 89, |
| key: 'Y', |
| description: 'Previous color' |
| }, |
| download: { |
| keyCode: 68, |
| key: 'D', |
| description: 'Download drawings' |
| } |
| }; |
| |
| |
| var theme = 'chalkboard'; |
| var color = [ 0, 0 ]; |
| var toggleChalkboardButton = false; |
| var toggleNotesButton = false; |
| var colorButtons = true; |
| var boardHandle = true; |
| var transition = 800; |
| |
| var readOnly = false; |
| var messageType = 'broadcast'; |
| |
| var config = configure( Reveal.getConfig().chalkboard || {} ); |
| if ( config.keyBindings ) { |
| for ( var key in config.keyBindings ) { |
| keyBindings[ key ] = config.keyBindings[ key ]; |
| }; |
| } |
| |
| function configure( config ) { |
| |
| if ( config.boardmarkerWidth || config.penWidth ) boardmarkerWidth = config.boardmarkerWidth || config.penWidth; |
| if ( config.chalkWidth ) chalkWidth = config.chalkWidth; |
| if ( config.chalkEffect ) chalkEffect = config.chalkEffect; |
| if ( config.rememberColor ) rememberColor = config.rememberColor; |
| if ( config.eraser ) eraser = config.eraser; |
| if ( config.boardmarkers ) boardmarkers = config.boardmarkers; |
| if ( config.chalks ) chalks = config.chalks; |
| |
| if ( config.theme ) theme = config.theme; |
| switch ( theme ) { |
| case 'whiteboard': |
| background = [ 'rgba(127,127,127,.1)', path + 'img/whiteboard.png' ]; |
| draw = [ drawWithBoardmarker, drawWithBoardmarker ]; |
| pens = [ boardmarkers, boardmarkers ]; |
| grid = { |
| color: 'rgb(127,127,255,0.1)', |
| distance: 40, |
| width: 2 |
| }; |
| break; |
| case 'chalkboard': |
| default: |
| background = [ 'rgba(127,127,127,.1)', path + 'img/blackboard.png' ]; |
| draw = [ drawWithBoardmarker, drawWithChalk ]; |
| pens = [ boardmarkers, chalks ]; |
| grid = { |
| color: 'rgb(50,50,10,0.5)', |
| distance: 80, |
| width: 2 |
| }; |
| } |
| |
| if ( config.background ) background = config.background; |
| if ( config.grid != undefined ) grid = config.grid; |
| |
| if ( config.toggleChalkboardButton != undefined ) toggleChalkboardButton = config.toggleChalkboardButton; |
| if ( config.toggleNotesButton != undefined ) toggleNotesButton = config.toggleNotesButton; |
| if ( config.colorButtons != undefined ) colorButtons = config.colorButtons; |
| if ( config.boardHandle != undefined ) boardHandle = config.boardHandle; |
| if ( config.transition ) transition = config.transition; |
| |
| if ( config.readOnly != undefined ) readOnly = config.readOnly; |
| if ( config.messageType ) messageType = config.messageType; |
| |
| if ( drawingCanvas && ( config.theme || config.background || config.grid ) ) { |
| var canvas = document.getElementById( drawingCanvas[ 1 ].id ); |
| canvas.style.background = 'url("' + background[ 1 ] + '") repeat'; |
| clearCanvas( 1 ); |
| drawGrid(); |
| } |
| |
| return config; |
| } |
| /***************************************************************** |
| ** Setup |
| ******************************************************************/ |
| |
| function whenReady( callback ) { |
| // wait for markdown to be parsed and code to be highlighted |
| if ( !document.querySelector( 'section[data-markdown]:not([data-markdown-parsed])' ) |
| && !document.querySelector( '[data-load]:not([data-loaded])') |
| && !document.querySelector( 'code[data-line-numbers*="|"]') |
| ) { |
| callback(); |
| } else { |
| console.log( "Wait for external sources to be loaded and code to be highlighted" ); |
| setTimeout( whenReady, 500, callback ) |
| } |
| } |
| |
| function whenLoaded( callback ) { |
| // wait for drawings to be loaded and markdown to be parsed |
| if ( loaded !== null ) { |
| callback(); |
| } else { |
| console.log( "Wait for drawings to be loaded" ); |
| setTimeout( whenLoaded, 500, callback ) |
| } |
| } |
| |
| var drawingCanvas = [ { |
| id: 'notescanvas' |
| }, { |
| id: 'chalkboard' |
| } ]; |
| setupDrawingCanvas( 0 ); |
| setupDrawingCanvas( 1 ); |
| |
| var mode = 0; // 0: notes canvas, 1: chalkboard |
| var board = 0; // board index (only for chalkboard) |
| |
| var mouseX = 0; |
| var mouseY = 0; |
| var lastX = null; |
| var lastY = null; |
| |
| var drawing = false; |
| var erasing = false; |
| |
| var slideStart = Date.now(); |
| var slideIndices = { |
| h: 0, |
| v: 0 |
| }; |
| |
| var timeouts = [ |
| [], |
| [] |
| ]; |
| var slidechangeTimeout = null; |
| var updateStorageTimeout = null; |
| var playback = false; |
| |
| function changeCursor( element, tool ) { |
| element.style.cursor = tool.cursor; |
| var palette = document.querySelector('.palette[data-mode="' + mode + '"]'); |
| if ( palette ) { |
| palette.style.cursor = tool.cursor; |
| } |
| } |
| |
| function createPalette( colors, length ) { |
| if ( length === true || length > colors.length ) { |
| length = colors.length; |
| } |
| var palette = document.createElement( 'div' ); |
| palette.classList.add( 'palette' ); |
| var list = document.createElement( 'ul' ); |
| // color pickers |
| for ( var i = 0; i < length; i++ ) { |
| var colorButton = document.createElement( 'li' ); |
| colorButton.setAttribute( 'data-color', i ); |
| colorButton.innerHTML = '<i class="fa fa-square"></i>'; |
| colorButton.style.color = colors[ i ].color; |
| colorButton.addEventListener( 'click', function ( e ) { |
| var element = e.target; |
| while ( !element.hasAttribute( 'data-color' ) ) { |
| element = element.parentElement; |
| } |
| colorIndex( parseInt( element.getAttribute( 'data-color' ) ) ); |
| } ); |
| colorButton.addEventListener( 'touchstart', function ( e ) { |
| var element = e.target; |
| while ( !element.hasAttribute( 'data-color' ) ) { |
| element = element.parentElement; |
| } |
| colorIndex( parseInt( element.getAttribute( 'data-color' ) ) ); |
| } ); |
| list.appendChild( colorButton ); |
| } |
| // eraser |
| var eraserButton = document.createElement( 'li' ); |
| eraserButton.setAttribute( 'data-eraser', 'true' ); |
| var spongeImg = document.createElement( 'img' ); |
| spongeImg.src = eraser.src; |
| spongeImg.height = "24"; |
| spongeImg.width = "24"; |
| spongeImg.style.marginTop = '10px'; |
| spongeImg.style.marginRight = '0'; |
| spongeImg.style.marginBottom = '0'; |
| spongeImg.style.marginLeft = '0'; |
| eraserButton.appendChild(spongeImg); |
| eraserButton.addEventListener( 'click', function ( e ) { |
| colorIndex( -1 ); |
| } ); |
| eraserButton.addEventListener( 'touchstart', function ( e ) { |
| colorIndex( -1 ); |
| } ); |
| list.appendChild( eraserButton ); |
| |
| palette.appendChild( list ); |
| return palette; |
| }; |
| |
| function switchBoard( boardIdx ) { |
| selectBoard( boardIdx, true ); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'selectboard', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board |
| }; |
| document.dispatchEvent( message ); |
| } |
| |
| function setupDrawingCanvas( id ) { |
| var container = document.createElement( 'div' ); |
| container.id = drawingCanvas[ id ].id; |
| container.classList.add( 'overlay' ); |
| container.setAttribute( 'data-prevent-swipe', 'true' ); |
| container.oncontextmenu = function () { |
| return false; |
| } |
| |
| changeCursor( container, pens[ id ][ color[ id ] ] ); |
| |
| drawingCanvas[ id ].width = window.innerWidth; |
| drawingCanvas[ id ].height = window.innerHeight; |
| drawingCanvas[ id ].scale = 1; |
| drawingCanvas[ id ].xOffset = 0; |
| drawingCanvas[ id ].yOffset = 0; |
| |
| if ( id == "0" ) { |
| container.style.background = 'rgba(0,0,0,0)'; |
| container.style.zIndex = 24; |
| container.style.opacity = 1; |
| container.style.visibility = 'visible'; |
| container.style.pointerEvents = 'none'; |
| |
| var slides = document.querySelector( '.slides' ); |
| var aspectRatio = Reveal.getConfig().width / Reveal.getConfig().height; |
| if ( drawingCanvas[ id ].width > drawingCanvas[ id ].height * aspectRatio ) { |
| drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - drawingCanvas[ id ].height * aspectRatio ) / 2; |
| } else if ( drawingCanvas[ id ].height > drawingCanvas[ id ].width / aspectRatio ) { |
| drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - drawingCanvas[ id ].width / aspectRatio ) / 2; |
| } |
| |
| if ( colorButtons ) { |
| var palette = createPalette( boardmarkers, colorButtons ); |
| palette.dataset.mode = id; |
| palette.style.visibility = 'hidden'; // only show palette in drawing mode |
| container.appendChild( palette ); |
| } |
| } else { |
| container.style.background = 'url("' + background[ id ] + '") repeat'; |
| container.style.zIndex = 26; |
| container.style.opacity = 0; |
| container.style.visibility = 'hidden'; |
| |
| if ( colorButtons ) { |
| var palette = createPalette( chalks, colorButtons ); |
| palette.dataset.mode = id; |
| container.appendChild( palette ); |
| } |
| if ( boardHandle ) { |
| var handle = document.createElement( 'div' ); |
| handle.classList.add( 'boardhandle' ); |
| 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>'; |
| handle.querySelector( '#previousboard' ).addEventListener( 'click', function ( e ) { |
| e.preventDefault(); |
| switchBoard( board - 1 ); |
| } ); |
| handle.querySelector( '#nextboard' ).addEventListener( 'click', function ( e ) { |
| e.preventDefault(); |
| switchBoard( board + 1 ); |
| } ); |
| handle.querySelector( '#previousboard' ).addEventListener( 'touchstart', function ( e ) { |
| e.preventDefault(); |
| switchBoard( board - 1 ); |
| } ); |
| handle.querySelector( '#nextboard' ).addEventListener( 'touchstart', function ( e ) { |
| e.preventDefault(); |
| switchBoard( board + 1 ); |
| } ); |
| |
| container.appendChild( handle ); |
| } |
| } |
| |
| var canvas = document.createElement( 'canvas' ); |
| canvas.width = drawingCanvas[ id ].width; |
| canvas.height = drawingCanvas[ id ].height; |
| canvas.setAttribute( 'data-chalkboard', id ); |
| changeCursor( canvas, pens[ id ][ color[ id ] ] ); |
| container.appendChild( canvas ); |
| drawingCanvas[ id ].canvas = canvas; |
| |
| drawingCanvas[ id ].context = canvas.getContext( '2d' ); |
| |
| setupCanvasEvents( container ); |
| |
| document.querySelector( '.reveal' ).appendChild( container ); |
| drawingCanvas[ id ].container = container; |
| } |
| |
| |
| /***************************************************************** |
| ** Storage |
| ******************************************************************/ |
| |
| var storage = [ { |
| width: Reveal.getConfig().width, |
| height: Reveal.getConfig().height, |
| data: [] |
| }, |
| { |
| width: Reveal.getConfig().width, |
| height: Reveal.getConfig().height, |
| data: [] |
| } |
| ]; |
| |
| var loaded = null; |
| |
| if ( config.storage ) { |
| // Get chalkboard drawings from session storage |
| loaded = initStorage( sessionStorage.getItem( config.storage ) ); |
| } |
| |
| if ( !loaded && config.src != null ) { |
| // Get chalkboard drawings from the given file |
| loadData( config.src ); |
| } |
| |
| /** |
| * Initialize storage. |
| */ |
| function initStorage( json ) { |
| var success = false; |
| try { |
| var data = JSON.parse( json ); |
| for ( var id = 0; id < data.length; id++ ) { |
| if ( drawingCanvas[ id ].width != data[ id ].width || drawingCanvas[ id ].height != data[ id ].height ) { |
| drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / data[ id ].width, drawingCanvas[ id ].height / data[ id ].height ); |
| drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - data[ id ].width * drawingCanvas[ id ].scale ) / 2; |
| drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - data[ id ].height * drawingCanvas[ id ].scale ) / 2; |
| } |
| if ( config.readOnly ) { |
| drawingCanvas[ id ].container.style.cursor = 'default'; |
| drawingCanvas[ id ].canvas.style.cursor = 'default'; |
| } |
| } |
| success = true; |
| storage = data; |
| } catch ( err ) { |
| console.warn( "Cannot initialise storage!" ); |
| } |
| return success; |
| } |
| |
| |
| /** |
| * Load data. |
| */ |
| function loadData( filename ) { |
| var xhr = new XMLHttpRequest(); |
| xhr.onload = function () { |
| if ( xhr.readyState === 4 && xhr.status != 404 ) { |
| loaded = initStorage( xhr.responseText ); |
| updateStorage(); |
| console.log( "Drawings loaded from file" ); |
| } else { |
| config.readOnly = undefined; |
| readOnly = undefined; |
| console.warn( 'Failed to get file ' + filename + '. ReadyState: ' + xhr.readyState + ', Status: ' + xhr.status ); |
| loaded = false; |
| } |
| }; |
| |
| xhr.open( 'GET', filename, true ); |
| try { |
| xhr.send(); |
| } catch ( error ) { |
| config.readOnly = undefined; |
| readOnly = undefined; |
| 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 ); |
| loaded = false; |
| } |
| } |
| |
| |
| function storageChanged( now ) { |
| if ( !now ) { |
| // create or update timer |
| if ( updateStorageTimeout ) { |
| clearTimeout( updateStorageTimeout ); |
| } |
| updateStorageTimeout = setTimeout( storageChanged, 1000, true); |
| } |
| else { |
| // console.log("Update storage", updateStorageTimeout, Date.now()); |
| updateStorage(); |
| updateStorageTimeout = null; |
| } |
| } |
| |
| function updateStorage() { |
| var json = JSON.stringify( storage ) |
| if ( config.storage ) { |
| sessionStorage.setItem( config.storage, json ) |
| } |
| return json; |
| } |
| |
| function recordEvent( event ) { |
| //console.log(event); |
| event.time = Date.now() - slideStart; |
| if ( mode == 1 ) event.board = board; |
| var slideData = getSlideData(); |
| var i = slideData.events.length; |
| while ( i > 0 && event.time < slideData.events[ i - 1 ].time ) { |
| i--; |
| } |
| slideData.events.splice( i, 0, event ); |
| slideData.duration = Math.max( slideData.duration, Date.now() - slideStart ) + 1; |
| |
| storageChanged(); |
| } |
| |
| /** |
| * Get data as json string. |
| */ |
| function getData() { |
| // cleanup slide data without events |
| for ( var id = 0; id < 2; id++ ) { |
| for ( var i = storage[ id ].data.length - 1; i >= 0; i-- ) { |
| if ( storage[ id ].data[ i ].events.length == 0 ) { |
| storage[ id ].data.splice( i, 1 ); |
| } |
| } |
| } |
| |
| return updateStorage(); |
| } |
| |
| /** |
| * Download data. |
| */ |
| function downloadData() { |
| var a = document.createElement( 'a' ); |
| document.body.appendChild( a ); |
| try { |
| a.download = 'chalkboard.json'; |
| var blob = new Blob( [ getData() ], { |
| type: 'application/json' |
| } ); |
| a.href = window.URL.createObjectURL( blob ); |
| } catch ( error ) { |
| // https://stackoverflow.com/a/6234804 |
| // escape data for proper handling of quotes and line breaks |
| // in case malicious user gets a chance to craft the exception message |
| error = String(error) |
| .replace(/&/g, "&") |
| .replace(/</g, "<") |
| .replace(/>/g, ">") |
| .replace(/"/g, """) |
| .replace(/'/g, "'"); |
| a.innerHTML += ' (' + error + ')'; |
| } |
| a.click(); |
| document.body.removeChild( a ); |
| } |
| |
| /** |
| * Returns data object for the slide with the given indices. |
| */ |
| function getSlideData( indices, id ) { |
| if ( id == undefined ) id = mode; |
| if ( !indices ) indices = slideIndices; |
| var data; |
| for ( var i = 0; i < storage[ id ].data.length; i++ ) { |
| 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 ) { |
| data = storage[ id ].data[ i ]; |
| return data; |
| } |
| } |
| var page = Number( Reveal.getCurrentSlide().getAttribute('data-pdf-page-number') ); |
| //console.log( indices, Reveal.getCurrentSlide() ); |
| storage[ id ].data.push( { |
| slide: indices, |
| page, |
| events: [], |
| duration: 0 |
| } ); |
| data = storage[ id ].data[ storage[ id ].data.length - 1 ]; |
| return data; |
| } |
| |
| /** |
| * Returns maximum duration of slide playback for both modes |
| */ |
| function getSlideDuration( indices ) { |
| if ( !indices ) indices = slideIndices; |
| var duration = 0; |
| for ( var id = 0; id < 2; id++ ) { |
| for ( var i = 0; i < storage[ id ].data.length; i++ ) { |
| 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 ) { |
| duration = Math.max( duration, storage[ id ].data[ i ].duration ); |
| break; |
| } |
| } |
| } |
| //console.log( duration ); |
| return duration; |
| } |
| |
| /***************************************************************** |
| ** Print |
| ******************************************************************/ |
| var printMode = ( /print-pdf/gi ).test( window.location.search ); |
| //console.log("createPrintout" + printMode) |
| |
| function addPageNumbers() { |
| // determine page number for printouts with fragments serialised |
| var slides = Reveal.getSlides(); |
| var page = 0; |
| for ( var i=0; i < slides.length; i++) { |
| slides[i].setAttribute('data-pdf-page-number',page.toString()); |
| // add number of fragments without fragment indices |
| var count = slides[i].querySelectorAll('.fragment:not([data-fragment-index])').length; |
| var fragments = slides[i].querySelectorAll('.fragment[data-fragment-index]'); |
| for ( var j=0; j < fragments.length; j++) { |
| // increasenumber of fragments by highest fragment index (which start at 0) |
| if ( Number(fragments[j].getAttribute('data-fragment-index')) + 1 > count ) { |
| count = Number(fragments[j].getAttribute('data-fragment-index')) + 1; |
| } |
| } |
| page += count + 1; |
| } |
| } |
| |
| function createPrintout() { |
| //console.warn(Reveal.getTotalSlides(),Reveal.getSlidesElement()); |
| if ( storage[ 1 ].data.length == 0 ) return; |
| console.log( 'Create printout(s) for ' + storage[ 1 ].data.length + " slides" ); |
| drawingCanvas[ 0 ].container.style.opacity = 0; // do not print notes canvas |
| drawingCanvas[ 0 ].container.style.visibility = 'hidden'; |
| |
| var patImg = new Image(); |
| patImg.onload = function () { |
| var slides = Reveal.getSlides(); |
| //console.log(slides); |
| for ( var i = storage[ 1 ].data.length - 1; i >= 0; i-- ) { |
| console.log( 'Create printout for slide ' + storage[ 1 ].data[ i ].slide.h + '.' + storage[ 1 ].data[ i ].slide.v ); |
| var slideData = getSlideData( storage[ 1 ].data[ i ].slide, 1 ); |
| var drawings = createDrawings( slideData, patImg ); |
| addDrawings( slides[storage[ 1 ].data[ i ].page], drawings ); |
| |
| } |
| // Reveal.sync(); |
| }; |
| patImg.src = background[ 1 ]; |
| } |
| |
| |
| function cloneCanvas( oldCanvas ) { |
| //create a new canvas |
| var newCanvas = document.createElement( 'canvas' ); |
| var context = newCanvas.getContext( '2d' ); |
| //set dimensions |
| newCanvas.width = oldCanvas.width; |
| newCanvas.height = oldCanvas.height; |
| //apply the old canvas to the new one |
| context.drawImage( oldCanvas, 0, 0 ); |
| //return the new canvas |
| return newCanvas; |
| } |
| |
| function getCanvas( template, container, board ) { |
| var idx = container.findIndex( element => element.board === board ); |
| if ( idx === -1 ) { |
| var canvas = cloneCanvas( template ); |
| if ( !container.length ) { |
| idx = 0; |
| container.push( { |
| board, |
| canvas |
| } ); |
| } else if ( board < container[ 0 ].board ) { |
| idx = 0; |
| container.unshift( { |
| board, |
| canvas |
| } ); |
| } else if ( board > container[ container.length - 1 ].board ) { |
| idx = container.length; |
| container.push( { |
| board, |
| canvas |
| } ); |
| } |
| } |
| |
| return container[ idx ].canvas; |
| } |
| |
| function createDrawings( slideData, patImg ) { |
| var width = Reveal.getConfig().width; |
| var height = Reveal.getConfig().height; |
| var scale = 1; |
| var xOffset = 0; |
| var yOffset = 0; |
| if ( width != storage[ 1 ].width || height != storage[ 1 ].height ) { |
| scale = Math.min( width / storage[ 1 ].width, height / storage[ 1 ].height ); |
| xOffset = ( width - storage[ 1 ].width * scale ) / 2; |
| yOffset = ( height - storage[ 1 ].height * scale ) / 2; |
| } |
| mode = 1; |
| board = 0; |
| // console.log( 'Create printout(s) for slide ', slideData ); |
| |
| var drawings = []; |
| var template = document.createElement( 'canvas' ); |
| template.width = width; |
| template.height = height; |
| |
| var imgCtx = template.getContext( '2d' ); |
| imgCtx.fillStyle = imgCtx.createPattern( patImg, 'repeat' ); |
| imgCtx.rect( 0, 0, width, height ); |
| imgCtx.fill(); |
| |
| for ( var j = 0; j < slideData.events.length; j++ ) { |
| switch ( slideData.events[ j ].type ) { |
| case 'draw': |
| draw[ 1 ]( getCanvas( template, drawings, board ).getContext( '2d' ), |
| xOffset + slideData.events[ j ].x1 * scale, |
| yOffset + slideData.events[ j ].y1 * scale, |
| xOffset + slideData.events[ j ].x2 * scale, |
| yOffset + slideData.events[ j ].y2 * scale, |
| yOffset + slideData.events[ j ].color |
| ); |
| break; |
| case 'erase': |
| eraseWithSponge( getCanvas( template, drawings, board ).getContext( '2d' ), |
| xOffset + slideData.events[ j ].x * scale, |
| yOffset + slideData.events[ j ].y * scale |
| ); |
| break; |
| case 'selectboard': |
| selectBoard( slideData.events[ j ].board ); |
| break; |
| case 'clear': |
| getCanvas( template, drawings, board ).getContext( '2d' ).clearRect( 0, 0, width, height ); |
| getCanvas( template, drawings, board ).getContext( '2d' ).fill(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| drawings = drawings.sort( ( a, b ) => a.board > b.board && 1 || -1 ); |
| |
| mode = 0; |
| |
| return drawings; |
| } |
| |
| function addDrawings( slide, drawings ) { |
| var parent = slide.parentElement.parentElement; |
| var nextSlide = slide.parentElement.nextElementSibling; |
| |
| for ( var i = 0; i < drawings.length; i++ ) { |
| var newPDFPage = document.createElement( 'div' ); |
| newPDFPage.classList.add( 'pdf-page' ); |
| newPDFPage.style.height = Reveal.getConfig().height; |
| newPDFPage.append( drawings[ i ].canvas ); |
| //console.log("Add drawing", newPDFPage); |
| if ( nextSlide != null ) { |
| parent.insertBefore( newPDFPage, nextSlide ); |
| } else { |
| parent.append( newPDFPage ); |
| } |
| } |
| } |
| |
| /***************************************************************** |
| ** Drawings |
| ******************************************************************/ |
| |
| function drawWithBoardmarker( context, fromX, fromY, toX, toY, colorIdx ) { |
| if ( colorIdx == undefined ) colorIdx = color[ mode ]; |
| context.lineWidth = boardmarkerWidth; |
| context.lineCap = 'round'; |
| context.strokeStyle = boardmarkers[ colorIdx ].color; |
| context.beginPath(); |
| context.moveTo( fromX, fromY ); |
| context.lineTo( toX, toY ); |
| context.stroke(); |
| } |
| |
| function drawWithChalk( context, fromX, fromY, toX, toY, colorIdx ) { |
| if ( colorIdx == undefined ) colorIdx = color[ mode ]; |
| var brushDiameter = chalkWidth; |
| context.lineWidth = brushDiameter; |
| context.lineCap = 'round'; |
| context.fillStyle = chalks[ colorIdx ].color; // 'rgba(255,255,255,0.5)'; |
| context.strokeStyle = chalks[ colorIdx ].color; |
| |
| var opacity = 1.0; |
| context.strokeStyle = context.strokeStyle.replace( /[\d\.]+\)$/g, opacity + ')' ); |
| context.beginPath(); |
| context.moveTo( fromX, fromY ); |
| context.lineTo( toX, toY ); |
| context.stroke(); |
| // Chalk Effect |
| var length = Math.round( Math.sqrt( Math.pow( toX - fromX, 2 ) + Math.pow( toY - fromY, 2 ) ) / ( 5 / brushDiameter ) ); |
| var xUnit = ( toX - fromX ) / length; |
| var yUnit = ( toY - fromY ) / length; |
| for ( var i = 0; i < length; i++ ) { |
| if ( chalkEffect > ( Math.random() * 0.9 ) ) { |
| var xCurrent = fromX + ( i * xUnit ); |
| var yCurrent = fromY + ( i * yUnit ); |
| var xRandom = xCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2; |
| var yRandom = yCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2; |
| context.clearRect( xRandom, yRandom, Math.random() * 2 + 2, Math.random() + 1 ); |
| } |
| } |
| } |
| |
| function eraseWithSponge( context, x, y ) { |
| context.save(); |
| context.beginPath(); |
| context.arc( x + eraser.radius, y + eraser.radius, eraser.radius, 0, 2 * Math.PI, false ); |
| context.clip(); |
| context.clearRect( x - 1, y - 1, eraser.radius * 2 + 2, eraser.radius * 2 + 2 ); |
| context.restore(); |
| if ( mode == 1 && grid ) { |
| redrawGrid( x + eraser.radius, y + eraser.radius, eraser.radius ); |
| } |
| } |
| |
| |
| /** |
| * Show an overlay for the chalkboard. |
| */ |
| function showChalkboard() { |
| //console.log("showChalkboard"); |
| drawingCanvas[ 1 ].container.style.opacity = 1; |
| drawingCanvas[ 1 ].container.style.visibility = 'visible'; |
| mode = 1; |
| } |
| |
| |
| /** |
| * Closes open chalkboard. |
| */ |
| function closeChalkboard() { |
| drawingCanvas[ 1 ].container.style.opacity = 0; |
| drawingCanvas[ 1 ].container.style.visibility = 'hidden'; |
| lastX = null; |
| lastY = null; |
| mode = 0; |
| } |
| |
| /** |
| * Clear current canvas. |
| */ |
| function clearCanvas( id ) { |
| if ( id == 0 ) clearTimeout( slidechangeTimeout ); |
| drawingCanvas[ id ].context.clearRect( 0, 0, drawingCanvas[ id ].width, drawingCanvas[ id ].height ); |
| if ( id == 1 && grid ) drawGrid(); |
| } |
| |
| /** |
| * Draw grid on background |
| */ |
| function drawGrid() { |
| var context = drawingCanvas[ 1 ].context; |
| |
| drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height ); |
| drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2; |
| drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2; |
| |
| var scale = drawingCanvas[ 1 ].scale; |
| var xOffset = drawingCanvas[ 1 ].xOffset; |
| var yOffset = drawingCanvas[ 1 ].yOffset; |
| |
| var distance = grid.distance * scale; |
| |
| var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance; |
| for ( var x = fromX; x < drawingCanvas[ 1 ].width; x += distance ) { |
| context.beginPath(); |
| context.lineWidth = grid.width * scale; |
| context.lineCap = 'round'; |
| context.fillStyle = grid.color; |
| context.strokeStyle = grid.color; |
| context.moveTo( x, 0 ); |
| context.lineTo( x, drawingCanvas[ 1 ].height ); |
| context.stroke(); |
| } |
| var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance; |
| |
| for ( var y = fromY; y < drawingCanvas[ 1 ].height; y += distance ) { |
| context.beginPath(); |
| context.lineWidth = grid.width * scale; |
| context.lineCap = 'round'; |
| context.fillStyle = grid.color; |
| context.strokeStyle = grid.color; |
| context.moveTo( 0, y ); |
| context.lineTo( drawingCanvas[ 1 ].width, y ); |
| context.stroke(); |
| } |
| } |
| |
| function redrawGrid( centerX, centerY, diameter ) { |
| var context = drawingCanvas[ 1 ].context; |
| |
| drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height ); |
| drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2; |
| drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2; |
| |
| var scale = drawingCanvas[ 1 ].scale; |
| var xOffset = drawingCanvas[ 1 ].xOffset; |
| var yOffset = drawingCanvas[ 1 ].yOffset; |
| |
| var distance = grid.distance * scale; |
| |
| var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance; |
| |
| for ( var x = fromX + distance * Math.ceil( ( centerX - diameter - fromX ) / distance ); x <= fromX + distance * Math.floor( ( centerX + diameter - fromX ) / distance ); x += distance ) { |
| context.beginPath(); |
| context.lineWidth = grid.width * scale; |
| context.lineCap = 'round'; |
| context.fillStyle = grid.color; |
| context.strokeStyle = grid.color; |
| context.moveTo( x, centerY - Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) ); |
| context.lineTo( x, centerY + Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) ); |
| context.stroke(); |
| } |
| var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance; |
| for ( var y = fromY + distance * Math.ceil( ( centerY - diameter - fromY ) / distance ); y <= fromY + distance * Math.floor( ( centerY + diameter - fromY ) / distance ); y += distance ) { |
| context.beginPath(); |
| context.lineWidth = grid.width * scale; |
| context.lineCap = 'round'; |
| context.fillStyle = grid.color; |
| context.strokeStyle = grid.color; |
| context.moveTo( centerX - Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y ); |
| context.lineTo( centerX + Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y ); |
| context.stroke(); |
| } |
| } |
| |
| /** |
| * Set the color |
| */ |
| function setColor( index, record ) { |
| // protect against out of bounds (this could happen when |
| // replaying events recorded with different color settings). |
| if ( index >= pens[ mode ].length ) index = 0; |
| |
| color[ mode ] = index; |
| |
| if ( color[ mode ] < 0 ) { |
| // use eraser |
| changeCursor( drawingCanvas[ mode ].canvas, sponge ); |
| } |
| else { |
| changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] ); |
| } |
| } |
| |
| /** |
| * Set the board |
| */ |
| function selectBoard( boardIdx, record ) { |
| //console.log("Set board",boardIdx); |
| if ( board == boardIdx ) return; |
| |
| board = boardIdx; |
| redrawChalkboard( boardIdx ); |
| if ( record ) { |
| recordEvent( { type: 'selectboard' } ); |
| } |
| } |
| |
| function redrawChalkboard( boardIdx ) { |
| clearCanvas( 1 ); |
| var slideData = getSlideData( slideIndices, 1 ); |
| var index = 0; |
| var play = ( boardIdx == 0 ); |
| while ( index < slideData.events.length && slideData.events[ index ].time < Date.now() - slideStart ) { |
| if ( boardIdx == slideData.events[ index ].board ) { |
| playEvent( 1, slideData.events[ index ], Date.now() - slideStart ); |
| } |
| |
| index++; |
| } |
| } |
| |
| |
| /** |
| * Forward cycle color |
| */ |
| function cycleColorNext() { |
| color[ mode ] = ( color[ mode ] + 1 ) % pens[ mode ].length; |
| return color[ mode ]; |
| } |
| |
| /** |
| * Backward cycle color |
| */ |
| function cycleColorPrev() { |
| color[ mode ] = ( color[ mode ] + ( pens[ mode ].length - 1 ) ) % pens[ mode ].length; |
| return color[ mode ]; |
| } |
| |
| /***************************************************************** |
| ** Broadcast |
| ******************************************************************/ |
| |
| var eventQueue = []; |
| |
| document.addEventListener( 'received', function ( message ) { |
| if ( message.content && message.content.sender == 'chalkboard-plugin' ) { |
| // add message to queue |
| eventQueue.push( message ); |
| console.log( JSON.stringify( message ) ); |
| } |
| if ( eventQueue.length == 1 ) processQueue(); |
| } ); |
| |
| function processQueue() { |
| // take first message from queue |
| var message = eventQueue.shift(); |
| |
| // synchronize time with seminar host |
| slideStart = Date.now() - message.content.timestamp; |
| // set status |
| if ( mode < message.content.mode ) { |
| // open chalkboard |
| showChalkboard(); |
| } else if ( mode > message.content.mode ) { |
| // close chalkboard |
| closeChalkboard(); |
| } |
| if ( board != message.content.board ) { |
| board = message.content.board; |
| redrawChalkboard( board ); |
| }; |
| |
| switch ( message.content.type ) { |
| case 'showChalkboard': |
| showChalkboard(); |
| break; |
| case 'closeChalkboard': |
| closeChalkboard(); |
| break; |
| case 'erase': |
| erasePoint( message.content.x, message.content.y ); |
| break; |
| case 'draw': |
| drawSegment( message.content.fromX, message.content.fromY, message.content.toX, message.content.toY, message.content.color ); |
| break; |
| case 'clear': |
| clearSlide(); |
| break; |
| case 'selectboard': |
| selectBoard( message.content.board, true ); |
| break; |
| case 'resetSlide': |
| resetSlideDrawings(); |
| break; |
| case 'init': |
| storage = message.content.storage; |
| for ( var id = 0; id < 2; id++ ) { |
| drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height ); |
| drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2; |
| drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2; |
| } |
| clearCanvas( 0 ); |
| clearCanvas( 1 ); |
| if ( !playback ) { |
| slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 ); |
| } |
| if ( mode == 1 && message.content.mode == 0 ) { |
| setTimeout( closeChalkboard, transition + 50 ); |
| } |
| if ( mode == 0 && message.content.mode == 1 ) { |
| setTimeout( showChalkboard, transition + 50 ); |
| } |
| mode = message.content.mode; |
| board = message.content.board; |
| break; |
| default: |
| break; |
| } |
| |
| // continue with next message if queued |
| if ( eventQueue.length > 0 ) { |
| processQueue(); |
| } else { |
| storageChanged(); |
| } |
| } |
| |
| document.addEventListener( 'welcome', function ( user ) { |
| // broadcast storage |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| recipient: user.id, |
| type: 'init', |
| timestamp: Date.now() - slideStart, |
| storage: storage, |
| mode, |
| board |
| }; |
| document.dispatchEvent( message ); |
| } ); |
| |
| /***************************************************************** |
| ** Playback |
| ******************************************************************/ |
| |
| document.addEventListener( 'seekplayback', function ( event ) { |
| //console.log('event seekplayback ' + event.timestamp); |
| stopPlayback(); |
| if ( !playback || event.timestamp == 0 ) { |
| // in other cases startplayback fires after seeked |
| startPlayback( event.timestamp ); |
| } |
| //console.log('seeked'); |
| } ); |
| |
| |
| document.addEventListener( 'startplayback', function ( event ) { |
| //console.log('event startplayback ' + event.timestamp); |
| stopPlayback(); |
| playback = true; |
| startPlayback( event.timestamp ); |
| } ); |
| |
| document.addEventListener( 'stopplayback', function ( event ) { |
| //console.log('event stopplayback ' + (Date.now() - slideStart) ); |
| playback = false; |
| stopPlayback(); |
| } ); |
| |
| document.addEventListener( 'startrecording', function ( event ) { |
| //console.log('event startrecording ' + event.timestamp); |
| startRecording(); |
| } ); |
| |
| |
| function startRecording() { |
| resetSlide( true ); |
| slideStart = Date.now(); |
| } |
| |
| function startPlayback( timestamp, finalMode ) { |
| //console.log("playback " + timestamp ); |
| slideStart = Date.now() - timestamp; |
| closeChalkboard(); |
| mode = 0; |
| board = 0; |
| for ( var id = 0; id < 2; id++ ) { |
| clearCanvas( id ); |
| var slideData = getSlideData( slideIndices, id ); |
| //console.log( timestamp +" / " + JSON.stringify(slideData)); |
| var index = 0; |
| while ( index < slideData.events.length && slideData.events[ index ].time < ( Date.now() - slideStart ) ) { |
| playEvent( id, slideData.events[ index ], timestamp ); |
| index++; |
| } |
| |
| while ( playback && index < slideData.events.length ) { |
| timeouts[ id ].push( setTimeout( playEvent, slideData.events[ index ].time - ( Date.now() - slideStart ), id, slideData.events[ index ], timestamp ) ); |
| index++; |
| } |
| } |
| //console.log("Mode: " + finalMode + "/" + mode ); |
| if ( finalMode != undefined ) { |
| mode = finalMode; |
| } |
| if ( mode == 1 ) showChalkboard(); |
| //console.log("playback (ok)"); |
| |
| }; |
| |
| function stopPlayback() { |
| //console.log("stopPlayback"); |
| //console.log("Timeouts: " + timeouts[0].length + "/"+ timeouts[1].length); |
| for ( var id = 0; id < 2; id++ ) { |
| for ( var i = 0; i < timeouts[ id ].length; i++ ) { |
| clearTimeout( timeouts[ id ][ i ] ); |
| } |
| timeouts[ id ] = []; |
| } |
| }; |
| |
| function playEvent( id, event, timestamp ) { |
| //console.log( timestamp +" / " + JSON.stringify(event)); |
| //console.log( id + ": " + timestamp +" / " + event.time +" / " + event.type +" / " + mode ); |
| switch ( event.type ) { |
| case 'open': |
| if ( timestamp <= event.time ) { |
| showChalkboard(); |
| } else { |
| mode = 1; |
| } |
| |
| break; |
| case 'close': |
| if ( timestamp < event.time ) { |
| closeChalkboard(); |
| } else { |
| mode = 0; |
| } |
| break; |
| case 'clear': |
| clearCanvas( id ); |
| break; |
| case 'selectboard': |
| selectBoard( event.board ); |
| break; |
| case 'draw': |
| drawLine( id, event, timestamp ); |
| break; |
| case 'erase': |
| eraseCircle( id, event, timestamp ); |
| break; |
| } |
| }; |
| |
| function drawLine( id, event, timestamp ) { |
| var ctx = drawingCanvas[ id ].context; |
| var scale = drawingCanvas[ id ].scale; |
| var xOffset = drawingCanvas[ id ].xOffset; |
| var yOffset = drawingCanvas[ id ].yOffset; |
| draw[ id ]( ctx, xOffset + event.x1 * scale, yOffset + event.y1 * scale, xOffset + event.x2 * scale, yOffset + event.y2 * scale, event.color ); |
| }; |
| |
| function eraseCircle( id, event, timestamp ) { |
| var ctx = drawingCanvas[ id ].context; |
| var scale = drawingCanvas[ id ].scale; |
| var xOffset = drawingCanvas[ id ].xOffset; |
| var yOffset = drawingCanvas[ id ].yOffset; |
| |
| eraseWithSponge( ctx, xOffset + event.x * scale, yOffset + event.y * scale ); |
| }; |
| |
| function startErasing( x, y ) { |
| drawing = false; |
| erasing = true; |
| erasePoint( x, y ); |
| } |
| |
| function erasePoint( x, y ) { |
| var ctx = drawingCanvas[ mode ].context; |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| |
| recordEvent( { |
| type: 'erase', |
| x, |
| y |
| } ); |
| |
| if ( |
| x * scale + xOffset > 0 && |
| y * scale + yOffset > 0 && |
| x * scale + xOffset < drawingCanvas[ mode ].width && |
| y * scale + yOffset < drawingCanvas[ mode ].height |
| ) { |
| eraseWithSponge( ctx, x * scale + xOffset, y * scale + yOffset ); |
| } |
| } |
| |
| function stopErasing() { |
| erasing = false; |
| } |
| |
| function startDrawing( x, y ) { |
| drawing = true; |
| |
| var ctx = drawingCanvas[ mode ].context; |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| lastX = x * scale + xOffset; |
| lastY = y * scale + yOffset; |
| } |
| |
| function drawSegment( fromX, fromY, toX, toY, colorIdx ) { |
| var ctx = drawingCanvas[ mode ].context; |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| |
| recordEvent( { |
| type: 'draw', |
| color: colorIdx, |
| x1: fromX, |
| y1: fromY, |
| x2: toX, |
| y2: toY |
| } ); |
| |
| if ( |
| fromX * scale + xOffset > 0 && |
| fromY * scale + yOffset > 0 && |
| fromX * scale + xOffset < drawingCanvas[ mode ].width && |
| fromY * scale + yOffset < drawingCanvas[ mode ].height && |
| toX * scale + xOffset > 0 && |
| toY * scale + yOffset > 0 && |
| toX * scale + xOffset < drawingCanvas[ mode ].width && |
| toY * scale + yOffset < drawingCanvas[ mode ].height |
| ) { |
| draw[ mode ]( ctx, fromX * scale + xOffset, fromY * scale + yOffset, toX * scale + xOffset, toY * scale + yOffset, colorIdx ); |
| } |
| } |
| |
| function stopDrawing() { |
| drawing = false; |
| } |
| |
| |
| /***************************************************************** |
| ** User interface |
| ******************************************************************/ |
| |
| function setupCanvasEvents( canvas ) { |
| // TODO: check all touchevents |
| canvas.addEventListener( 'touchstart', function ( evt ) { |
| evt.preventDefault(); |
| //console.log("Touch start"); |
| if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) { |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| |
| var touch = evt.touches[ 0 ]; |
| mouseX = touch.pageX; |
| mouseY = touch.pageY; |
| if ( color[ mode ] < 0 ) { |
| startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale); |
| } |
| else { |
| startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); |
| } |
| } |
| }, passiveSupported ? { |
| passive: false |
| } : false ); |
| |
| canvas.addEventListener( 'touchmove', function ( evt ) { |
| evt.preventDefault(); |
| //console.log("Touch move"); |
| if ( drawing || erasing ) { |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| |
| var touch = evt.touches[ 0 ]; |
| mouseX = touch.pageX; |
| mouseY = touch.pageY; |
| |
| if ( drawing ) { |
| drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] ); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'draw', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board, |
| fromX: ( lastX - xOffset ) / scale, |
| fromY: ( lastY - yOffset ) / scale, |
| toX: ( mouseX - xOffset ) / scale, |
| toY: ( mouseY - yOffset ) / scale, |
| color: color[ mode ] |
| }; |
| document.dispatchEvent( message ); |
| |
| lastX = mouseX; |
| lastY = mouseY; |
| } else { |
| erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'erase', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board, |
| x: ( mouseX - xOffset ) / scale, |
| y: ( mouseY - yOffset ) / scale |
| }; |
| document.dispatchEvent( message ); |
| } |
| |
| } |
| }, false ); |
| |
| |
| canvas.addEventListener( 'touchend', function ( evt ) { |
| evt.preventDefault(); |
| stopDrawing(); |
| stopErasing(); |
| }, false ); |
| |
| canvas.addEventListener( 'mousedown', function ( evt ) { |
| evt.preventDefault(); |
| if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) { |
| //console.log( "mousedown: " + evt.button ); |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| |
| mouseX = evt.pageX; |
| mouseY = evt.pageY; |
| |
| if ( color[ mode ] < 0 || evt.button == 2 || evt.button == 1 ) { |
| if ( color[ mode ] >= 0 ) { |
| // show sponge |
| changeCursor( drawingCanvas[ mode ].canvas, sponge ); |
| } |
| startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'erase', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board, |
| x: ( mouseX - xOffset ) / scale, |
| y: ( mouseY - yOffset ) / scale |
| }; |
| document.dispatchEvent( message ); |
| } else { |
| startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); |
| } |
| } |
| } ); |
| |
| canvas.addEventListener( 'mousemove', function ( evt ) { |
| evt.preventDefault(); |
| //console.log("Mouse move"); |
| |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| |
| mouseX = evt.pageX; |
| mouseY = evt.pageY; |
| |
| if ( drawing || erasing ) { |
| var scale = drawingCanvas[ mode ].scale; |
| var xOffset = drawingCanvas[ mode ].xOffset; |
| var yOffset = drawingCanvas[ mode ].yOffset; |
| |
| mouseX = evt.pageX; |
| mouseY = evt.pageY; |
| |
| if ( drawing ) { |
| drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] ); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'draw', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board, |
| fromX: ( lastX - xOffset ) / scale, |
| fromY: ( lastY - yOffset ) / scale, |
| toX: ( mouseX - xOffset ) / scale, |
| toY: ( mouseY - yOffset ) / scale, |
| color: color[ mode ] |
| }; |
| document.dispatchEvent( message ); |
| |
| lastX = mouseX; |
| lastY = mouseY; |
| } else { |
| erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'erase', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board, |
| x: ( mouseX - xOffset ) / scale, |
| y: ( mouseY - yOffset ) / scale |
| }; |
| document.dispatchEvent( message ); |
| } |
| |
| } |
| } ); |
| |
| |
| canvas.addEventListener( 'mouseup', function ( evt ) { |
| evt.preventDefault(); |
| if ( color[ mode ] >= 0 ) { |
| changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] ); |
| } |
| if ( drawing || erasing ) { |
| stopDrawing(); |
| stopErasing(); |
| } |
| } ); |
| } |
| |
| function resize() { |
| //console.log("resize"); |
| // Resize the canvas and draw everything again |
| var timestamp = Date.now() - slideStart; |
| if ( !playback ) { |
| timestamp = getSlideDuration(); |
| } |
| |
| //console.log( drawingCanvas[0].scale + "/" + drawingCanvas[0].xOffset + "/" +drawingCanvas[0].yOffset ); |
| for ( var id = 0; id < 2; id++ ) { |
| drawingCanvas[ id ].width = window.innerWidth; |
| drawingCanvas[ id ].height = window.innerHeight; |
| drawingCanvas[ id ].canvas.width = drawingCanvas[ id ].width; |
| drawingCanvas[ id ].canvas.height = drawingCanvas[ id ].height; |
| drawingCanvas[ id ].context.canvas.width = drawingCanvas[ id ].width; |
| drawingCanvas[ id ].context.canvas.height = drawingCanvas[ id ].height; |
| |
| drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height ); |
| drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2; |
| drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2; |
| //console.log( drawingCanvas[id].scale + "/" + drawingCanvas[id].xOffset + "/" +drawingCanvas[id].yOffset ); |
| } |
| //console.log( window.innerWidth + "/" + window.innerHeight); |
| startPlayback( timestamp, mode, true ); |
| } |
| |
| Reveal.addEventListener( 'pdf-ready', function ( evt ) { |
| // console.log( "Create printouts when ready" ); |
| whenLoaded( createPrintout ); |
| }); |
| |
| Reveal.addEventListener( 'ready', function ( evt ) { |
| //console.log('ready'); |
| if ( !printMode ) { |
| window.addEventListener( 'resize', resize ); |
| |
| slideStart = Date.now() - getSlideDuration(); |
| slideIndices = Reveal.getIndices(); |
| if ( !playback ) { |
| startPlayback( getSlideDuration(), 0 ); |
| } |
| if ( Reveal.isAutoSliding() ) { |
| var event = new CustomEvent( 'startplayback' ); |
| event.timestamp = 0; |
| document.dispatchEvent( event ); |
| } |
| updateStorage(); |
| whenReady( addPageNumbers ); |
| } |
| } ); |
| Reveal.addEventListener( 'slidechanged', function ( evt ) { |
| // clearTimeout( slidechangeTimeout ); |
| //console.log('slidechanged'); |
| if ( !printMode ) { |
| slideStart = Date.now() - getSlideDuration(); |
| slideIndices = Reveal.getIndices(); |
| closeChalkboard(); |
| board = 0; |
| clearCanvas( 0 ); |
| clearCanvas( 1 ); |
| if ( !playback ) { |
| slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 ); |
| } |
| if ( Reveal.isAutoSliding() ) { |
| var event = new CustomEvent( 'startplayback' ); |
| event.timestamp = 0; |
| document.dispatchEvent( event ); |
| } |
| } |
| } ); |
| Reveal.addEventListener( 'fragmentshown', function ( evt ) { |
| // clearTimeout( slidechangeTimeout ); |
| //console.log('fragmentshown'); |
| if ( !printMode ) { |
| slideStart = Date.now() - getSlideDuration(); |
| slideIndices = Reveal.getIndices(); |
| closeChalkboard(); |
| board = 0; |
| clearCanvas( 0 ); |
| clearCanvas( 1 ); |
| if ( Reveal.isAutoSliding() ) { |
| var event = new CustomEvent( 'startplayback' ); |
| event.timestamp = 0; |
| document.dispatchEvent( event ); |
| } else if ( !playback ) { |
| startPlayback( getSlideDuration(), 0 ); |
| // closeChalkboard(); |
| } |
| } |
| } ); |
| Reveal.addEventListener( 'fragmenthidden', function ( evt ) { |
| // clearTimeout( slidechangeTimeout ); |
| //console.log('fragmenthidden'); |
| if ( !printMode ) { |
| slideStart = Date.now() - getSlideDuration(); |
| slideIndices = Reveal.getIndices(); |
| closeChalkboard(); |
| board = 0; |
| clearCanvas( 0 ); |
| clearCanvas( 1 ); |
| if ( Reveal.isAutoSliding() ) { |
| document.dispatchEvent( new CustomEvent( 'stopplayback' ) ); |
| } else if ( !playback ) { |
| startPlayback( getSlideDuration() ); |
| closeChalkboard(); |
| } |
| } |
| } ); |
| |
| Reveal.addEventListener( 'autoslideresumed', function ( evt ) { |
| //console.log('autoslideresumed'); |
| var event = new CustomEvent( 'startplayback' ); |
| event.timestamp = 0; |
| document.dispatchEvent( event ); |
| } ); |
| Reveal.addEventListener( 'autoslidepaused', function ( evt ) { |
| //console.log('autoslidepaused'); |
| document.dispatchEvent( new CustomEvent( 'stopplayback' ) ); |
| |
| // advance to end of slide |
| // closeChalkboard(); |
| startPlayback( getSlideDuration(), 0 ); |
| } ); |
| |
| function toggleNotesCanvas() { |
| if ( !readOnly ) { |
| if ( mode == 1 ) { |
| toggleChalkboard(); |
| notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)'; |
| notescanvas.style.pointerEvents = 'auto'; |
| } |
| else { |
| if ( notescanvas.style.pointerEvents != 'none' ) { |
| // hide notes canvas |
| if ( colorButtons ) { |
| notescanvas.querySelector( '.palette' ).style.visibility = 'hidden'; |
| } |
| notescanvas.style.background = 'rgba(0,0,0,0)'; |
| notescanvas.style.pointerEvents = 'none'; |
| } |
| else { |
| // show notes canvas |
| if ( colorButtons ) { |
| notescanvas.querySelector( '.palette' ).style.visibility = 'visible'; |
| } |
| notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)'; |
| notescanvas.style.pointerEvents = 'auto'; |
| |
| var idx = 0; |
| if ( color[ mode ] ) { |
| idx = color[ mode ]; |
| } |
| |
| setColor( idx, true ); |
| } |
| } |
| } |
| }; |
| |
| function toggleChalkboard() { |
| //console.log("toggleChalkboard " + mode); |
| if ( mode == 1 ) { |
| if ( !readOnly ) { |
| recordEvent( { type: 'close' } ); |
| } |
| closeChalkboard(); |
| |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'closeChalkboard', |
| timestamp: Date.now() - slideStart, |
| mode: 0, |
| board |
| }; |
| document.dispatchEvent( message ); |
| |
| |
| } else { |
| showChalkboard(); |
| if ( !readOnly ) { |
| recordEvent( { type: 'open' } ); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'showChalkboard', |
| timestamp: Date.now() - slideStart, |
| mode: 1, |
| board |
| }; |
| document.dispatchEvent( message ); |
| |
| var idx = 0; |
| |
| if ( rememberColor[ mode ] ) { |
| idx = color[ mode ]; |
| } |
| |
| setColor( idx, true ); |
| } |
| } |
| }; |
| |
| function clearSlide() { |
| recordEvent( { type: 'clear' } ); |
| clearCanvas( mode ); |
| } |
| |
| function clear() { |
| if ( !readOnly ) { |
| clearSlide(); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'clear', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board |
| }; |
| document.dispatchEvent( message ); |
| } |
| }; |
| |
| function colorIndex( idx ) { |
| if ( !readOnly ) { |
| setColor( idx, true ); |
| } |
| } |
| |
| function colorNext() { |
| if ( !readOnly ) { |
| let idx = cycleColorNext(); |
| setColor( idx, true ); |
| } |
| } |
| |
| function colorPrev() { |
| if ( !readOnly ) { |
| let idx = cycleColorPrev(); |
| setColor( idx, true ); |
| } |
| } |
| |
| function resetSlideDrawings() { |
| slideStart = Date.now(); |
| closeChalkboard(); |
| |
| clearCanvas( 0 ); |
| clearCanvas( 1 ); |
| |
| mode = 1; |
| var slideData = getSlideData(); |
| slideData.duration = 0; |
| slideData.events = []; |
| mode = 0; |
| var slideData = getSlideData(); |
| slideData.duration = 0; |
| slideData.events = []; |
| |
| updateStorage(); |
| } |
| |
| function resetSlide( force ) { |
| var ok = force || confirm( "Please confirm to delete chalkboard drawings on this slide!" ); |
| if ( ok ) { |
| //console.log("resetSlide "); |
| stopPlayback(); |
| resetSlideDrawings(); |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'resetSlide', |
| timestamp: Date.now() - slideStart, |
| mode, |
| board |
| }; |
| document.dispatchEvent( message ); |
| } |
| }; |
| |
| function resetStorage( force ) { |
| var ok = force || confirm( "Please confirm to delete all chalkboard drawings!" ); |
| if ( ok ) { |
| stopPlayback(); |
| slideStart = Date.now(); |
| clearCanvas( 0 ); |
| clearCanvas( 1 ); |
| if ( mode == 1 ) { |
| closeChalkboard(); |
| } |
| |
| storage = [ { |
| width: Reveal.getConfig().width, |
| height: Reveal.getConfig().height, |
| data: [] |
| }, |
| { |
| width: Reveal.getConfig().width, |
| height: Reveal.getConfig().height, |
| data: [] |
| } |
| ]; |
| |
| if ( config.storage ) { |
| sessionStorage.setItem( config.storage, null ) |
| } |
| // broadcast |
| var message = new CustomEvent( messageType ); |
| message.content = { |
| sender: 'chalkboard-plugin', |
| type: 'init', |
| timestamp: Date.now() - slideStart, |
| storage, |
| mode, |
| board |
| }; |
| document.dispatchEvent( message ); |
| } |
| }; |
| |
| this.toggleNotesCanvas = toggleNotesCanvas; |
| this.toggleChalkboard = toggleChalkboard; |
| this.colorIndex = colorIndex; |
| this.colorNext = colorNext; |
| this.colorPrev = colorPrev; |
| this.clear = clear; |
| this.reset = resetSlide; |
| this.resetAll = resetStorage; |
| this.download = downloadData; |
| this.updateStorage = updateStorage; |
| this.getData = getData; |
| this.configure = configure; |
| |
| |
| for ( var key in keyBindings ) { |
| if ( keyBindings[ key ] ) { |
| Reveal.addKeyBinding( keyBindings[ key ], RevealChalkboard[ key ] ); |
| } |
| }; |
| |
| return this; |
| }; |