showoff 0.13.4 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2c16f1de4dfa9e1401b2d8882200287df206d61
4
- data.tar.gz: e4db72901b96e5faf1c35ad018e60d3c29a14865
3
+ metadata.gz: 0a62e1cad01e674aaa210d8ec7130949db0965fb
4
+ data.tar.gz: 88a4bb776f6c82aa0ddc05efc73dc1379567bafb
5
5
  SHA512:
6
- metadata.gz: 95c4ef9e3f7d94060af10f68bf7a7b0188c99b034eefc431e75e26cf4943d7697ccbf1d6f8c3cc884af6f97f03534af19b04c05e2cc00f615d6e1c12826667ed
7
- data.tar.gz: c9c46ab3f68540dd773d7140c05d3e3cb8c23646e0a945ed7b0b5c455418e9dd9a10837f4bf0e33ae0bbe6bac72e8056944eaf03e29f43dc0f94a857d37a6f22
6
+ metadata.gz: 7926f32b1b0c317bbd74355901e5cbe0ad9c8f827b1fb9401c13554ce5e90c2f73193ac83c87b42dfcfdc956a4968561389cc85c08ba55a25a2c869c720f63a0
7
+ data.tar.gz: 4accf69a75e69ce437ab85c81f6a1ca07ef22731f8d49ae2a434e3855b11297743d72aea711a3e90c54cfa26e8b122164b35c617e42068cfab51faf8c3f8a302
data/bin/showoff CHANGED
@@ -176,7 +176,13 @@ command :serve do |c|
176
176
  c.flag [:f, :file, :pres_file]
177
177
 
178
178
  c.desc 'Git URL to a repository containing the presentation'
179
- c.flag [:u, :url]
179
+ c.flag [:u, :url, :git_url]
180
+
181
+ c.desc 'Branch of the git repository to use'
182
+ c.flag [:git_branch]
183
+
184
+ c.desc 'Path of the presentation within the git repository'
185
+ c.flag [:git_path]
180
186
 
181
187
  c.action do |global_options,options,args|
182
188
 
@@ -220,7 +226,7 @@ To run it from presenter view, go to: [ #{url}/presenter ]
220
226
  "
221
227
 
222
228
  if options[:url]
223
- ShowOffUtils.clone(options[:url], options[:verbose]) do
229
+ ShowOffUtils.clone(options[:git_url], options[:git_branch], options[:git_path]) do
224
230
  ShowOff.run!(options) do |server|
225
231
  if options[:ssl]
226
232
  server.ssl = true
data/lib/showoff.rb CHANGED
@@ -413,6 +413,7 @@ class ShowOff < Sinatra::Application
413
413
 
414
414
  content += sl
415
415
  content += "</div>\n"
416
+ content += "<canvas class=\"annotations\"></canvas>\n"
416
417
  content += "</div>\n"
417
418
 
418
419
  final += update_commandline_code(content)
@@ -1461,6 +1462,9 @@ class ShowOff < Sinatra::Application
1461
1462
  when 'complete'
1462
1463
  EM.next_tick { settings.sockets.each{|s| s.send(control.to_json) } }
1463
1464
 
1465
+ when 'annotation', 'annotationConfig'
1466
+ EM.next_tick { (settings.sockets - settings.presenters).each{|s| s.send(control.to_json) } }
1467
+
1464
1468
  when 'feedback'
1465
1469
  filename = "#{settings.statsdir}/#{settings.feedback}"
1466
1470
  slide = control['slide']
@@ -1,3 +1,3 @@
1
1
  # No namespace here since ShowOff is a class and I'd have to inherit from
2
2
  # Sinatra::Application (which we don't want to load here)
3
- SHOWOFF_VERSION = '0.13.4'
3
+ SHOWOFF_VERSION = '0.14.0'
data/lib/showoff_utils.rb CHANGED
@@ -205,13 +205,17 @@ class ShowOffUtils
205
205
  end
206
206
 
207
207
  # clone a repo url, then run a provided block
208
- def self.clone(url, verbose=false)
208
+ def self.clone(url, branch=nil, path=nil)
209
209
  require 'tmpdir'
210
210
  Dir.mktmpdir do |dir|
211
- Dir.chdir dir do
212
- puts "Cloning presentation repository to #{dir}..." if verbose
213
- system('git', 'clone', '--depth', '1', url, '.')
211
+ if branch
212
+ system('git', 'clone', '-b', branch, '--single-branch', '--depth', '1', url, dir)
213
+ else
214
+ system('git', 'clone', '--depth', '1', url, dir)
215
+ end
214
216
 
217
+ dir = File.join(dir, path) if path
218
+ Dir.chdir dir do
215
219
  yield if block_given?
216
220
  end
217
221
  end
@@ -7,6 +7,7 @@
7
7
  -webkit-flex-flow: column;
8
8
  flex-flow: column;
9
9
  min-width: 630px;
10
+ min-height: 675px;
10
11
  }
11
12
 
12
13
  #timerSection,
@@ -217,15 +218,22 @@
217
218
  order: 1;
218
219
  min-width: 0px;
219
220
  }
221
+ #frame {
222
+ display: -webkit-flex;
223
+ display: flex;
224
+ -webkit-flex-flow: row;
225
+ flex-flow: row;
226
+ -webkit-flex: 10;
227
+ flex: 10;
228
+ }
220
229
 
221
230
  #preview {
222
- -webkit-flex: 1;
223
- flex: 1;
231
+ -webkit-flex: 10;
232
+ flex: 10;
224
233
  overflow: auto;
225
234
  background: #eee;
226
235
  padding: 1em;
227
236
  }
228
-
229
237
  #preview input[type=button].display {
230
238
  display: inline;
231
239
  }
@@ -245,6 +253,10 @@
245
253
  visibility: visible;
246
254
  color: #ccc;
247
255
  }
256
+ #preview .inverse .incremental.hidden {
257
+ color: #404040;
258
+ }
259
+
248
260
 
249
261
  #preso {
250
262
  -webkit-box-shadow:0 0 25px rgba(0,0,0,0.35);
@@ -253,6 +265,8 @@
253
265
  }
254
266
 
255
267
  #statusbar {
268
+ -webkit-flex: 0.5;
269
+ flex: 0.5;
256
270
  height: 22px;
257
271
  line-height: 22px;
258
272
  text-transform: uppercase;
@@ -267,7 +281,8 @@
267
281
  display: inline;
268
282
  }
269
283
  #enableFollower,
270
- #enableRemote {
284
+ #enableRemote,
285
+ #enableAnnotations {
271
286
  float: right;
272
287
  margin: 1px;
273
288
  padding: 0.1em;
@@ -279,6 +294,52 @@
279
294
  box-shadow: 0px 0px 15px 5px rgba(255, 255, 190, .75);
280
295
  }
281
296
 
297
+ #annotationToolbar {
298
+ display: -webkit-flex;
299
+ display: flex;
300
+ -webkit-flex-flow: column;
301
+ flex-flow: column;
302
+ -webkit-flex: 0.5;
303
+ flex: 0.5;
304
+ -webkit-order: 2;
305
+ order: 2;
306
+ min-width: 48px;
307
+ width: 48px;
308
+ padding: 0;
309
+ margin: 0;
310
+ background-color: black;
311
+ color: white;
312
+ text-align: center;
313
+ }
314
+ #annotationToolbar label {
315
+ font-size: 0.8em;
316
+ padding-top: 1em;
317
+ margin-bottom: 1px;
318
+ border-bottom: 1px solid #ccc;
319
+ }
320
+ #annotationToolbar i {
321
+ padding: 5px;
322
+ margin: 0 6px;
323
+ border-radius: 3px;
324
+ }
325
+ #annotationToolbar i:hover {
326
+ background-color: #555;
327
+ }
328
+ #annotationToolbar i.active {
329
+ background-color: #003d96;
330
+ border: 1px solid white;
331
+ }
332
+ #annotationToolbar i.lines.color1 { color: red }
333
+ #annotationToolbar i.lines.color2 { color: cyan }
334
+ #annotationToolbar i.lines.color3 { color: orange }
335
+ #annotationToolbar i.lines.color4 { color: green }
336
+ #annotationToolbar i.shapes.color1 { color: red }
337
+ #annotationToolbar i.shapes.color2 { color: cyan }
338
+ #annotationToolbar i.shapes.color3 { color: orange }
339
+ #annotationToolbar i.shapes.color4 { color: green }
340
+
341
+
342
+
282
343
  #bottom {
283
344
  display: -webkit-flex;
284
345
  display: flex;
@@ -20,6 +20,11 @@ pre {
20
20
  padding: .25em;
21
21
  }
22
22
 
23
+ /* when code blocks are embedded in a table, we don't need giant whitespace */
24
+ table pre {
25
+ margin: 0;
26
+ }
27
+
23
28
  /* applies to inline code only */
24
29
  code {
25
30
  padding: 2px 4px;
@@ -110,6 +115,16 @@ pre code {
110
115
  max-width: 100%;
111
116
  max-height: 100%;
112
117
  }
118
+
119
+ /* set up annotation overlays */
120
+ .slide canvas.annotations {
121
+ position: absolute;
122
+ top: 0;
123
+ left: 0;
124
+ width: 100%;
125
+ height: 100%;
126
+ pointer-events: none;
127
+ }
113
128
  }
114
129
 
115
130
  /* plain (non-bullet) text */
@@ -167,6 +182,18 @@ pre code {
167
182
  font-size: 70%;
168
183
  }
169
184
 
185
+ /* inverse slide style */
186
+ .slide.inverse {
187
+ color: #efefef;
188
+ background-color: #262626;
189
+ }
190
+ .slide.inverse code {
191
+ color: black;
192
+ }
193
+ .slide.inverse h1:first-child {
194
+ border-bottom: 3px solid #efefef;
195
+ }
196
+
170
197
  /**********************************
171
198
  *** List styling ***
172
199
  **********************************/
@@ -183,7 +210,7 @@ pre code {
183
210
  }
184
211
 
185
212
  /* ironically, normal lists have bullets and 'bullets' lists don't */
186
- .bullets > ul {
213
+ .bullets ul {
187
214
  list-style: none;
188
215
  padding-left: 0px;
189
216
  }
@@ -480,6 +507,9 @@ img#disconnected {
480
507
  }
481
508
 
482
509
  .navItem {
510
+ white-space: nowrap;
511
+ overflow: hidden;
512
+ text-overflow: ellipsis;
483
513
  padding: .5em 1.5em;
484
514
  }
485
515
 
@@ -0,0 +1,279 @@
1
+ function Annotate(params) {
2
+ if (!(this instanceof Annotate)) {
3
+ // the constructor was called without "new".
4
+ return new Annotate(params);;
5
+ }
6
+ params = typeof params !== 'undefined' ? params : {};
7
+
8
+ this.tool = 'draw';
9
+ this.context = null;
10
+ this.callbacks = null;
11
+ this.concurrent = 0;
12
+ this.imgData = null;
13
+
14
+ // I'd like this to be styled, but this is enough for MVP
15
+ this.lineWidth = params.lineWidth || 5;
16
+ this.lineColor = params.lineColor || "#df4b26";
17
+ this.fillColor = params.fillColor || "#F49638";
18
+ this.highlightRadius = params.highlightRadius || 20;
19
+ this.highlightPeriod = params.highlightPeriod || 500;
20
+ this.highlightFillColor = params.highlightFillColor || 'rgba(245, 213, 213, 0.5)';
21
+ this.highlightLineColor = params.highlightLineColor || '#cc0000';
22
+ this.zoom = params.zoom || 1; // Can you just die already?
23
+
24
+ this.setActiveCanvas = function(canvas) {
25
+ // dereference so we can use raw DOM objects or jQuery collections
26
+ canvas = canvas[0] || canvas;
27
+ if (canvas.nodeName.toLowerCase() !== 'canvas' ) {
28
+ throw new TypeError('Expected a DOM canvas element');
29
+ }
30
+ this.context = canvas.getContext("2d");
31
+
32
+ this.context.strokeStyle = this.lineColor;
33
+ this.context.fillStyle = this.fillColor;
34
+ this.context.lineWidth = this.lineWidth;
35
+ this.context.lineJoin = "round";
36
+ }
37
+
38
+ this.erase = function() {
39
+ if ( this.callbacks && this.callbacks['erase'] ) {
40
+ try {
41
+ this.callbacks['erase']();
42
+ } catch (e) {
43
+ console.log('Erase callback failed. ' + e);
44
+ }
45
+ }
46
+
47
+ this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
48
+ }
49
+
50
+ this.draw = function(x, y) {
51
+ if (this.tool == 'draw') {
52
+ // undo the effects of the zoom
53
+ x = x * this.zoom;
54
+ y = y * this.zoom;
55
+
56
+
57
+ if ( this.callbacks && this.callbacks['draw'] ) {
58
+ try {
59
+ this.callbacks['draw'](x, y);
60
+ } catch (e) {
61
+ console.log('Draw callback failed. ' + e);
62
+ }
63
+ }
64
+
65
+ this.context.strokeStyle = this.lineColor;
66
+ this.context.lineTo(x, y);
67
+ this.context.stroke();
68
+ }
69
+ }
70
+
71
+ this.click = function(x, y) {
72
+ // undo the effects of the zoom
73
+ x = x * this.zoom;
74
+ y = y * this.zoom;
75
+
76
+ if ( this.callbacks && this.callbacks['click'] ) {
77
+ try {
78
+ this.callbacks['click'](x, y);
79
+ } catch (e) {
80
+ console.log('Click callback failed. ' + e);
81
+ }
82
+ }
83
+
84
+ this.context.fillStyle = this.fillColor;
85
+ this.context.beginPath();
86
+ this.context.moveTo(x, y);
87
+
88
+ switch(this.tool) {
89
+ case 'leftArrow':
90
+ // IE doesn't understand Path2D
91
+ // var left = new Path2D('m'+x+','+y+' 40,-40 0,20 50,0 0,40 -50,0 0,20 -40,-40 z');
92
+ this.context.beginPath();
93
+ this.context.moveTo(x, y); x += 40; y -= 40;
94
+ this.context.lineTo(x, y); y += 20;
95
+ this.context.lineTo(x, y); x += 50;
96
+ this.context.lineTo(x, y); y += 40;
97
+ this.context.lineTo(x, y); x -= 50;
98
+ this.context.lineTo(x, y); y += 20;
99
+ this.context.lineTo(x, y); x -= 40; y -= 40;
100
+
101
+ this.context.fill();
102
+ break;
103
+
104
+ case 'rightArrow':
105
+ // var right = new Path2D('m'+x+','+y+' -40,-40 0,20 -50,0 0,40 50,0 0,20 40,-40 z');
106
+ this.context.beginPath();
107
+ this.context.moveTo(x, y); x -= 40; y -= 40;
108
+ this.context.lineTo(x, y); y += 20;
109
+ this.context.lineTo(x, y); x -= 50;
110
+ this.context.lineTo(x, y); y += 40;
111
+ this.context.lineTo(x, y); x += 50;
112
+ this.context.lineTo(x, y); y += 20;
113
+ this.context.lineTo(x, y); x += 40; y -= 40;
114
+
115
+ this.context.fill();
116
+ break;
117
+
118
+ case 'highlight':
119
+ // save the current state of the canvas so we can restore it
120
+ var width = this.context.canvas.width;
121
+ var height = this.context.canvas.height;
122
+
123
+ // save the canvas so we can restore it, but only if the user hasn't clicked multiple times.
124
+ if ( this.concurrent == 0 ) {
125
+ this.imgData = this.context.getImageData(0, 0, width, height);
126
+ }
127
+ this.concurrent += 1;
128
+
129
+ var period = this.highlightPeriod;
130
+ var start = null;
131
+
132
+ // Save the settings object so the animate() callback can get to it
133
+ var settings = this;
134
+
135
+ // can only accept a single timestamp argument
136
+ function animate(timestamp) {
137
+ if (!start) start = timestamp;
138
+ var progress = timestamp - start;
139
+
140
+ var linear = timestamp % period / period; // ranges from 0 to 1
141
+ var easing = Math.sin(linear * Math.PI); // simple easing to create some bounce
142
+ var radius = settings.highlightRadius * easing;
143
+
144
+ settings.context.clearRect(0, 0, width, height);
145
+ settings.context.beginPath();
146
+ settings.context.arc(x, y, radius, 0, Math.PI*2);
147
+
148
+ settings.context.fillStyle = settings.highlightFillColor;
149
+ settings.context.strokeStyle = settings.highlightLineColor;
150
+
151
+ settings.context.fill();
152
+ settings.context.stroke();
153
+
154
+ if (progress < 1000) {
155
+ window.requestAnimationFrame(animate);
156
+ }
157
+ else {
158
+ settings.concurrent -= 1;
159
+ if (settings.concurrent == 0) {
160
+ // We're done animating, restore the canvas
161
+ settings.context.clearRect(0, 0, width, height);
162
+ settings.context.putImageData(settings.imgData, 0, 0);
163
+ settings.context.strokeStyle = settings.lineColor;
164
+ settings.context.fillStyle = settings.fillColor;
165
+ settings.imgData = null;
166
+ }
167
+ }
168
+ }
169
+ window.requestAnimationFrame(animate);
170
+ break;
171
+ }
172
+ }
173
+
174
+ }
175
+
176
+
177
+ // Allow us to attach the annotations to canvases via jquery
178
+ // var annotations = new Annotate({lineColor: 'blue'});
179
+ // $('#overlay').annotate(annotations);
180
+ jQuery.fn.extend({
181
+ annotate: function (annotations) {
182
+ return this.each(function() {
183
+ if ( ! $(this).is( "canvas" ) ) {
184
+ throw new TypeError('The annotation functions only work on canvas elements');
185
+ }
186
+ if ( typeof annotations == 'undefined') {
187
+ // instantiate with defaults
188
+ annotations = new Annotate();
189
+ }
190
+ console.log('starting annotations');
191
+ var painting = false;
192
+
193
+ // the canvas cannot be css sized because reasons.
194
+ // Nor is it smart enough to understand how it fits into the styled page.
195
+ var height = $(this).parent().height();
196
+ var width = $(this).parent().width();
197
+
198
+ // We only want to do this the first time. It clears the canvas.
199
+ // Note that if the browser is resized, then the annotations are cleared.
200
+ if (this.height != height) {
201
+ this.height = height
202
+ }
203
+ if (this.width != width) {
204
+ this.width = width
205
+ }
206
+
207
+ annotations.setActiveCanvas(this);
208
+
209
+ // let the annotation overlay own mouse events.
210
+ // This means that clicking links or copying text will not work.
211
+ $(this).css('pointer-events', 'initial');
212
+
213
+ $(this).unbind( "mousedown" );
214
+ $(this).mousedown(function(e){
215
+ painting = true;
216
+ annotations.click(e.offsetX, e.offsetY)
217
+ });
218
+
219
+ $(this).unbind( "mouseup" );
220
+ $(this).mouseup(function(e){
221
+ painting = false;
222
+ });
223
+
224
+ $(this).unbind( "mouseleave" );
225
+ $(this).mouseleave(function(e){
226
+ painting = false;
227
+ });
228
+
229
+ $(this).unbind( "mouseleave" );
230
+ $(this).mousemove(function(e){
231
+ if(painting){
232
+ annotations.draw(e.offsetX, e.offsetY);
233
+ }
234
+ });
235
+ });
236
+ },
237
+ stopAnnotation: function () {
238
+ return this.each(function() {
239
+ if ( ! $(this).is( "canvas" ) ) {
240
+ throw new TypeError('The annotation functions only work on canvas elements');
241
+ }
242
+ // Ignore pointer events again to make the overlay inactive.
243
+ $(this).css('pointer-events', 'none');
244
+
245
+ $(this).unbind( "mousedown" );
246
+ $(this).unbind( "mouseup" );
247
+ $(this).unbind( "mouseleave" );
248
+ $(this).unbind( "mouseleave" );
249
+ });
250
+ },
251
+ annotationListener: function (settings) {
252
+ return this.each(function() {
253
+ if ( ! $(this).is( "canvas" ) ) {
254
+ throw new TypeError('The annotation functions only work on canvas elements');
255
+ }
256
+ if ( typeof settings == 'undefined') {
257
+ // instantiate with defaults
258
+ annotations = Annotate();
259
+ }
260
+ console.log('starting annotation listener');
261
+
262
+ // the canvas cannot be css sized because reasons.
263
+ height = $(this).parent().height();
264
+ width = $(this).parent().width();
265
+
266
+ // We only want to do this the first time. It clears the canvas.
267
+ // Note that if the browser is resized, then the annotations are cleared.
268
+ if (this.height != height) {
269
+ this.height = height
270
+ }
271
+ if (this.width != width) {
272
+ this.width = width
273
+ }
274
+
275
+ annotations.setActiveCanvas(this);
276
+ });
277
+ }
278
+
279
+ });
@@ -9,7 +9,7 @@ section = 'notes'; // which section the presenter has chosen to view
9
9
 
10
10
  $(document).ready(function(){
11
11
  // set up the presenter modes
12
- mode = { track: true, follow: true, update: true, slave: false, next: false, notes: false};
12
+ mode = { track: true, follow: true, update: true, slave: false, next: false, notes: false, annotations: false};
13
13
 
14
14
  // attempt to open another window for the presentation if the mode defaults
15
15
  // to enabling this. It does not by default, so this is likely a no-op.
@@ -23,8 +23,8 @@ $(document).ready(function(){
23
23
  $("#stopTimer").click(function() { stopTimer() });
24
24
 
25
25
  /* zoom slide to match preview size, then set up resize handler. */
26
- zoom();
27
- $(window).resize(function() { zoom(); });
26
+ zoom(true);
27
+ $(window).resize(function() { zoom(true); });
28
28
 
29
29
  $('#statslink').click(function(e) {
30
30
  presenterPopupToggle('/stats', e);
@@ -54,13 +54,71 @@ $(document).ready(function(){
54
54
  });
55
55
  }
56
56
 
57
+ // Hide with js so jquery knows what display property to assign when showing
58
+ toggleAnnotations();
59
+
60
+ $('#annotationToolbar i.tool').click(function(e) {
61
+ var action = $(this).attr('data-action');
62
+
63
+ switch (action) {
64
+ case 'erase':
65
+ annotations.erase();
66
+ break;
67
+
68
+ default:
69
+ $('#annotationToolbar i.tool').removeClass('active');
70
+ $(this).addClass('active');
71
+ annotations.tool = action;
72
+ if (slaveWindow) slaveWindow.annotations.tool = action;
73
+ sendAnnotationConfig('tool', action);
74
+ }
75
+
76
+ });
77
+
78
+ $('#annotationToolbar i.lines').click(function(e) {
79
+ $('#annotationToolbar i.lines').removeClass('active');
80
+ $(this).addClass('active');
81
+ var color = $(this).css('color');
82
+
83
+ annotations.lineColor = color;
84
+ if (slaveWindow) slaveWindow.annotations.lineColor = color;
85
+ sendAnnotationConfig('lineColor', color);
86
+ });
87
+
88
+ $('#annotationToolbar i.shapes').click(function(e) {
89
+ $('#annotationToolbar i.shapes').removeClass('active');
90
+ $(this).addClass('active');
91
+ var color = $(this).css('color');
92
+
93
+ annotations.fillColor = color;
94
+ if (slaveWindow) slaveWindow.annotations.fillColor = color;
95
+ sendAnnotationConfig('fillColor', color);
96
+ });
97
+
57
98
  $('#remoteToggle').change( toggleFollower );
58
99
  $('#followerToggle').change( toggleUpdater );
100
+ $('#annotationsToggle').change( toggleAnnotations );
59
101
 
60
102
  setInterval(function() { updatePace() }, 1000);
61
103
 
62
104
  // Tell the showoff server that we're a presenter
63
105
  register();
106
+
107
+ annotations.callbacks = {
108
+ erase: function() {
109
+ if (slaveWindow) slaveWindow.annotations.erase();
110
+ sendAnnotation('erase');
111
+ },
112
+ draw: function(x, y) {
113
+ if (slaveWindow) slaveWindow.annotations.draw(x,y);
114
+ sendAnnotation('draw', x, y);
115
+ },
116
+ click: function(x,y) {
117
+ if (slaveWindow) slaveWindow.annotations.click(x,y);
118
+ sendAnnotation('click', x, y);
119
+ }
120
+ };
121
+
64
122
  });
65
123
 
66
124
  function presenterPopupToggle(page, event) {
@@ -443,7 +501,7 @@ function postSlide() {
443
501
  $(notesWindow.document.body).html(notes);
444
502
  }
445
503
 
446
- var fileName = currentSlide.children().first().attr('ref');
504
+ var fileName = currentSlide.children('div').first().attr('ref');
447
505
  $('#slideFile').text(fileName);
448
506
 
449
507
  $("#notes div.form.wrapper").each(function(e) {
@@ -641,3 +699,22 @@ function toggleUpdater()
641
699
  mode.update = $("#followerToggle").prop("checked");
642
700
  update();
643
701
  }
702
+
703
+ /********************
704
+ Annotations
705
+ ********************/
706
+ function toggleAnnotations()
707
+ {
708
+ mode.annotations = $("#annotationsToggle").prop("checked");
709
+
710
+ if(mode.annotations) {
711
+ $('#annotationToolbar').show();
712
+ currentSlide.find('canvas.annotations').annotate(annotations);
713
+ $('canvas.annotations').show();
714
+ }
715
+ else {
716
+ $('#annotationToolbar').hide();
717
+ $('canvas.annotations').stopAnnotation();
718
+ $('canvas.annotations').hide();
719
+ }
720
+ }
data/public/js/showoff.js CHANGED
@@ -71,6 +71,9 @@ function setupPreso(load_slides, prefix) {
71
71
  zoom();
72
72
  $(window).resize(function() {zoom();});
73
73
 
74
+ // yes, this is a global
75
+ annotations = new Annotate();
76
+
74
77
  // Open up our control socket
75
78
  if(mode.track) {
76
79
  connectControlChannel();
@@ -168,7 +171,7 @@ function initializePresentation(prefix) {
168
171
  $("#preso").trigger("showoff:loaded");
169
172
  }
170
173
 
171
- function zoom() {
174
+ function zoom(presenter=false) {
172
175
  var preso = $("#preso");
173
176
  var hSlide = parseFloat(preso.height());
174
177
  var wSlide = parseFloat(preso.width());
@@ -186,6 +189,21 @@ function zoom() {
186
189
  // Don't use standard transform to avoid modifying Chrome
187
190
  preso.css("-moz-transform", "scale(" + newZoom + ")");
188
191
  preso.css("-moz-transform-origin", "0 0 0");
192
+
193
+ // correct the zoom factor for the presenter
194
+ if (presenter) {
195
+ // We only want to zoom if the canvas is actually zoomed. Firefox and IE
196
+ // should *not* be zoomed, so we want to exclude them. We do that by reading
197
+ // back the zoom property. It will return a string percentage in IE, which
198
+ // won't parse as a number, and Firefox simply returns undefined.
199
+ // Because reasons.
200
+
201
+ // TODO: When we fix the presenter on IE so the viewport isn't all wack, we
202
+ // may have to revisit this.
203
+
204
+ var zoomLevel = Number( preso.css('zoom') ) || 1;
205
+ annotations.zoom = 1 / zoomLevel
206
+ }
189
207
  }
190
208
 
191
209
  function setupSideMenu() {
@@ -252,6 +270,10 @@ function setupSideMenu() {
252
270
  closeMenu();
253
271
  });
254
272
 
273
+ $('#clearAnnotations').click(function() {
274
+ annotations.erase();
275
+ });
276
+
255
277
  $('#closeMenu, #sidebarExit').click(function() {
256
278
  closeMenu();
257
279
  });
@@ -337,15 +359,19 @@ function setupMenu() {
337
359
  // look for first header to use as a title
338
360
  if (headers.length > 0) {
339
361
  slideTitle = headers.first().text();
362
+
340
363
  } else {
341
- // if no header, look at content
364
+ // if no header, look at the first non-empty line of content
342
365
  content = $(slide).find(".content");
343
- slideTitle = content.text().substr(0, 20).trim();
366
+ slideTitle = content.text().split("\n").filter(Boolean)[0] || ''; // split() gives us an empty array when there's no content.
344
367
 
345
368
  // if no content (like photo only) fall back to slide name
346
369
  if (slideTitle == "") {
347
370
  slideTitle = content.attr('ref').split('/').pop();
348
371
  }
372
+
373
+ // just in case we've got any extra whitespace around.
374
+ slideTitle = slideTitle.trim();
349
375
  }
350
376
 
351
377
  var navLink = $("<a>")
@@ -443,6 +469,11 @@ function showSlide(back_step, updatepv) {
443
469
  return
444
470
  }
445
471
 
472
+ // stop annotations on old slide if we're a presenter
473
+ if(currentSlide && typeof slaveWindow !== 'undefined') {
474
+ currentSlide.find('canvas.annotations').first().stopAnnotation();
475
+ }
476
+
446
477
  currentSlide = slides.eq(slidenum)
447
478
 
448
479
  var transition = currentSlide.attr('data-transition')
@@ -481,13 +512,24 @@ function showSlide(back_step, updatepv) {
481
512
 
482
513
  var ret = setCurrentNotes();
483
514
 
484
- var fileName = currentSlide.children().first().attr('ref');
515
+ var fileName = currentSlide.children('div').first().attr('ref');
485
516
  $('#slideFilename').text(fileName);
486
517
 
487
518
  if (query.next) {
488
519
  $(currentSlide).find('li').removeClass('hidden');
489
520
  }
490
521
 
522
+ if(typeof slaveWindow == 'undefined') {
523
+ // hook up the annotations for viewing
524
+ currentSlide.find('canvas.annotations').annotationListener(annotations);
525
+ }
526
+ else {
527
+ if (mode.annotations) {
528
+ currentSlide.find('canvas.annotations').annotate(annotations);
529
+ }
530
+ }
531
+
532
+
491
533
  // Update presenter view, if we spawned one
492
534
  if (updatepv && 'presenterView' in window && ! mode.next) {
493
535
  var pv = window.presenterView;
@@ -861,10 +903,22 @@ function parseMessage(data) {
861
903
  case 'cancel':
862
904
  removeQuestion(command["questionID"]);
863
905
  break;
906
+
907
+ case 'annotation':
908
+ invokeAnnotation(command["type"], command["x"], command["y"]);
909
+ break;
910
+
911
+ case 'annotationConfig':
912
+ setting = command['setting'];
913
+ value = command['value'];
914
+
915
+ annotations[setting] = value;
916
+ break;
917
+
864
918
  }
865
919
  }
866
920
  catch(e) {
867
- console.log("Not a presenter!");
921
+ console.log("Not a presenter! " + e);
868
922
  }
869
923
 
870
924
  }
@@ -905,6 +959,34 @@ function sendFeedback(rating, feedback) {
905
959
  }
906
960
  }
907
961
 
962
+ function sendAnnotation(type, x, y) {
963
+ if (ws.readyState == WebSocket.OPEN) {
964
+ ws.send(JSON.stringify({ message: 'annotation', type: type, x: x, y: y }));
965
+ }
966
+ }
967
+
968
+ function sendAnnotationConfig(setting, value) {
969
+ if (ws.readyState == WebSocket.OPEN) {
970
+ ws.send(JSON.stringify({ message: 'annotationConfig', setting: setting, value: value }));
971
+ }
972
+ }
973
+
974
+ function invokeAnnotation(type, x, y) {
975
+ switch (type) {
976
+ case 'erase':
977
+ annotations.erase();
978
+ break;
979
+
980
+ case 'draw':
981
+ annotations.draw(x,y);
982
+ break;
983
+
984
+ case 'click':
985
+ annotations.click(x,y);
986
+ break;
987
+ }
988
+ }
989
+
908
990
  function feedbackActivity() {
909
991
  $('#hamburger').addClass('highlight');
910
992
  setTimeout(function() { $("#hamburger").removeClass('highlight') }, 75);
data/views/header.erb CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  <script type="text/javascript" src="<%= @asset_path %>/js/coffee-script.js"></script>
25
25
 
26
+ <script type="text/javascript" src="<%= @asset_path %>/js/annotations.js"></script>
26
27
  <script type="text/javascript" src="<%= @asset_path %>/js/showoff.js"></script>
27
28
 
28
29
  <% css_files.each do |css_file| %>
data/views/index.erb CHANGED
@@ -63,6 +63,8 @@
63
63
  <div id="editSlide" class="buttonWrapper">Edit Current Slide</div>
64
64
  <% end %>
65
65
  <hr>
66
+ <div id="clearAnnotations" class="buttonWrapper"><i class="fa fa-eraser"></i> Clear Annotations</div>
67
+ <hr>
66
68
  <% end %>
67
69
 
68
70
  <div id="closeMenu" class="buttonWrapper"><i class="fa fa-close"></i> Close</div>
data/views/presenter.erb CHANGED
@@ -74,9 +74,29 @@
74
74
  <div id="navigation" class="submenu"></div>
75
75
  </div>
76
76
  <div id="presenter">
77
- <div id="preview">
78
- <img id="disconnected" src="<%= @asset_path %>/css/disconnected-large.png" />
79
- <div id="preso">loading presentation...</div>
77
+ <div id="frame">
78
+ <div id="preview">
79
+ <img id="disconnected" src="<%= @asset_path %>/css/disconnected-large.png" />
80
+ <div id="preso">loading presentation...</div>
81
+ </div>
82
+ <div id="annotationToolbar">
83
+ <label>Tools</label>
84
+ <i class="fa fa-pencil tool default active" data-action="draw" aria-hidden="true"></i>
85
+ <i class="fa fa-arrow-right tool" data-action="rightArrow" aria-hidden="true"></i>
86
+ <i class="fa fa-arrow-left tool" data-action="leftArrow" aria-hidden="true"></i>
87
+ <i class="fa fa-bullseye tool" data-action="highlight" aria-hidden="true"></i>
88
+ <i class="fa fa-eraser tool" data-action="erase" aria-hidden="true"></i>
89
+ <label>Lines</label>
90
+ <i class="fa fa-square-o lines color1 active" aria-hidden="true"></i>
91
+ <i class="fa fa-square-o lines color2" aria-hidden="true"></i>
92
+ <i class="fa fa-square-o lines color3" aria-hidden="true"></i>
93
+ <i class="fa fa-square-o lines color4" aria-hidden="true"></i>
94
+ <label>Shapes</label>
95
+ <i class="fa fa-square shapes color1" aria-hidden="true"></i>
96
+ <i class="fa fa-square shapes color2" aria-hidden="true"></i>
97
+ <i class="fa fa-square shapes color3 active" aria-hidden="true"></i>
98
+ <i class="fa fa-square shapes color4" aria-hidden="true"></i>
99
+ </div>
80
100
  </div>
81
101
  <div id="statusbar">
82
102
  <span id="progress">
@@ -89,6 +109,10 @@
89
109
  <span id="enableFollower" title="Send slide change notifications.">
90
110
  <label for="followerToggle">Update Follower</label><input type="checkbox" id="followerToggle" checked />
91
111
  </span>
112
+ <span id="enableAnnotations" title="Enable the annotation system.">
113
+ <label for="annotationsToggle">Annotations</label><input type="checkbox" id="annotationsToggle" />
114
+ </span>
115
+
92
116
  </div>
93
117
  </div>
94
118
  </div>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: showoff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.4
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-10 00:00:00.000000000 Z
11
+ date: 2016-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -326,6 +326,7 @@ files:
326
326
  - public/css/titlebar/right.png
327
327
  - public/favicon.ico
328
328
  - public/js/TimeCircles.js
329
+ - public/js/annotations.js
329
330
  - public/js/coffee-script.js
330
331
  - public/js/highlight.pack.js
331
332
  - public/js/jTypeWriter.js