/***************************************************************** ** Author: Asvin Goel, goel@telematique.eu ** ** A plugin for reveal.js adding a chalkboard. ** ** Version: 1.5.0 ** ** License: MIT license (see LICENSE.md) ** ** Credits: ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard ** Multi color support by Kurt Rinnert https://github.com/rinnert ** Compatibility with reveal.js v4 by Hakim El Hattab https://github.com/hakimel ******************************************************************/ window.RevealChalkboard = window.RevealChalkboard || { id: 'RevealChalkboard', init: function(deck) { initChalkboard(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, pen, 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 keyBindings = { toggleNotesCanvas: { keyCode: 67, key: 'C', description: 'Toggle notes canvas' }, toggleChalkboard: { keyCode: 66, key: 'B', description: 'Toggle chalkboard' }, clear: { keyCode: 171, key: '+', description: 'Clear drawings on slide' }, reset: { keyCode: 46, key: 'DEL', 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 = true; var toggleNotesButton = true; 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 drawings to be loaded and markdown to be parsed if ( document.querySelectorAll(".pdf-page").length && loaded !== null ) { callback(); } else { console.log("Wait for pdf pages to be created and drawings to be loaded"); setTimeout( whenReady, 500, callback ) } } if ( toggleChalkboardButton ) { //console.log("toggleChalkboardButton") var button = document.createElement( 'div' ); button.className = "chalkboard-button"; button.id = "toggle-chalkboard"; button.style.visibility = "visible"; button.style.position = "absolute"; button.style.zIndex = 30; button.style.fontSize = "24px"; button.style.left = toggleChalkboardButton.left || "30px"; button.style.bottom = toggleChalkboardButton.bottom || "30px"; button.style.top = toggleChalkboardButton.top || "auto"; button.style.right = toggleChalkboardButton.right || "auto"; button.innerHTML = '' document.querySelector(".reveal").appendChild( button ); } if ( toggleNotesButton ) { //console.log("toggleNotesButton") var button = document.createElement( 'div' ); button.className = "chalkboard-button"; button.id = "toggle-notes"; button.style.position = "absolute"; button.style.zIndex = 30; button.style.fontSize = "24px"; button.style.left = toggleNotesButton.left || "70px"; button.style.bottom = toggleNotesButton.bottom || "30px"; button.style.top = toggleNotesButton.top || "auto"; button.style.right = toggleNotesButton.right || "auto"; button.innerHTML = '' document.querySelector(".reveal").appendChild( button ); } //alert("Buttons"); 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 xLast = null; var yLast = null; var slideStart = Date.now(); var slideIndices = { h:0, v:0 }; var event = null; var timeouts = [ [], [] ]; var touchTimeout = null; var slidechangeTimeout = null; var playback = false; 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 = ''; colorButton.style.color = colors[i].color; colorButton.addEventListener("click", function(e) { colorIndex(e.target.parentElement.getAttribute("data-color")); }); list.appendChild( colorButton ); } palette.appendChild( list ); return palette; }; function setupDrawingCanvas( id ) { var container = document.createElement( 'div' ); container.id = drawingCanvas[id].id; container.classList.add( 'overlay' ); container.setAttribute( 'data-prevent-swipe', '' ); container.oncontextmenu = function() { return false; } container.style.cursor = pens[ id ][ color[id] ].cursor; 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.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 ); container.appendChild(palette); } if ( boardHandle ) { var handle = document.createElement( 'div' ); handle.classList.add('boardhandle'); handle.innerHTML=''; handle.querySelector("#previousboard").addEventListener("click", function(e) { e.preventDefault(); setBoard(board-1,true); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'setboard', timestamp: Date.now() - slideStart, index: board, status: { mode, board, color } }; document.dispatchEvent( message ); }); handle.querySelector("#nextboard").addEventListener("click", function(e) { e.preventDefault(); setBoard(board+1,true); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'setboard', timestamp: Date.now() - slideStart, index: board, status: { mode, board, color } }; document.dispatchEvent( message ); }); container.appendChild(handle); } } var sponge = document.createElement( 'img' ); sponge.src = eraser.src; sponge.id = "sponge"; sponge.style.visibility = "hidden"; sponge.style.position = "absolute"; container.appendChild( sponge ); drawingCanvas[id].sponge = sponge; var canvas = document.createElement( 'canvas' ); canvas.width = drawingCanvas[id].width; canvas.height = drawingCanvas[id].height; canvas.setAttribute( 'data-chalkboard', id ); canvas.style.cursor = pens[ id ][ color[id] ].cursor; container.appendChild( canvas ); drawingCanvas[id].canvas = canvas; drawingCanvas[id].context = canvas.getContext("2d"); 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); 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 updateStorage() { var json = JSON.stringify( storage ) if ( config.storage ) { sessionStorage.setItem( config.storage, json ) } return json; } /** * 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 ) { 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; } } storage[id].data.push( { slide: indices, 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 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 = getSlidesArray(); //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 ); var slide = slides[ storage[1].data[i].slide.h][ storage[1].data[i].slide.v ]; //console.log("Slide:", slide); addDrawings( slide, drawings ); } // Reveal.sync(); }; patImg.src = background[1]; } function getSlidesArray() { var horizontal = document.querySelectorAll('.slides > div.pdf-page > section, .slides > section'); var slides = []; var slidenumber = undefined; for ( var i=0; i < horizontal.length; i++) { if ( horizontal[i].parentElement.classList.contains("pdf-page") ) { // Horizontal slide if ( horizontal[i].getAttribute("data-slide-number") != slidenumber ) { // new slide slides.push([]); slides[slides.length-1].push(horizontal[i]); slidenumber = horizontal[i].getAttribute("data-slide-number"); } else { // fragment of same slide slides[slides.length-1][slides[slides.length-1].length-1] = horizontal[i]; } } else { // Vertical slides var vertical = horizontal[i].querySelectorAll('section'); slides.push([]); var slidenumber = undefined; for ( var j=0; j < vertical.length; j++) { if ( vertical[j].getAttribute("data-slide-number") != slidenumber ) { // new slide slides[slides.length-1].push(vertical[j]); slidenumber = vertical[j].getAttribute("data-slide-number"); } else { // fragment of same slide slides[slides.length-1][slides[slides.length-1].length-1] = vertical[j]; } } } } //console.log("Slides:", slides); return slides; } 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": for (var k = 1; k < slideData.events[j].curve.length; k++) { draw[1]( getCanvas(template,drawings,board).getContext("2d"), xOffset + slideData.events[j].curve[k-1].x*scale, yOffset + slideData.events[j].curve[k-1].y*scale, xOffset + slideData.events[j].curve[k].x*scale, yOffset + slideData.events[j].curve[k].y*scale ); } break; case "erase": for (var k = 0; k < slideData.events[j].curve.length; k++) { eraseWithSponge( getCanvas(template,drawings,board).getContext("2d"), xOffset + slideData.events[j].curve[k].x*scale, yOffset + slideData.events[j].curve[k].y*scale ); } break; case "setcolor": setColor(slideData.events[j].index); break; case "setboard": // Todo: create new canvas for each new index setBoard(slideData.events[j].index); //board = 0; 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.innerHTML = '

Drawing should be here!

'; 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){ context.lineWidth = boardmarkerWidth; context.lineCap = 'round'; context.strokeStyle = boardmarkers[color[mode]].color; context.beginPath(); context.moveTo(fromX, fromY); context.lineTo(toX, toY); context.stroke(); } function drawWithChalk(context,fromX,fromY,toX,toY) { var brushDiameter = chalkWidth; context.lineWidth = brushDiameter; context.lineCap = 'round'; context.fillStyle = chalks[color[mode]].color; // 'rgba(255,255,255,0.5)'; context.strokeStyle = chalks[color[mode]].color; /*var opacity = Math.min(0.8, Math.max(0,color[1].replace(/^.*,(.+)\)/,'$1') - 0.1)) + Math.random()*0.2;*/ 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 (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, y, eraser.radius, 0, 2 * Math.PI, false); context.clip(); context.clearRect(x - eraser.radius - 1, y - eraser.radius - 1, eraser.radius * 2 + 2, eraser.radius * 2 + 2); context.restore(); if ( mode == 1 && grid) { redrawGrid(x,y,eraser.radius); } } /** * Show an overlay for the chalkboard. */ function showChalkboard() { //console.log("showChalkboard"); clearTimeout(touchTimeout); touchTimeout = null; drawingCanvas[0].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden drawingCanvas[1].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden drawingCanvas[1].container.style.opacity = 1; drawingCanvas[1].container.style.visibility = 'visible'; mode = 1; } /** * Closes open chalkboard. */ function closeChalkboard() { clearTimeout(touchTimeout); touchTimeout = null; drawingCanvas[0].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden drawingCanvas[1].sponge.style.visibility = "hidden"; // make sure that the sponge from touch events is hidden drawingCanvas[1].container.style.opacity = 0; drawingCanvas[1].container.style.visibility = 'hidden'; xLast = null; yLast = null; event = 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 >= boardmarkers[mode].length ) index = 0; color[mode] = index; drawingCanvas[mode].canvas.style.cursor = pens[mode][color[mode]].cursor; if ( record ) { recordEvent( { type: "setcolor", index: index, begin: Date.now() - slideStart } ); updateStorage(); } } /** * Set the board */ function setBoard( index, record ) { //console.log("Set board",index); board = index; redrawChalkboard( board ); if ( record ) { recordEvent( { type: "setboard", index: board, begin: Date.now() - slideStart } ); updateStorage(); } } function redrawChalkboard( board ) { clearCanvas( 1 ); var slideData = getSlideData( slideIndices, 1 ); var index = 0; var play = ( board == 0 ); while ( index < slideData.events.length && slideData.events[index].begin < Date.now() - slideStart) { if ( slideData.events[index].type == "setboard" ) { play = ( board == slideData.events[index].index ); } else if ( play || slideData.events[index].type == "setcolor" ) { 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); } if ( eventQueue.length == 1 ) processQueue(); }); //console.log(JSON.stringify(message)); 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.status.mode ) { // open chalkboard showChalkboard(); } else if ( mode > message.content.status.mode ) { // close chalkboard closeChalkboard(); } if ( board != message.content.status.board ) { board = message.content.status.board; redrawChalkboard( board ); }; color = message.content.status.color; switch ( message.content.type ) { case 'showChalkboard': showChalkboard(); break; case 'closeChalkboard': closeChalkboard(); break; case 'startDrawing': startDrawing(message.content.x, message.content.y, message.content.erase); break; case 'startErasing': if ( message.content ) { message.content.type = "erase"; message.content.begin = Date.now() - slideStart; eraseWithSponge(drawingCanvas[mode].context, message.content.x, message.content.y); } break; case 'drawSegment': drawSegment(message.content.x, message.content.y, message.content.erase); break; case 'stopDrawing': stopDrawing(); break; case 'clear': clear(); break; case 'setcolor': setColor(message.content.index, true); break; case 'setboard': setBoard(message.content.index, true); break; case 'resetSlide': resetSlide(true); 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; break; default: break; } // continue with next message if queued if ( eventQueue.length > 0 ) { processQueue(); } else { updateStorage(); } } 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, status: { mode, board, color } }; 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 recordEvent( event ) { var slideData = getSlideData(); var i = slideData.events.length; while ( i > 0 && event.begin < slideData.events[i-1].begin ) { i--; } slideData.events.splice( i, 0, event); slideData.duration = Math.max( slideData.duration, Date.now() - slideStart ) + 1; } 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].begin < (Date.now() - slideStart) ) { playEvent( id, slideData.events[index], timestamp ); index++; } while ( playback && index < slideData.events.length ) { timeouts[id].push( setTimeout( playEvent, slideData.events[index].begin - (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.begin +" / " + event.type +" / " + mode ); switch ( event.type ) { case "open": if ( timestamp <= event.begin ) { showChalkboard(); } else { mode = 1; } break; case "close": if ( timestamp < event.begin ) { closeChalkboard(); } else { mode = 0; } break; case "clear": clearCanvas( id ); break; case "setcolor": setColor(event.index); break; case "setboard": setBoard(event.index); break; case "draw": drawCurve( id, event, timestamp ); break; case "erase": eraseCurve( id, event, timestamp ); break; } }; function drawCurve( id, event, timestamp ) { if ( event.curve.length > 1 ) { var ctx = drawingCanvas[id].context; var scale = drawingCanvas[id].scale; var xOffset = drawingCanvas[id].xOffset; var yOffset = drawingCanvas[id].yOffset; var stepDuration = ( event.end - event.begin )/ ( event.curve.length - 1 ); //console.log("---"); for (var i = 1; i < event.curve.length; i++) { if (event.begin + i * stepDuration <= (Date.now() - slideStart)) { //console.log( "Draw " + timestamp +" / " + event.begin + " + " + i + " * " + stepDuration ); draw[id](ctx, xOffset + event.curve[i-1].x*scale, yOffset + event.curve[i-1].y*scale, xOffset + event.curve[i].x*scale, yOffset + event.curve[i].y*scale); } else if ( playback ) { //console.log( "Cue " + timestamp +" / " + (Date.now() - slideStart) +" / " + event.begin + " + " + i + " * " + stepDuration + " = " + Math.max(0,event.begin + i * stepDuration - timestamp) ); timeouts.push( setTimeout( draw[id], Math.max(0,event.begin + i * stepDuration - (Date.now() - slideStart)), ctx, xOffset + event.curve[i-1].x*scale, yOffset + event.curve[i-1].y*scale, xOffset + event.curve[i].x*scale, yOffset + event.curve[i].y*scale ) ); } } } }; function eraseCurve( id, event, timestamp ) { if ( event.curve.length > 1 ) { var ctx = drawingCanvas[id].context; var scale = drawingCanvas[id].scale; var xOffset = drawingCanvas[id].xOffset; var yOffset = drawingCanvas[id].yOffset; var stepDuration = ( event.end - event.begin )/ event.curve.length; for (var i = 0; i < event.curve.length; i++) { if (event.begin + i * stepDuration <= (Date.now() - slideStart)) { eraseWithSponge(ctx, xOffset + event.curve[i].x*scale, yOffset + event.curve[i].y*scale); } else if ( playback ) { timeouts.push( setTimeout( eraseWithSponge, Math.max(0,event.begin + i * stepDuration - (Date.now() - slideStart)), ctx, xOffset + event.curve[i].x * scale, yOffset + event.curve[i].y * scale ) ); } } } }; function startDrawing( x, y, erase ) { var ctx = drawingCanvas[mode].context; var scale = drawingCanvas[mode].scale; var xOffset = drawingCanvas[mode].xOffset; var yOffset = drawingCanvas[mode].yOffset; xLast = x * scale + xOffset; yLast = y * scale + yOffset; if ( erase == true) { event = { type: "erase", begin: Date.now() - slideStart, end: null, curve: [{x: x, y: y}]}; drawingCanvas[mode].canvas.style.cursor = 'url("' + eraser.src + '") ' + eraser.radius + ' ' + eraser.radius + ', auto'; eraseWithSponge(ctx, x * scale + xOffset, y * scale + yOffset); } else { event = { type: "draw", begin: Date.now() - slideStart, end: null, curve: [{x: x, y: y}] }; } } function showSponge(x,y) { if ( event ) { event.type = "erase"; event.begin = Date.now() - slideStart; // show sponge image drawingCanvas[mode].sponge.style.left = (x - eraser.radius) +"px" ; drawingCanvas[mode].sponge.style.top = (y - eraser.radius) +"px" ; drawingCanvas[mode].sponge.style.visibility = "visible"; eraseWithSponge(drawingCanvas[mode].context,x,y); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'startErasing', timestamp: Date.now() - slideStart, status: { mode, board, color }, x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale }; document.dispatchEvent( message ); } } function drawSegment( x, y, erase ) { var ctx = drawingCanvas[mode].context; var scale = drawingCanvas[mode].scale; var xOffset = drawingCanvas[mode].xOffset; var yOffset = drawingCanvas[mode].yOffset; if ( !event ) { // safeguard if broadcast hickup startDrawing( x, y, erase ); } event.curve.push({x: x, y: y}); if(y * scale + yOffset < drawingCanvas[mode].height && x * scale + xOffset < drawingCanvas[mode].width) { if ( erase ) { eraseWithSponge(ctx, x * scale + xOffset, y * scale + yOffset); } else { draw[mode](ctx, xLast, yLast, x * scale + xOffset, y * scale + yOffset); } xLast = x * scale + xOffset; yLast = y * scale + yOffset; } } function stopDrawing() { if ( event ) { event.end = Date.now() - slideStart; if ( event.type == "erase" || event.curve.length > 1 ) { // do not save a line with a single point only recordEvent( event ); updateStorage(); } event = null; } } /***************************************************************** ** User interface ******************************************************************/ // TODO: check all touchevents document.addEventListener('touchstart', function(evt) { //console.log("Touch start"); if ( !readOnly && evt.target.getAttribute('data-chalkboard') == mode ) { // var ctx = drawingCanvas[mode].context; var scale = drawingCanvas[mode].scale; var xOffset = drawingCanvas[mode].xOffset; var yOffset = drawingCanvas[mode].yOffset; evt.preventDefault(); var touch = evt.touches[0]; mouseX = touch.pageX; mouseY = touch.pageY; startDrawing( (mouseX - xOffset)/scale, (mouseY-yOffset)/scale, false ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'startDrawing', timestamp: Date.now() - slideStart, status: { mode, board, color }, x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale, erase: false }; document.dispatchEvent( message ); /* xLast = mouseX; yLast = mouseY; event = { type: "draw", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}] }; */ touchTimeout = setTimeout( showSponge, 500, mouseX, mouseY ); } }, passiveSupported ? {passive: false} : false); document.addEventListener('touchmove', function(evt) { //console.log("Touch move"); clearTimeout( touchTimeout ); touchTimeout = null; if ( event ) { // var ctx = drawingCanvas[mode].context; 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 (mouseY < drawingCanvas[mode].height && mouseX < drawingCanvas[mode].width) { evt.preventDefault(); // move sponge if ( event.type == "erase" ) { drawingCanvas[mode].sponge.style.left = (mouseX - eraser.radius) +"px" ; drawingCanvas[mode].sponge.style.top = (mouseY - eraser.radius) +"px" ; } } drawSegment( (mouseX - xOffset)/scale, (mouseY-yOffset)/scale, ( event.type == "erase" ) ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'drawSegment', timestamp: Date.now() - slideStart, status: { mode, board, color }, x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale, erase: ( event.type == "erase" ) }; document.dispatchEvent( message ); /* if (mouseY < drawingCanvas[mode].height && mouseX < drawingCanvas[mode].width) { evt.preventDefault(); event.curve.push({x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}); if ( event.type == "erase" ) { drawingCanvas[mode].sponge.style.left = (mouseX - eraser.radius) +"px" ; drawingCanvas[mode].sponge.style.top = (mouseY - eraser.radius) +"px" ; eraseWithSponge(ctx, mouseX, mouseY); } else { draw[mode](ctx, xLast, yLast, mouseX, mouseY); } xLast = mouseX; yLast = mouseY; } */ } }, false); document.addEventListener('touchend', function(evt) { clearTimeout( touchTimeout ); touchTimeout = null; // hide sponge image drawingCanvas[mode].sponge.style.visibility = "hidden"; stopDrawing(); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', timestamp: Date.now() - slideStart, type: 'stopDrawing', status: { mode, board, color } }; document.dispatchEvent( message ); /* if ( event ) { event.end = Date.now() - slideStart; if ( event.type == "erase" || event.curve.length > 1 ) { // do not save a line with a single point only recordEvent( event ); } event = null; } */ }, false); document.addEventListener( 'mousedown', function( evt ) { //console.log("Mouse down"); //console.log( "Read only: " + readOnly ); if ( !readOnly && evt.target.getAttribute('data-chalkboard') == mode ) { //console.log( "mousedown: " + evt.button ); // var ctx = drawingCanvas[mode].context; var scale = drawingCanvas[mode].scale; var xOffset = drawingCanvas[mode].xOffset; var yOffset = drawingCanvas[mode].yOffset; mouseX = evt.pageX; mouseY = evt.pageY; startDrawing( (mouseX - xOffset)/scale, (mouseY-yOffset)/scale, ( evt.button == 2 || evt.button == 1) ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'startDrawing', timestamp: Date.now() - slideStart, status: { mode, board, color }, x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale, erase: ( evt.button == 2 || evt.button == 1) }; document.dispatchEvent( message ); /* xLast = mouseX; yLast = mouseY; if ( evt.button == 2) { event = { type: "erase", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}]}; drawingCanvas[mode].canvas.style.cursor = 'url("' + path + 'img/sponge.png") ' + eraser.radius + ' ' + eraser.radius + ', auto'; eraseWithSponge(ctx,mouseX,mouseY); } else { event = { type: "draw", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}] }; } */ } } ); document.addEventListener( 'mousemove', function( evt ) { //console.log("Mouse move"); if ( event ) { // var ctx = drawingCanvas[mode].context; var scale = drawingCanvas[mode].scale; var xOffset = drawingCanvas[mode].xOffset; var yOffset = drawingCanvas[mode].yOffset; mouseX = evt.pageX; mouseY = evt.pageY; drawSegment( (mouseX - xOffset)/scale, (mouseY-yOffset)/scale, ( event.type == "erase" ) ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'drawSegment', timestamp: Date.now() - slideStart, status: { mode, board, color }, x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale, erase: ( event.type == "erase" ) }; document.dispatchEvent( message ); /* event.curve.push({x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}); if(mouseY < drawingCanvas[mode].height && mouseX < drawingCanvas[mode].width) { if ( event.type == "erase" ) { eraseWithSponge(ctx,mouseX,mouseY); } else { draw[mode](ctx, xLast, yLast, mouseX,mouseY); } xLast = mouseX; yLast = mouseY; } */ } } ); document.addEventListener( 'mouseup', function( evt ) { drawingCanvas[mode].canvas.style.cursor = pens[mode][color[mode]].cursor; if ( event ) { stopDrawing(); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'stopDrawing', timestamp: Date.now() - slideStart, status: { mode, board, color } }; document.dispatchEvent( message ); /* if(evt.button == 2){ } event.end = Date.now() - slideStart; if ( event.type == "erase" || event.curve.length > 1 ) { // do not save a line with a single point only recordEvent( event ); } event = null; */ } } ); window.addEventListener( "resize", function() { //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( 'ready', function( evt ) { //console.log('ready'); if ( !printMode ) { 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(); } else { console.log("Create printouts when ready"); whenReady( createPrintout ); } }); Reveal.addEventListener( 'slidechanged', function( evt ) { // clearTimeout( slidechangeTimeout ); //console.log('slidechanged'); if ( !printMode ) { slideStart = Date.now() - getSlideDuration(); slideIndices = Reveal.getIndices(); closeChalkboard(); 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 ); } updateStorage(); } }); Reveal.addEventListener( 'fragmentshown', function( evt ) { // clearTimeout( slidechangeTimeout ); //console.log('fragmentshown'); if ( !printMode ) { slideStart = Date.now() - getSlideDuration(); slideIndices = Reveal.getIndices(); closeChalkboard(); 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(); 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"; } event = null; 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); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'setcolor', timestamp: Date.now() - slideStart, status: { mode, board, color }, index: idx }; document.dispatchEvent( message ); } } } }; function toggleChalkboard() { //console.log("toggleChalkboard " + mode); if ( mode == 1 ) { event = null; if ( !readOnly ) { recordEvent( { type:"close", begin: Date.now() - slideStart } ); updateStorage(); } closeChalkboard(); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'closeChalkboard', timestamp: Date.now() - slideStart, status: { mode, board, color } }; document.dispatchEvent( message ); } else { showChalkboard(); if ( !readOnly ) { recordEvent( { type:"open", begin: Date.now() - slideStart } ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'showChalkboard', timestamp: Date.now() - slideStart, status: { mode, board, color } }; document.dispatchEvent( message ); var idx = 0; if (rememberColor[mode]) { idx = color[mode]; } setColor(idx, true); // broadcast message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'setcolor', timestamp: Date.now() - slideStart, index: idx, status: { mode, board, color } }; document.dispatchEvent( message ); } } }; function clear() { if ( !readOnly ) { recordEvent( { type:"clear", begin: Date.now() - slideStart } ); clearCanvas( mode ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'clear', timestamp: Date.now() - slideStart, status: { mode, board, color } }; document.dispatchEvent( message ); } }; function colorIndex( idx ) { if ( !readOnly ) { setColor(idx, true); // recordEvent( { type: "setcolor", index: idx, begin: Date.now() - slideStart } ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'setcolor', timestamp: Date.now() - slideStart, index: idx, status: { mode, board, color } }; document.dispatchEvent( message ); } } function colorNext() { if ( !readOnly ) { let idx = cycleColorNext(); setColor(idx, true); // recordEvent( { type: "setcolor", index: idx, begin: Date.now() - slideStart } ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'setcolor', timestamp: Date.now() - slideStart, index: idx, status: { mode, board, color } }; document.dispatchEvent( message ); } } function colorPrev() { if ( !readOnly ) { let idx = cycleColorPrev(); setColor(idx, true); // recordEvent( { type: "setcolor", index: idx, begin: Date.now() - slideStart } ); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'setcolor', timestamp: Date.now() - slideStart, index: idx, status: { mode, board, color } }; document.dispatchEvent( message ); } } function resetSlide( force ) { var ok = force || confirm("Please confirm to delete chalkboard drawings on this slide!"); if ( ok ) { //console.log("resetSlide "); stopPlayback(); slideStart = Date.now(); event = null; 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(); // broadcast var message = new CustomEvent(messageType); message.content = { sender: 'chalkboard-plugin', type: 'resetSlide', timestamp: Date.now() - slideStart, status: { mode, board, color } }; 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 ) { event = null; closeChalkboard(); } storage = [ { width: Reveal.getConfig().width, height: Reveal.getConfig().height, data: []}, { width: Reveal.getConfig().width, height: Reveal.getConfig().height, data: []} ]; /* storage = [ { width: drawingCanvas[0].width - 2 * drawingCanvas[0].xOffset, height: drawingCanvas[0].height - 2 * drawingCanvas[0].yOffset, data: []}, { width: drawingCanvas[1].width, height: drawingCanvas[1].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: storage, status: { mode, board, color } }; document.dispatchEvent( message ); } }; /* this.drawWithBoardmarker = drawWithBoardmarker; this.drawWithChalk = drawWithChalk; this.startRecording = startRecording; */ 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; };