/***************************************************************** ** Author: Asvin Goel, goel@telematique.eu ** ** A plugin for reveal.js adding a chalkboard. ** ** Version: 0.3 ** ** License: MIT license (see LICENSE.md) ** ** Credits: ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard ******************************************************************/ var RevealChalkboard = window.RevealChalkboard || (function(){ var path = scriptPath(); function scriptPath() { // obtain plugin path from the script element var path; if (document.currentScript) { path = document.currentScript.src.slice(0, -13); } else { var sel = document.querySelector('script[src$="/chalkboard.js"]') if (sel) { path = sel.src.slice(0, -13); } } //console.log("Path: " + path); return path; } /***************************************************************** ** Configuration ******************************************************************/ var config = Reveal.getConfig().chalkboard || {}; var background, pen, draw, color; var theme = config.theme || "chalkboard"; switch ( theme ) { case "whiteboard": background = [ 'rgba(127,127,127,.1)' , path + 'img/whiteboard.png' ]; pen = [ path + 'img/boardmarker.png', path + 'img/boardmarker.png' ]; draw = [ drawWithPen , drawWithPen ]; color = [ 'rgba(0,0,255,1)', 'rgba(0,0,255,1)' ]; break; default: background = [ 'rgba(127,127,127,.1)' , path + 'img/blackboard.png' ]; pen = [ path + 'img/boardmarker.png', path + 'img/chalk.png' ]; draw = [ drawWithPen , drawWithChalk ]; color = [ 'rgba(0,0,255,1)', 'rgba(255,255,255,0.5)' ]; } if ( config.background ) background = config.background; if ( config.pen ) pen = config.pen; if ( config.draw ) draw = config.draw; if ( config.color ) color = config.color; var toggleChalkboardButton = config.toggleChalkboardButton == undefined ? true : config.toggleChalkboardButton; var toggleNotesButton = config.toggleNotesButton == undefined ? true : config.toggleNotesButton; var toggleEraserButton = config.toggleEraserButton == undefined ? true : config.toggleEraserButton; var transition = config.transition || 800; var readOnly = config.readOnly; // MARIO var eraseMode = false; /***************************************************************** ** Setup ******************************************************************/ var eraserDiameter = 20; if ( toggleChalkboardButton ) { var buttonC = document.createElement( 'div' ); buttonC.id = "toggle-chalkboard"; buttonC.style.position = "absolute"; buttonC.style.zIndex = 30; buttonC.style.fontSize = "20px"; buttonC.style.left = "10px"; buttonC.style.bottom = "10px"; buttonC.style.top = "auto"; buttonC.style.right = "auto"; buttonC.style.padding = "3px"; buttonC.style.borderRadius = "3px"; buttonC.style.color = "lightgrey"; buttonC.innerHTML = ''; document.querySelector(".reveal").appendChild( buttonC ); } if ( toggleNotesButton ) { var buttonN = document.createElement( 'div' ); buttonN.id = "toggle-notes"; buttonN.style.position = "absolute"; buttonN.style.zIndex = 30; buttonN.style.fontSize = "20px"; buttonN.style.left = "50px"; buttonN.style.bottom = "10px"; buttonN.style.top = "auto"; buttonN.style.right = "auto"; buttonN.style.padding = "3px"; buttonN.style.borderRadius = "3px"; buttonN.style.color = "lightgrey"; buttonN.innerHTML = ''; document.querySelector(".reveal").appendChild( buttonN ); } if ( toggleEraserButton ) { var buttonE = document.createElement( 'div' ); buttonE.id = "button-eraser"; buttonE.style.position = "absolute"; buttonE.style.zIndex = 30; buttonE.style.fontSize = "20px"; buttonE.style.left = "90px"; buttonE.style.bottom = "10px"; buttonE.style.top = "auto"; buttonE.style.right = "auto"; buttonE.style.padding = "3px"; buttonE.style.borderRadius = "3px"; buttonE.style.color = "lightgrey"; buttonE.innerHTML = ''; document.querySelector(".reveal").appendChild( buttonE ); } var drawingCanvas = [ {id: "notescanvas" }, {id: "chalkboard" } ]; setupDrawingCanvas(0); setupDrawingCanvas(1); var mode = 0; // 0: notes canvas, 1: 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 slidechangeTimeout = null; var playback = false; 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 = 'url("' + pen[ id ] + '"), auto'; 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.classList.add( '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; } } else { container.style.background = 'url("' + background[id] + '") repeat'; container.style.zIndex = "26"; } var sponge = document.createElement( 'img' ); sponge.src = path + 'img/sponge.png'; 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 = 'url("' + pen[ id ] + '"), auto'; 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: 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: []} ]; //console.log( JSON.stringify(storage)); if ( config.src != null ) { loadData( config.src ); } /** * Load data. */ function loadData( filename ) { var xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.readyState === 4) { storage = JSON.parse(xhr.responseText); for (var id = 0; id < storage.length; id++) { if ( drawingCanvas[id].width != storage[id].width || drawingCanvas[id].height != storage[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; } if ( config.readOnly ) { drawingCanvas[id].container.style.cursor = 'default'; drawingCanvas[id].canvas.style.cursor = 'default'; } } } else { config.readOnly = undefined; readOnly = undefined; console.warn( 'Failed to get file ' + filename +". ReadyState: " + xhr.readyState + ", Status: " + xhr.status); } }; 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 ); } } /** * Download data. */ function downloadData() { var a = document.createElement('a'); document.body.appendChild(a); try { a.download = "chalkboard.json"; var blob = new Blob( [ JSON.stringify( storage ) ], { 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; //console.log("ID: " + id + "/" + mode ); if (!indices) indices = slideIndices; 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 ) { return storage[id].data[i]; } } //console.log(JSON.stringify(indices) + " push"); storage[id].data.push( { slide: indices, events: [], duration: 0 } ); return storage[id].data[storage[id].data.length-1]; } function hasSlideData( indices, id ) { if ( id == undefined ) id = mode; if (!indices) indices = slideIndices; 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 ) { return storage[id].data[i].events.length > 0; } } return false; } /** * 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 ); function createPrintout( ) { // MARIO: we want to print the drawings //drawingCanvas[0].container.classList.remove( 'visible' ); // do not print notes canvas var patImg = new Image(); patImg.onload = function () { var nextSlide = []; 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; } // collect next-slides for all slides with board stuff for (var i = 0; i < storage[1].data.length; i++) { var h = storage[1].data[i].slide.h; var v = storage[1].data[i].slide.v; var f = storage[1].data[i].slide.f; var slide = f ? Reveal.getSlide(h,v,f) : Reveal.getSlide(h,v); nextSlide.push( slide.nextSibling ); } // go through board storage, paint image, insert slide for (var i = 0; i < storage[1].data.length; i++) { var h = storage[1].data[i].slide.h; var v = storage[1].data[i].slide.v; var f = storage[1].data[i].slide.f; var slide = f ? Reveal.getSlide(h,v,f) : Reveal.getSlide(h,v); var slideData = getSlideData( storage[1].data[i].slide, 1 ); var imgCanvas = document.createElement('canvas'); imgCanvas.width = 2*width; imgCanvas.height = 2*height; // MARIO: not sure why we have to multiply but 2, but now it looks much better // maybe a MacOS retina artifact var imgCtx = imgCanvas.getContext("2d"); imgCtx.imageSmoothingEnabled = true; // setup drawing context: for print, use black on white drawing imgCtx.fillStyle = "white"; color[1] = "black"; imgCtx.rect(0,0,imgCanvas.width,imgCanvas.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]( imgCtx, 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++) { erase( imgCtx, xOffset + slideData.events[j].curve[k].x*scale, yOffset + slideData.events[j].curve[k].y*scale ); } break; case "clear": var newSlide = document.createElement( 'section' ); newSlide.classList.add( 'future' ); newSlide.innerHTML = '

Drawing

'; newSlide.setAttribute("data-background", 'url("' + imgCanvas.toDataURL("image/png") +'")' ); slide.parentElement.insertBefore( newSlide, nextSlide[i] ); var imgCanvas = document.createElement('canvas'); imgCanvas.width = width; imgCanvas.height = height; var imgCtx = imgCanvas.getContext("2d"); imgCtx.fillStyle = imgCtx.createPattern( patImg ,'repeat'); imgCtx.rect(0,0,imgCanvas.width,imgCanvas.height); imgCtx.fill(); break; default: break; } } var newSlide = document.createElement( 'section' ); newSlide.classList.add( 'future' ); newSlide.innerHTML = '

Drawing

'; newSlide.setAttribute("data-background", 'url("' + imgCanvas.toDataURL("image/png") +'")' ); slide.parentElement.insertBefore( newSlide, nextSlide[i] ); } Reveal.sync(); }; patImg.src = background[1]; } /***************************************************************** ** Drawings ******************************************************************/ function drawWithPen(context,fromX,fromY,toX,toY){ context.lineWidth = 2; context.lineCap = 'round'; context.strokeStyle = color[0]; context.beginPath(); context.moveTo(fromX, fromY); context.lineTo(toX, toY); context.stroke(); } function drawWithChalk(context,fromX,fromY,toX,toY){ var brushDiameter = 2; context.lineWidth = brushDiameter; context.lineCap = 'round'; context.fillStyle = color[1]; // 'rgba(255,255,255,0.5)'; context.strokeStyle = color[1]; context.beginPath(); context.moveTo(fromX, fromY); context.lineTo(toX, toY); context.stroke(); } function erase(context,x,y){ context.save(); context.beginPath(); context.arc(x, y, eraserDiameter, 0, 2 * Math.PI, false); context.clip(); context.clearRect(x - eraserDiameter - 1, y - eraserDiameter - 1, eraserDiameter * 2 + 2, eraserDiameter * 2 + 2); context.restore(); } /** * Opens an overlay for the chalkboard. */ function showChalkboard() { 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.classList.add( 'visible' ); mode = 1; } /** * Closes open chalkboard. */ function closeChalkboard() { 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.classList.remove( 'visible' ); 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); } /***************************************************************** ** 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(); }); 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 startPlayback( timestamp, finalMode ) { updateReadOnlyMode(); //console.log("playback " + timestamp ); slideStart = Date.now() - timestamp; closeChalkboard(); mode = 0; for ( var id = 0; id < 2; id++ ) { clearCanvas( id ); /* MARIO: don't just call getSlideData, since it pushed slide data when nothing is found which somehow inserts black slides for printing */ if (hasSlideData( slideIndices, id )) { var slideData = getSlideData( slideIndices, id ); 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++; } } } if ( finalMode != undefined ) { mode = finalMode; } if( mode == 1 ) showChalkboard(); }; 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 "draw": drawCurve( id, event, timestamp ); break; case "erase": eraseCurve( id, event, timestamp ); break; } }; function drawCurve( id, event, timestamp ) { var ctx = drawingCanvas[id].context; var scale = drawingCanvas[id].scale; var xOffset = drawingCanvas[id].xOffset; var yOffset = drawingCanvas[id].yOffset; if ( event.curve.length > 1 ) { var stepDuration = ( event.end - event.begin )/ ( event.curve.length - 1 ); for (var i = 1; i < event.curve.length; i++) { if (event.begin + i * stepDuration <= (Date.now() - slideStart)) { 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 ) { 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 ) ); } } } else { // we need to record and play single points (for math equations) var x = xOffset + event.curve[0].x*scale; var y = yOffset + event.curve[0].y*scale; draw[id](ctx, x, y, x, y); } }; 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)) { erase(ctx, xOffset + event.curve[i].x*scale, yOffset + event.curve[i].y*scale); } else if ( playback ) { timeouts.push( setTimeout( erase, Math.max(0,event.begin + i * stepDuration - (Date.now() - slideStart)), ctx, xOffset + event.curve[i].x * scale, yOffset + event.curve[i].y * scale ) ); } } } }; /***************************************************************** ** User interface ******************************************************************/ // TODO: check all touchevents document.addEventListener('touchstart', function(evt) { 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; xLast = mouseX; yLast = mouseY; if (eraseMode) { drawingCanvas[mode].sponge.style.visibility = "visible"; event = { type: "erase", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}] }; } else { event = { type: "draw", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}] }; } } }, false); document.addEventListener('touchmove', function(evt) { 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]; // finger touch has force == 0 -> ignore // pencil touch has force != 0 -> process if (touch.force != 0) { mouseX = touch.pageX; mouseY = touch.pageY; if ((mouseX-xLast)*(mouseX-xLast) + (mouseY-yLast)*(mouseY-yLast) > 4.0) { 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 - eraserDiameter) +"px" ; drawingCanvas[mode].sponge.style.top = (mouseY - eraserDiameter) +"px" ; erase(ctx, mouseX, mouseY); } else { draw[mode](ctx, xLast, yLast, mouseX, mouseY); } xLast = mouseX; yLast = mouseY; } } } } }, false); document.addEventListener('touchend', function(evt) { if ( event ) { // hide sponge image drawingCanvas[mode].sponge.style.visibility = "hidden"; 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 ) { 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; mouseX = evt.pageX; mouseY = evt.pageY; xLast = mouseX; yLast = mouseY; if ( evt.button == 2 || eraseMode ) { 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") ' + eraserDiameter + ' ' + eraserDiameter + ', auto'; erase(ctx,mouseX,mouseY); } else { event = { type: "draw", begin: Date.now() - slideStart, end: null, curve: [{x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}] }; drawingCanvas[mode].canvas.style.cursor = 'url("' + pen[mode] + '"), auto'; draw[mode](ctx, xLast, yLast, mouseX,mouseY); } } }, true ); document.addEventListener( 'mousemove', function( evt ) { if ( event ) { mouseX = evt.pageX; mouseY = evt.pageY; if ((mouseX-xLast)*(mouseX-xLast) + (mouseY-yLast)*(mouseY-yLast) > 4.0) { var ctx = drawingCanvas[mode].context; var scale = drawingCanvas[mode].scale; var xOffset = drawingCanvas[mode].xOffset; var yOffset = drawingCanvas[mode].yOffset; event.curve.push({x: (mouseX - xOffset)/scale, y: (mouseY-yOffset)/scale}); if(mouseY < drawingCanvas[mode].height && mouseX < drawingCanvas[mode].width) { if ( event.type == "erase" ) { erase(ctx,mouseX,mouseY); } else { draw[mode](ctx, xLast, yLast, mouseX,mouseY); } xLast = mouseX; yLast = mouseY; } } } }); document.addEventListener( 'mouseup', function( evt ) { if ( event ) { if(evt.button == 2){ drawingCanvas[mode].canvas.style.cursor = 'url("' + pen[mode] + '"), auto'; } event.end = Date.now() - slideStart; // we need to record and play single points (for math equations) //if ( event.type == "erase" || event.curve.length > 1 ) { recordEvent( event ); } event = null; } } ); window.addEventListener( "resize", function() { //console.log("resize"); //console.log(Reveal.getScale()); // 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 ); } ); function updateReadOnlyMode() { if ( config.readOnly == undefined ) { readOnly = ( getSlideDuration() > 0 ); if ( readOnly ) { drawingCanvas[0].container.style.cursor = 'default'; drawingCanvas[1].container.style.cursor = 'default'; drawingCanvas[0].canvas.style.cursor = 'default'; drawingCanvas[1].canvas.style.cursor = 'default'; if ( notescanvas.style.pointerEvents != "none" ) { event = null; notescanvas.style.background = 'rgba(0,0,0,0)'; notescanvas.style.pointerEvents = "none"; } } else { drawingCanvas[0].container.style.cursor = 'url("' + pen[0] + '"), auto';; drawingCanvas[1].container.style.cursor = 'url("' + pen[1] + '"), auto';; drawingCanvas[0].canvas.style.cursor = 'url("' + pen[0] + '"), auto';; drawingCanvas[1].canvas.style.cursor = 'url("' + pen[1] + '"), auto';; } } } Reveal.addEventListener( 'ready', function( evt ) { //console.log('ready'); if ( !printMode ) { slideStart = Date.now(); slideIndices = Reveal.getIndices(); if ( !playback ) { startPlayback( getSlideDuration(), 0 ); } if ( Reveal.isAutoSliding() ) { var event = new CustomEvent('startplayback'); event.timestamp = 0; document.dispatchEvent( event ); } updateReadOnlyMode(); } else { createPrintout(); } }); Reveal.addEventListener( 'slidechanged', function( evt ) { if ( !printMode ) { slideStart = Date.now(); slideIndices = Reveal.getIndices(); closeChalkboard(); clearCanvas( 0 ); clearCanvas( 1 ); if ( !playback ) { // MARIO: show stuff immediately //slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 ); slidechangeTimeout = setTimeout( startPlayback, 50, getSlideDuration(), 0 ); } if ( Reveal.isAutoSliding() ) { var event = new CustomEvent('startplayback'); event.timestamp = 0; document.dispatchEvent( event ); } updateReadOnlyMode(); } }); Reveal.addEventListener( 'fragmentshown', function( evt ) { if ( !printMode ) { slideStart = Date.now(); 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 ); } updateReadOnlyMode(); } }); Reveal.addEventListener( 'fragmenthidden', function( evt ) { if ( !printMode ) { slideStart = Date.now(); slideIndices = Reveal.getIndices(); closeChalkboard(); clearCanvas( 0 ); clearCanvas( 1 ); if ( Reveal.isAutoSliding() ) { document.dispatchEvent( new CustomEvent('stopplayback') ); } else if ( !playback ) { startPlayback( getSlideDuration() ); closeChalkboard(); } updateReadOnlyMode(); } }); Reveal.addEventListener( 'autoslideresumed', function( evt ) { var event = new CustomEvent('startplayback'); event.timestamp = 0; document.dispatchEvent( event ); }); Reveal.addEventListener( 'autoslidepaused', function( evt ) { document.dispatchEvent( new CustomEvent('stopplayback') ); startPlayback( getSlideDuration(), 0 ); }); // check whether slide has blackboard scribbles, and then highlight icon function updateIcon() { if ( !printMode ) { var idx = Reveal.getIndices(); if (hasSlideData(idx, 1)) { buttonC.style.color = "red"; buttonC.style.fontSize = "28px"; buttonC.style.left = "6px"; buttonC.style.bottom = "6px"; } else { buttonC.style.color = "lightgrey"; buttonC.style.fontSize = "20px"; buttonC.style.left = "10px"; buttonC.style.bottom = "10px"; } } } Reveal.addEventListener( 'slidechanged', updateIcon ); Reveal.addEventListener( 'fragmentshown', updateIcon ); Reveal.addEventListener( 'fragmenthidden', updateIcon ); function toggleSponge() { if ( !readOnly ) { if (eraseMode) { eraseMode = false; drawingCanvas[mode].canvas.style.cursor = 'url("' + pen[mode] + '"), auto'; buttonE.style.color = "lightgrey"; } else { eraseMode = true; drawingCanvas[mode].canvas.style.cursor = 'url("' + path + 'img/sponge.png") ' + eraserDiameter + ' ' + eraserDiameter + ', auto'; buttonE.style.color = "#2a9ddf"; } } } 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" ) { event = null; notescanvas.style.background = 'rgba(0,0,0,0)'; notescanvas.style.pointerEvents = "none"; buttonN.style.color = "lightgrey"; } else { notescanvas.style.background = background[0]; //'rgba(255,0,0,0.5)'; notescanvas.style.pointerEvents = "auto"; buttonN.style.color = "#2a9ddf"; } } } }; function toggleChalkboard() { //console.log("toggleChalkboard " + mode); if ( mode == 1 ) { event = null; // MARIO if ( !readOnly ) recordEvent( { type:"close", begin: Date.now() - slideStart } ); closeChalkboard(); buttonC.style.color = "lightgrey"; } else { showChalkboard(); // MARIO if ( !readOnly ) recordEvent( { type:"open", begin: Date.now() - slideStart } ); buttonC.style.color = "#2a9ddf"; } }; function clear() { if ( !readOnly ) { recordEvent( { type:"clear", begin: Date.now() - slideStart } ); clearCanvas( mode ); } }; function resetSlide( force ) { var ok = force || confirm("Please confirm to delete chalkboard drawings on this slide!"); if ( ok ) { 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 = []; updateReadOnlyMode(); } }; 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: 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: []} ]; updateReadOnlyMode(); } }; this.drawWithPen = drawWithPen; this.drawWithChalk = drawWithChalk; this.toggleNotesCanvas = toggleNotesCanvas; this.toggleChalkboard = toggleChalkboard; this.clear = clear; this.reset = resetSlide; this.resetAll = resetStorage; this.download = downloadData; this.toggleSponge = toggleSponge; return this; })();