asciidoctor-revealjs 4.1.0 → 5.0.1

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