showoff 0.16.1 → 0.16.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/showoff +17 -8
- data/lib/showoff.rb +250 -56
- data/lib/showoff/version.rb +1 -1
- data/public/css/presenter.css +7 -0
- data/public/css/showoff.css +93 -1
- data/public/js/presenter.js +21 -2
- data/public/js/showoff.js +67 -15
- data/views/header.erb +2 -0
- data/views/header_mini.erb +2 -0
- data/views/stats.erb +45 -54
- metadata +16 -3
- data/public/zoomline-0.0.1.html +0 -2085
data/lib/showoff/version.rb
CHANGED
data/public/css/presenter.css
CHANGED
@@ -78,6 +78,9 @@
|
|
78
78
|
#links a.enabled {
|
79
79
|
background-color: #003d96;
|
80
80
|
}
|
81
|
+
#links a.warning {
|
82
|
+
background-color: #8e030a;
|
83
|
+
}
|
81
84
|
|
82
85
|
#links .mobile {
|
83
86
|
display: none;
|
@@ -109,6 +112,10 @@
|
|
109
112
|
color: #ffffff;
|
110
113
|
}
|
111
114
|
|
115
|
+
#presenterPopup #stats .zoomline {
|
116
|
+
color: #000;
|
117
|
+
}
|
118
|
+
|
112
119
|
#nextWindowConfirmation {
|
113
120
|
right: 0.25em;
|
114
121
|
left: auto;
|
data/public/css/showoff.css
CHANGED
@@ -843,6 +843,7 @@ form .element {
|
|
843
843
|
-webkit-border-radius: 5px;
|
844
844
|
-khtml-border-radius: 5px;
|
845
845
|
border-radius: 5px;
|
846
|
+
cursor: default;
|
846
847
|
}
|
847
848
|
/* and to the bar inside the row */
|
848
849
|
#stats div.bar {
|
@@ -864,6 +865,68 @@ form .element {
|
|
864
865
|
right: 0.25em;
|
865
866
|
}
|
866
867
|
|
868
|
+
/* Add the disclosure triangles */
|
869
|
+
#stats div.row:before {
|
870
|
+
font-family: FontAwesome;
|
871
|
+
font-style: normal;
|
872
|
+
font-weight: normal;
|
873
|
+
font-size: 1.25em;
|
874
|
+
text-decoration: inherit;
|
875
|
+
}
|
876
|
+
#stats div.row.top:hover:before {
|
877
|
+
position: absolute;
|
878
|
+
content: "\f0da"; /* fa-caret-right */
|
879
|
+
margin-left: -0.5em;
|
880
|
+
}
|
881
|
+
#stats div.row.top.active:before {
|
882
|
+
position: absolute;
|
883
|
+
content: "\f0d7"; /* fa-caret-down */
|
884
|
+
margin-left: -0.75em;
|
885
|
+
}
|
886
|
+
|
887
|
+
#stats h2 {
|
888
|
+
border-bottom: 1px solid #eee;
|
889
|
+
}
|
890
|
+
#stats #viewers,
|
891
|
+
#stats #elapsed {
|
892
|
+
text-align: center;
|
893
|
+
min-height: 2em;
|
894
|
+
}
|
895
|
+
#stats #viewers.zoomline,
|
896
|
+
#stats #elapsed.zoomline {
|
897
|
+
text-align: left;
|
898
|
+
}
|
899
|
+
#stats .zoomline .col.current,
|
900
|
+
#stats .zoomline .col.current .inner {
|
901
|
+
background-color: #ffb105;
|
902
|
+
min-width: 5px;
|
903
|
+
}
|
904
|
+
#stats .zoomline .col.current:hover {
|
905
|
+
min-width: 10px;
|
906
|
+
background: #ffb105;
|
907
|
+
background: linear-gradient(#efefec, #ffb105);
|
908
|
+
}
|
909
|
+
#stats .zoomline .col.current:hover .inner,
|
910
|
+
#stats .zoomline .col.current .inner:hover {
|
911
|
+
background-color: #f3708d;
|
912
|
+
}
|
913
|
+
|
914
|
+
|
915
|
+
#stats #stray,
|
916
|
+
#stats #idle {
|
917
|
+
display: none;
|
918
|
+
width: 75%;
|
919
|
+
margin: 1em auto;
|
920
|
+
padding: 0.5em;
|
921
|
+
border: 1px solid #ccc;
|
922
|
+
}
|
923
|
+
#stats #stray {
|
924
|
+
background-color: #8e030a;
|
925
|
+
}
|
926
|
+
#stats #idle {
|
927
|
+
background-color: #3a64c1;
|
928
|
+
}
|
929
|
+
|
867
930
|
/* style all three boxes */
|
868
931
|
#stats div#least,
|
869
932
|
#stats div#most,
|
@@ -903,6 +966,7 @@ form .element {
|
|
903
966
|
}
|
904
967
|
#stats div#all div.bar {
|
905
968
|
background: #66b366;
|
969
|
+
border-color: #000;
|
906
970
|
}
|
907
971
|
|
908
972
|
/* detail view */
|
@@ -944,7 +1008,8 @@ form .element {
|
|
944
1008
|
.callout.exercise,
|
945
1009
|
.callout.stop,
|
946
1010
|
.callout.thumbsup,
|
947
|
-
.callout.conversation
|
1011
|
+
.callout.conversation,
|
1012
|
+
.callout.glossary {
|
948
1013
|
padding-left: 3em;
|
949
1014
|
}
|
950
1015
|
|
@@ -955,6 +1020,7 @@ form .element {
|
|
955
1020
|
.callout.stop:before { content: "\f05e"; } /* fa-ban */
|
956
1021
|
.callout.thumbsup:before { content: "\f164"; } /* fa-thumbs-up */
|
957
1022
|
.callout.conversation:before { content: "\f0e5"; } /* fa-comment */
|
1023
|
+
.callout.glossary:before { content: "\f02d"; } /* fa-book */
|
958
1024
|
|
959
1025
|
/* callouts used on error pages */
|
960
1026
|
.error .callout {
|
@@ -970,6 +1036,32 @@ form .element {
|
|
970
1036
|
/**********************
|
971
1037
|
*** end callouts ***
|
972
1038
|
**********************/
|
1039
|
+
.callout.glossary a.label,
|
1040
|
+
ul.glossary.terms a.label {
|
1041
|
+
display: block;
|
1042
|
+
font-weight: 600;
|
1043
|
+
text-decoration: none;
|
1044
|
+
}
|
1045
|
+
ul.glossary.terms a.return {
|
1046
|
+
text-decoration: none;
|
1047
|
+
padding-right: 3em;
|
1048
|
+
}
|
1049
|
+
a.term {
|
1050
|
+
text-decoration: none;
|
1051
|
+
}
|
1052
|
+
a.term:after {
|
1053
|
+
font-family: FontAwesome;
|
1054
|
+
font-size: 0.8em;
|
1055
|
+
vertical-align: super;
|
1056
|
+
content: "\f27b";
|
1057
|
+
text-decoration: none;/* fa-commenting-o */
|
1058
|
+
}
|
1059
|
+
|
1060
|
+
|
1061
|
+
/**********************
|
1062
|
+
*** glossary ***
|
1063
|
+
**********************/
|
1064
|
+
|
973
1065
|
|
974
1066
|
/* Render hidden headlines so that when we print with wkhtmltopdf, we can use section
|
975
1067
|
titles for page headers. it would make sense to put this in the print section, but
|
data/public/js/presenter.js
CHANGED
@@ -126,6 +126,27 @@ $(document).ready(function(){
|
|
126
126
|
|
127
127
|
setInterval(function() { updatePace() }, 1000);
|
128
128
|
|
129
|
+
setInterval(function() {
|
130
|
+
$.getJSON("/stats_data", function( json ) {
|
131
|
+
var percent = json['stray_p'];
|
132
|
+
if(percent > 25) {
|
133
|
+
$('#topbar #statslink').addClass('warning');
|
134
|
+
$('#topbar #statslink').attr('title', percent + "% of your audience is not viewing the same slide you are.");
|
135
|
+
}
|
136
|
+
else {
|
137
|
+
$('#stray').hide(); // in case the popup is open
|
138
|
+
$('#topbar #statslink').removeClass('warning');
|
139
|
+
$('#topbar #statslink').attr('title', "");
|
140
|
+
}
|
141
|
+
|
142
|
+
if( $('#presenterPopup #stats').is(':visible') ) {
|
143
|
+
setupStats(json);
|
144
|
+
}
|
145
|
+
});
|
146
|
+
|
147
|
+
}, 30000);
|
148
|
+
|
149
|
+
|
129
150
|
// Tell the showoff server that we're a presenter
|
130
151
|
register();
|
131
152
|
|
@@ -172,8 +193,6 @@ function presenterPopupToggle(page, event) {
|
|
172
193
|
content.append($(data).siblings('#wrapper').html());
|
173
194
|
popup.append(content);
|
174
195
|
|
175
|
-
setupStats(); // this function is in showoff.js because /stats does not load presenter.js
|
176
|
-
|
177
196
|
$('body').append(popup);
|
178
197
|
popup.slideDown(200); // #presenterPopup is display: none by default
|
179
198
|
});
|
data/public/js/showoff.js
CHANGED
@@ -448,6 +448,10 @@ function currentSlideFromName(name) {
|
|
448
448
|
var count = 0;
|
449
449
|
if(name.length > 0 ) {
|
450
450
|
slides.each(function(s, slide) {
|
451
|
+
if (name == $(slide).attr("id") ) {
|
452
|
+
found = count;
|
453
|
+
return false;
|
454
|
+
}
|
451
455
|
if (name == $(slide).find(".content").attr("ref") ) {
|
452
456
|
found = count;
|
453
457
|
return false;
|
@@ -460,12 +464,13 @@ function currentSlideFromName(name) {
|
|
460
464
|
|
461
465
|
function currentSlideFromParams() {
|
462
466
|
var result;
|
463
|
-
|
464
|
-
|
467
|
+
// Match numeric slide hashes: #241
|
468
|
+
if (result = window.location.hash.match(/^#([0-9]+)$/)) {
|
469
|
+
return result[1] - 1;
|
465
470
|
}
|
466
|
-
|
467
|
-
|
468
|
-
return currentSlideFromName(
|
471
|
+
// Match slide, with optional internal mark: #slideName(+internal)
|
472
|
+
else if (result = window.location.hash.match(/^#([^+]+)\+?(.*)?$/)) {
|
473
|
+
return currentSlideFromName(result[1]);
|
469
474
|
}
|
470
475
|
}
|
471
476
|
|
@@ -1053,18 +1058,24 @@ function feedbackActivity() {
|
|
1053
1058
|
setTimeout(function() { $("#hamburger").removeClass('highlight') }, 75);
|
1054
1059
|
}
|
1055
1060
|
|
1056
|
-
function track() {
|
1061
|
+
function track(current) {
|
1057
1062
|
if (mode.track && ws.readyState == WebSocket.OPEN) {
|
1058
|
-
var slideName
|
1059
|
-
|
1060
|
-
|
1063
|
+
var slideName = $("#slideFilename").text() || $("#slideFile").text(); // yey for consistency
|
1064
|
+
|
1065
|
+
if(current) {
|
1066
|
+
ws.send(JSON.stringify({ message: 'track', slide: slideName}));
|
1067
|
+
}
|
1068
|
+
else {
|
1069
|
+
var slideEndTime = new Date().getTime();
|
1070
|
+
var elapsedTime = slideEndTime - slideStartTime;
|
1061
1071
|
|
1062
|
-
|
1063
|
-
|
1072
|
+
// reset the timer
|
1073
|
+
slideStartTime = slideEndTime;
|
1064
1074
|
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1075
|
+
if (elapsedTime > 1000) {
|
1076
|
+
elapsedTime /= 1000;
|
1077
|
+
ws.send(JSON.stringify({ message: 'track', slide: slideName, time: elapsedTime}));
|
1078
|
+
}
|
1068
1079
|
}
|
1069
1080
|
}
|
1070
1081
|
}
|
@@ -1170,6 +1181,9 @@ function postSlide() {
|
|
1170
1181
|
}
|
1171
1182
|
|
1172
1183
|
$('#notes').html(notes);
|
1184
|
+
|
1185
|
+
// tell Showoff what slide we ended up on
|
1186
|
+
track(true);
|
1173
1187
|
}
|
1174
1188
|
}
|
1175
1189
|
|
@@ -1620,12 +1634,50 @@ function togglePause() {
|
|
1620
1634
|
Stats page
|
1621
1635
|
********************/
|
1622
1636
|
|
1623
|
-
function setupStats()
|
1637
|
+
function setupStats(data)
|
1624
1638
|
{
|
1625
1639
|
$("#stats div#all div.detail").hide();
|
1626
1640
|
$("#stats div#all div.row").click(function() {
|
1641
|
+
$(this).toggleClass('active');
|
1627
1642
|
$(this).find("div.detail").slideToggle("fast");
|
1628
1643
|
});
|
1644
|
+
|
1645
|
+
['stray', 'idle'].forEach(function(stat){
|
1646
|
+
var percent = data[stat+'_p'];
|
1647
|
+
var selector = '#'+stat;
|
1648
|
+
|
1649
|
+
if(percent > 25) {
|
1650
|
+
$(selector).show();
|
1651
|
+
$(selector+' .label').text(percent+'%');
|
1652
|
+
}
|
1653
|
+
else {
|
1654
|
+
$(selector).hide();
|
1655
|
+
}
|
1656
|
+
});
|
1657
|
+
|
1658
|
+
var location = window.location.pathname == '/presenter' ? '#' : '/#';
|
1659
|
+
var viewers = data['viewers'];
|
1660
|
+
if (viewers) {
|
1661
|
+
if (viewers.length == 1 && viewers[0][3] == 'current') {
|
1662
|
+
$("#viewers").removeClass('zoomline');
|
1663
|
+
$("#viewers").text("All audience members are viewing the presenter's slide.");
|
1664
|
+
}
|
1665
|
+
else {
|
1666
|
+
$("#viewers").zoomline({
|
1667
|
+
max: data['viewmax'],
|
1668
|
+
data: viewers,
|
1669
|
+
click: function(element) { window.location = (location + element.attr("data-left")); }
|
1670
|
+
});
|
1671
|
+
}
|
1672
|
+
}
|
1673
|
+
|
1674
|
+
if (data['elapsed']) {
|
1675
|
+
$("#elapsed").zoomline({
|
1676
|
+
max: data['maxtime'],
|
1677
|
+
data: data['elapsed'],
|
1678
|
+
click: function(element) { window.location = (location + element.attr("data-left")); }
|
1679
|
+
});
|
1680
|
+
}
|
1629
1681
|
}
|
1630
1682
|
|
1631
1683
|
/* Is this a mobile device? */
|
data/views/header.erb
CHANGED
@@ -11,6 +11,7 @@
|
|
11
11
|
<link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/mermaid-6.0.0.css" />
|
12
12
|
<link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/font-awesome-4.4.0/css/font-awesome.min.css">
|
13
13
|
<link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/showoff.css?v=<%= SHOWOFF_VERSION %>" />
|
14
|
+
<link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/zoomline-0.0.1.css">
|
14
15
|
|
15
16
|
<script type="text/javascript" src="<%= @asset_path %>/js/jquery-2.1.4.min.js"></script>
|
16
17
|
|
@@ -20,6 +21,7 @@
|
|
20
21
|
<script type="text/javascript" src="<%= @asset_path %>/js/jquery.doubletap-4ff02c5.js"></script>
|
21
22
|
<script type="text/javascript" src="<%= @asset_path %>/js/jTypeWriter-1.1.js"></script>
|
22
23
|
<script type="text/javascript" src="<%= @asset_path %>/js/bigtext-0.1.8.js"></script>
|
24
|
+
<script type="text/javascript" src="<%= @asset_path %>/js/zoomline-0.0.1.js"></script>
|
23
25
|
<script type="text/javascript" src="<%= @asset_path %>/js/highlight.pack-9.2.0.js"></script>
|
24
26
|
<script type="text/javascript" src="<%= @asset_path %>/js/mermaid-6.0.0-min.js"></script>
|
25
27
|
|
data/views/header_mini.erb
CHANGED
@@ -9,8 +9,10 @@
|
|
9
9
|
|
10
10
|
<link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/showoff.css?v=<%= SHOWOFF_VERSION %>" />
|
11
11
|
<link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/font-awesome-4.4.0/css/font-awesome.min.css">
|
12
|
+
<link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/zoomline-0.0.1.css">
|
12
13
|
|
13
14
|
<script type="text/javascript" src="<%= @asset_path %>/js/jquery-2.1.4.min.js"></script>
|
15
|
+
<script type="text/javascript" src="<%= @asset_path %>/js/zoomline-0.0.1.js"></script>
|
14
16
|
|
15
17
|
<script type="text/javascript" src="<%= @asset_path %>/js/showoff.js?v=<%= SHOWOFF_VERSION %>"></script>
|
16
18
|
|
data/views/stats.erb
CHANGED
@@ -3,68 +3,59 @@
|
|
3
3
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
4
|
<head>
|
5
5
|
<%= erb :header_mini %>
|
6
|
-
|
7
|
-
<script type="text/javascript">
|
8
|
-
$(document).ready(function(){ setupStats(); });
|
9
|
-
</script>
|
10
6
|
</head>
|
11
7
|
|
12
8
|
<body id="stats">
|
13
9
|
<div id="wrapper">
|
14
|
-
<h1>Viewing Statistics</h1>
|
15
10
|
|
16
|
-
<
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
<span class="label"><%= slide %></span>
|
24
|
-
<div class="bar" style="width: <%= (time/max)*100 %>%;"> </div>
|
25
|
-
<div class="time"><%= Time.at(time).gmtime.strftime(timestr) %></div>
|
26
|
-
</div>
|
27
|
-
<% end %>
|
28
|
-
<% end %>
|
29
|
-
</div>
|
11
|
+
<script type="text/javascript">
|
12
|
+
$(document).ready(function(){
|
13
|
+
$.getJSON("/stats_data", function( json ) {
|
14
|
+
setupStats(json);
|
15
|
+
});
|
16
|
+
});
|
17
|
+
</script>
|
30
18
|
|
31
|
-
<
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
<div class="bar" style="width: <%= (time/max)*100 %>%;"> </div>
|
40
|
-
<div class="time"><%= Time.at(time).gmtime.strftime(timestr) %></div>
|
41
|
-
</div>
|
42
|
-
<% end %>
|
43
|
-
<% end %>
|
44
|
-
</div>
|
19
|
+
<h1>Viewing Statistics</h1>
|
20
|
+
<p id="stray"><span class="label"></span> of your audience is not viewing the same slide you are.</p>
|
21
|
+
<p id="idle"><span class="label"></span> of your audience is idle.</p>
|
22
|
+
<h2>Slides currently being viewed:</h2>
|
23
|
+
<div id="viewers">No data to display.</div>
|
24
|
+
<h2>Elapsed time spent on each slide:</h2>
|
25
|
+
<div id="elapsed">No data to display.</div>
|
26
|
+
</div>
|
45
27
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
28
|
+
<div id="all">
|
29
|
+
<h3>All slides</h3>
|
30
|
+
<% max = @all.sort_by {|slide, time| -time}[0][1].to_f rescue 0 %>
|
31
|
+
<% @all.sort.map do |slide, time| %>
|
32
|
+
<% next if slide.empty? %>
|
33
|
+
<% timestr = (time < 60) ? ':%S' : (time < 3599) ? '%M:%S' : '%H:%M:%S' %>
|
34
|
+
<div class="row top">
|
35
|
+
<span class="label"><%= slide %></span>
|
36
|
+
<div class="bar" style="width: <%= (time/max)*100 %>%;"> </div>
|
37
|
+
<div class="time"><%= Time.at(time).gmtime.strftime(timestr) %></div>
|
38
|
+
<% if @counter %>
|
39
|
+
<div class="detail">
|
40
|
+
<% @counter[slide].each do |host, views| %>
|
41
|
+
<% count = views.inject(0) { |sum, view| sum += view['elapsed'] } %>
|
42
|
+
<% timestr = (count < 60) ? ':%S' : (count < 3599) ? '%M:%S' : '%H:%M:%S' %>
|
43
|
+
<div class="row">
|
44
|
+
<% if host == 'presenter' %>
|
45
|
+
<% bgcolor = '' %>
|
58
46
|
<span class="label"><%= host %>:</span>
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
47
|
+
<% else %>
|
48
|
+
<% bgcolor = "background-color: ##{host[0...6]}" %>
|
49
|
+
<% end %>
|
50
|
+
<div class="bar" style="width: <%= (count/max)*100 %>%; <%= bgcolor %>"> </div>
|
51
|
+
<div class="time"><%= Time.at(count).gmtime.strftime(timestr) %></div>
|
52
|
+
</div>
|
53
|
+
<% end %>
|
54
|
+
</div>
|
55
|
+
<% end %>
|
56
|
+
</div>
|
57
|
+
<% end %>
|
68
58
|
</div>
|
59
|
+
|
69
60
|
</body>
|
70
61
|
</html>
|