Talk given at Datapalooza Denver 2016 titled "(Your) Data as a Service: The Easy Way to Build an API for Your Data".
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

8 anos atrás
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>reveal.js - Slide Notes</title>
  6. <style>
  7. body {
  8. font-family: Helvetica;
  9. }
  10. #current-slide,
  11. #upcoming-slide,
  12. #speaker-controls {
  13. padding: 6px;
  14. box-sizing: border-box;
  15. -moz-box-sizing: border-box;
  16. }
  17. #current-slide iframe,
  18. #upcoming-slide iframe {
  19. width: 100%;
  20. height: 100%;
  21. border: 1px solid #ddd;
  22. }
  23. #current-slide .label,
  24. #upcoming-slide .label {
  25. position: absolute;
  26. top: 10px;
  27. left: 10px;
  28. font-weight: bold;
  29. font-size: 14px;
  30. z-index: 2;
  31. color: rgba( 255, 255, 255, 0.9 );
  32. }
  33. #current-slide {
  34. position: absolute;
  35. width: 65%;
  36. height: 100%;
  37. top: 0;
  38. left: 0;
  39. padding-right: 0;
  40. }
  41. #upcoming-slide {
  42. position: absolute;
  43. width: 35%;
  44. height: 40%;
  45. right: 0;
  46. top: 0;
  47. }
  48. #speaker-controls {
  49. position: absolute;
  50. top: 40%;
  51. right: 0;
  52. width: 35%;
  53. height: 60%;
  54. overflow: auto;
  55. font-size: 18px;
  56. }
  57. .speaker-controls-time.hidden,
  58. .speaker-controls-notes.hidden {
  59. display: none;
  60. }
  61. .speaker-controls-time .label,
  62. .speaker-controls-notes .label {
  63. text-transform: uppercase;
  64. font-weight: normal;
  65. font-size: 0.66em;
  66. color: #666;
  67. margin: 0;
  68. }
  69. .speaker-controls-time {
  70. border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
  71. margin-bottom: 10px;
  72. padding: 10px 16px;
  73. padding-bottom: 20px;
  74. cursor: pointer;
  75. }
  76. .speaker-controls-time .reset-button {
  77. opacity: 0;
  78. float: right;
  79. color: #666;
  80. text-decoration: none;
  81. }
  82. .speaker-controls-time:hover .reset-button {
  83. opacity: 1;
  84. }
  85. .speaker-controls-time .timer,
  86. .speaker-controls-time .clock {
  87. width: 50%;
  88. font-size: 1.9em;
  89. }
  90. .speaker-controls-time .timer {
  91. float: left;
  92. }
  93. .speaker-controls-time .clock {
  94. float: right;
  95. text-align: right;
  96. }
  97. .speaker-controls-time span.mute {
  98. color: #bbb;
  99. }
  100. .speaker-controls-notes {
  101. padding: 10px 16px;
  102. }
  103. .speaker-controls-notes .value {
  104. margin-top: 5px;
  105. line-height: 1.4;
  106. font-size: 1.2em;
  107. }
  108. .clear {
  109. clear: both;
  110. }
  111. @media screen and (max-width: 1080px) {
  112. #speaker-controls {
  113. font-size: 16px;
  114. }
  115. }
  116. @media screen and (max-width: 900px) {
  117. #speaker-controls {
  118. font-size: 14px;
  119. }
  120. }
  121. @media screen and (max-width: 800px) {
  122. #speaker-controls {
  123. font-size: 12px;
  124. }
  125. }
  126. </style>
  127. </head>
  128. <body>
  129. <div id="current-slide"></div>
  130. <div id="upcoming-slide"><span class="label">UPCOMING:</span></div>
  131. <div id="speaker-controls">
  132. <div class="speaker-controls-time">
  133. <h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
  134. <div class="clock">
  135. <span class="clock-value">0:00 AM</span>
  136. </div>
  137. <div class="timer">
  138. <span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
  139. </div>
  140. <div class="clear"></div>
  141. </div>
  142. <div class="speaker-controls-notes hidden">
  143. <h4 class="label">Notes</h4>
  144. <div class="value"></div>
  145. </div>
  146. </div>
  147. <script src="../../plugin/markdown/marked.js"></script>
  148. <script>
  149. (function() {
  150. var notes,
  151. notesValue,
  152. currentState,
  153. currentSlide,
  154. upcomingSlide,
  155. connected = false;
  156. window.addEventListener( 'message', function( event ) {
  157. var data = JSON.parse( event.data );
  158. // The overview mode is only useful to the reveal.js instance
  159. // where navigation occurs so we don't sync it
  160. if( data.state ) delete data.state.overview;
  161. // Messages sent by the notes plugin inside of the main window
  162. if( data && data.namespace === 'reveal-notes' ) {
  163. if( data.type === 'connect' ) {
  164. handleConnectMessage( data );
  165. }
  166. else if( data.type === 'state' ) {
  167. handleStateMessage( data );
  168. }
  169. }
  170. // Messages sent by the reveal.js inside of the current slide preview
  171. else if( data && data.namespace === 'reveal' ) {
  172. if( /ready/.test( data.eventName ) ) {
  173. // Send a message back to notify that the handshake is complete
  174. window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
  175. }
  176. else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
  177. window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' );
  178. }
  179. }
  180. } );
  181. /**
  182. * Called when the main window is trying to establish a
  183. * connection.
  184. */
  185. function handleConnectMessage( data ) {
  186. if( connected === false ) {
  187. connected = true;
  188. setupIframes( data );
  189. setupKeyboard();
  190. setupNotes();
  191. setupTimer();
  192. }
  193. }
  194. /**
  195. * Called when the main window sends an updated state.
  196. */
  197. function handleStateMessage( data ) {
  198. // Store the most recently set state to avoid circular loops
  199. // applying the same state
  200. currentState = JSON.stringify( data.state );
  201. // No need for updating the notes in case of fragment changes
  202. if ( data.notes ) {
  203. notes.classList.remove( 'hidden' );
  204. notesValue.style.whiteSpace = data.whitespace;
  205. if( data.markdown ) {
  206. notesValue.innerHTML = marked( data.notes );
  207. }
  208. else {
  209. notesValue.innerHTML = data.notes;
  210. }
  211. }
  212. else {
  213. notes.classList.add( 'hidden' );
  214. }
  215. // Update the note slides
  216. currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
  217. upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
  218. upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
  219. }
  220. // Limit to max one state update per X ms
  221. handleStateMessage = debounce( handleStateMessage, 200 );
  222. /**
  223. * Forward keyboard events to the current slide window.
  224. * This enables keyboard events to work even if focus
  225. * isn't set on the current slide iframe.
  226. */
  227. function setupKeyboard() {
  228. document.addEventListener( 'keydown', function( event ) {
  229. currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
  230. } );
  231. }
  232. /**
  233. * Creates the preview iframes.
  234. */
  235. function setupIframes( data ) {
  236. var params = [
  237. 'receiver',
  238. 'progress=false',
  239. 'history=false',
  240. 'transition=none',
  241. 'autoSlide=0',
  242. 'backgroundTransition=none'
  243. ].join( '&' );
  244. var urlSeparator = /\?/.test(data.url) ? '&' : '?';
  245. var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
  246. var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
  247. var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash;
  248. currentSlide = document.createElement( 'iframe' );
  249. currentSlide.setAttribute( 'width', 1280 );
  250. currentSlide.setAttribute( 'height', 1024 );
  251. currentSlide.setAttribute( 'src', currentURL );
  252. document.querySelector( '#current-slide' ).appendChild( currentSlide );
  253. upcomingSlide = document.createElement( 'iframe' );
  254. upcomingSlide.setAttribute( 'width', 640 );
  255. upcomingSlide.setAttribute( 'height', 512 );
  256. upcomingSlide.setAttribute( 'src', upcomingURL );
  257. document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
  258. }
  259. /**
  260. * Setup the notes UI.
  261. */
  262. function setupNotes() {
  263. notes = document.querySelector( '.speaker-controls-notes' );
  264. notesValue = document.querySelector( '.speaker-controls-notes .value' );
  265. }
  266. /**
  267. * Create the timer and clock and start updating them
  268. * at an interval.
  269. */
  270. function setupTimer() {
  271. var start = new Date(),
  272. timeEl = document.querySelector( '.speaker-controls-time' ),
  273. clockEl = timeEl.querySelector( '.clock-value' ),
  274. hoursEl = timeEl.querySelector( '.hours-value' ),
  275. minutesEl = timeEl.querySelector( '.minutes-value' ),
  276. secondsEl = timeEl.querySelector( '.seconds-value' );
  277. function _updateTimer() {
  278. var diff, hours, minutes, seconds,
  279. now = new Date();
  280. diff = now.getTime() - start.getTime();
  281. hours = Math.floor( diff / ( 1000 * 60 * 60 ) );
  282. minutes = Math.floor( ( diff / ( 1000 * 60 ) ) % 60 );
  283. seconds = Math.floor( ( diff / 1000 ) % 60 );
  284. clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
  285. hoursEl.innerHTML = zeroPadInteger( hours );
  286. hoursEl.className = hours > 0 ? '' : 'mute';
  287. minutesEl.innerHTML = ':' + zeroPadInteger( minutes );
  288. minutesEl.className = minutes > 0 ? '' : 'mute';
  289. secondsEl.innerHTML = ':' + zeroPadInteger( seconds );
  290. }
  291. // Update once directly
  292. _updateTimer();
  293. // Then update every second
  294. setInterval( _updateTimer, 1000 );
  295. timeEl.addEventListener( 'click', function() {
  296. start = new Date();
  297. _updateTimer();
  298. return false;
  299. } );
  300. }
  301. function zeroPadInteger( num ) {
  302. var str = '00' + parseInt( num );
  303. return str.substring( str.length - 2 );
  304. }
  305. /**
  306. * Limits the frequency at which a function can be called.
  307. */
  308. function debounce( fn, ms ) {
  309. var lastTime = 0,
  310. timeout;
  311. return function() {
  312. var args = arguments;
  313. var context = this;
  314. clearTimeout( timeout );
  315. var timeSinceLastCall = Date.now() - lastTime;
  316. if( timeSinceLastCall > ms ) {
  317. fn.apply( context, args );
  318. lastTime = Date.now();
  319. }
  320. else {
  321. timeout = setTimeout( function() {
  322. fn.apply( context, args );
  323. lastTime = Date.now();
  324. }, ms - timeSinceLastCall );
  325. }
  326. }
  327. }
  328. })();
  329. </script>
  330. </body>
  331. </html>