diff --git a/css/dzslides.css b/css/dzslides.css new file mode 100644 index 0000000..32e84d4 --- /dev/null +++ b/css/dzslides.css @@ -0,0 +1,150 @@ +/* * { + margin: 0; + padding: 0; + box-sizing: border-box; +} */ + +[role="note"] { + display: none; +} + +/* html { + font-size: 16px; +} */ + +body { + width: 800px; + height: 600px; + position: absolute; + top: 50%; + left: 50%; + overflow: hidden; + display: none; +} + +.view body { + position: static; + /* margin: 0; + padding: 0; */ + width: 100%; + height: 100%; + display: inline-block; + overflow: visible; + overflow-x: hidden; + /* undo Dz.onresize */ + transform: none !important; +} + +.view head, +.view head>title { + display: block +} + +article { + position: absolute; + pointer-events: none; + width: 100%; + height: 100%; +} + +.view article { + pointer-events: auto; + position: static; + width: 800px; + height: 600px; + margin: -150px -200px; + float: left; + transform: scale(.4); +} + +.view article>* { + pointer-events: none; +} + +article[aria-selected] { + pointer-events: auto; +} + +html { + overflow: hidden; +} + +html.view { + overflow: visible; +} + +body.loaded { + display: block; +} + +.next:not([active]) { + visibility: hidden; +} + +#progress-bar { + bottom: 0; + position: absolute; + transition: width 400ms linear 0s; +} + +.view #progress-bar { + display: none; +} + +header { + text-align: right; + position: absolute; + top: 0; + width: 100%; +} + +footer { + text-align: right; + position: absolute; + bottom: 0; + width: 100%; +} + +.view header { + display: none; +} + +.view footer { + display: none; +} + +@media print { + article { + transition: none; + transform: none; + position: static; + page-break-inside: avoid; + } + + body { + overflow: visible; + } + + #progress-bar { + display: none; + } +} + +/* + ************************************** + Uncomment the following for 16:9 slides + ************************************** +*/ +/* html { + font-size: 12px; +} */ + +body { + height: 450px; +} + +.view article { + height: 450px; + margin: -140px -200px; + transform: scale(.3); +} \ No newline at end of file diff --git a/js/dzslides.js b/js/dzslides.js new file mode 100644 index 0000000..5a15edc --- /dev/null +++ b/js/dzslides.js @@ -0,0 +1,349 @@ +var Dz = { + remoteWindows: [], + idx: -1, + step: 0, + html: null, + slides: null, + progressBar: null, + params: { + autoplay: "1" + } +}; + +const SLIDE_TAG = "article" + +Dz.init = function () { + document.body.className = "loaded"; + this.slides = Array.prototype.slice.call($$(`body > ${SLIDE_TAG}`)); + this.progressBar = $("#progress-bar"); + this.html = document.body.parentNode; + this.setupParams(); + this.onhashchange(); + this.setupTouchEvents(); + this.onresize(); + this.setupView(); +} +Dz.setupParams = function () { + var p = window.location.search.substr(1).split('&'); + p.forEach(function (e, i, a) { + var keyVal = e.split('='); + Dz.params[keyVal[0]] = decodeURIComponent(keyVal[1]); + }); + // Specific params handling + if (!+this.params.autoplay) + $$.forEach($$("video"), function (v) { + v.controls = true + }); +} +Dz.onkeydown = function (aEvent) { + // Don't intercept keyboard shortcuts + if (aEvent.altKey || + aEvent.ctrlKey || + aEvent.metaKey || + aEvent.shiftKey) { + return; + } + if (aEvent.keyCode == 37 // left arrow + || + aEvent.keyCode == 38 // up arrow + || + aEvent.keyCode == 33 // page up + ) { + aEvent.preventDefault(); + this.back(); + } + if (aEvent.keyCode == 39 // right arrow + || + aEvent.keyCode == 40 // down arrow + || + aEvent.keyCode == 34 // page down + ) { + aEvent.preventDefault(); + this.forward(); + } + if (aEvent.keyCode == 35) { // end + aEvent.preventDefault(); + this.goEnd(); + } + if (aEvent.keyCode == 36) { // home + aEvent.preventDefault(); + this.goStart(); + } + if (aEvent.keyCode == 32) { // space + aEvent.preventDefault(); + this.toggleContent(); + } + if (aEvent.keyCode == 70) { // f + aEvent.preventDefault(); + this.goFullscreen(); + } + if (aEvent.keyCode == 79 //o + || + aEvent.keyCode == 27 //Esc + ) { + aEvent.preventDefault(); + this.toggleView(); + } +} +/* Touch Events */ +Dz.setupTouchEvents = function () { + var orgX, newX; + var tracking = false; + var db = document.body; + db.addEventListener("touchstart", start.bind(this), false); + db.addEventListener("touchmove", move.bind(this), false); + + function start(aEvent) { + aEvent.preventDefault(); + tracking = true; + orgX = aEvent.changedTouches[0].pageX; + } + + function move(aEvent) { + if (!tracking) return; + newX = aEvent.changedTouches[0].pageX; + if (orgX - newX > 100) { + tracking = false; + this.forward(); + } else { + if (orgX - newX < -100) { + tracking = false; + this.back(); + } + } + } +} +Dz.setupView = function () { + document.body.addEventListener("click", function (e) { + if (!Dz.html.classList.contains("view")) return; + if (!e.target || e.target.nodeName != SLIDE_IDENTIFIER.toUpperCase) return; + Dz.html.classList.remove("view"); + Dz.setCursor(Dz.slides.indexOf(e.target) + 1); + }, false); +} +/* Adapt the size of the slides to the window */ +Dz.onresize = function () { + var db = document.body; + var sx = db.clientWidth / window.innerWidth; + var sy = db.clientHeight / window.innerHeight; + var transform = "scale(" + (1 / Math.max(sx, sy)) + ")"; + db.style.transform = transform; + db.style.marginTop = -db.clientHeight / 2 + "px"; + db.style.marginLeft = -db.clientWidth / 2 + "px"; +} +Dz.getNotes = function (aIdx) { + var s = $(`${SLIDE_TAG}:nth-of-type(${aIdx}$)`); + var d = s.$("[role='note']"); + return d ? d.innerHTML : ""; +} +Dz.onmessage = function (aEvent) { + var argv = aEvent.data.split(" "), + argc = argv.length; + argv.forEach(function (e, i, a) { + a[i] = decodeURIComponent(e) + }); + var win = aEvent.source; + if (argv[0] === "REGISTER" && argc === 1) { + this.remoteWindows.push(win); + this.postMsg(win, "REGISTERED", document.title, this.slides.length); + this.postMsg(win, "CURSOR", this.idx + "." + this.step); + return; + } + if (argv[0] === "BACK" && argc === 1) + this.back(); + if (argv[0] === "FORWARD" && argc === 1) + this.forward(); + if (argv[0] === "START" && argc === 1) + this.goStart(); + if (argv[0] === "END" && argc === 1) + this.goEnd(); + if (argv[0] === "TOGGLE_CONTENT" && argc === 1) + this.toggleContent(); + if (argv[0] === "SET_CURSOR" && argc === 2) + window.location.hash = "#" + argv[1]; + if (argv[0] === "GET_CURSOR" && argc === 1) + this.postMsg(win, "CURSOR", this.idx + "." + this.step); + if (argv[0] === "GET_NOTES" && argc === 1) + this.postMsg(win, "NOTES", this.getNotes(this.idx)); +} +Dz.toggleContent = function () { + // If a Video is present in this new slide, play it. + // If a Video is present in the previous slide, stop it. + var s = $(`${SLIDE_TAG}[aria-selected]`); + if (s) { + var video = s.$("video"); + if (video) { + if (video.ended || video.paused) { + video.play(); + } else { + video.pause(); + } + } + } +} +Dz.setCursor = function (aIdx, aStep) { + // If the user change the slide number in the URL bar, jump + // to this slide. + aStep = (aStep != 0 && typeof aStep !== "undefined") ? "." + aStep : ".0"; + window.location.hash = "#" + aIdx + aStep; +} +Dz.onhashchange = function () { + var cursor = window.location.hash.split("#"), + newidx = 1, + newstep = 0; + if (cursor.length == 2) { + newidx = ~~cursor[1].split(".")[0]; + newstep = ~~cursor[1].split(".")[1]; + if (newstep > Dz.slides[newidx - 1].$$('.next').length) { + newstep = 0; + newidx++; + } + } + this.setProgress(newidx, newstep); + if (newidx != this.idx) { + this.setSlide(newidx); + } + if (newstep != this.step) { + this.setIncremental(newstep); + } + for (var i = 0; i < this.remoteWindows.length; i++) { + this.postMsg(this.remoteWindows[i], "CURSOR", this.idx + "." + this.step); + } +} +Dz.back = function () { + if (this.idx == 1) { + return; + } + this.setCursor(this.idx - 1, + this.slides[this.idx - 2].$$('.next[active]').length); +} +Dz.forward = function () { + if (this.idx >= this.slides.length && + this.step >= this.slides[this.idx - 1].$$('.next').length) { + return; + } + if (this.html.classList.contains("view") || + this.step >= this.slides[this.idx - 1].$$('.next').length) { + this.setCursor(this.idx + 1, + this.slides[this.idx].$$('.next[active]').length); + } else { + this.setCursor(this.idx, this.step + 1); + } +} +Dz.goStart = function () { + this.setCursor(1, 0); +} +Dz.goEnd = function () { + var lastIdx = this.slides.length; + var lastStep = this.slides[lastIdx - 1].$$('.next').length; + this.setCursor(lastIdx, lastStep); +} +Dz.toggleView = function () { + this.html.classList.toggle("view"); + if (this.html.classList.contains("view")) { + $(`${SLIDE_TAG}[aria-selected]`).scrollIntoView(true); + } +} +Dz.setSlide = function (aIdx) { + this.idx = aIdx; + var old = $(`${SLIDE_TAG}[aria-selected]`); + var next = $(`${SLIDE_TAG}:nth-of-type(${this.idx})`); + if (old) { + old.removeAttribute("aria-selected"); + var video = old.$("video"); + if (video) { + video.pause(); + } + } + if (next) { + next.setAttribute("aria-selected", "true"); + if (this.html.classList.contains("view")) { + next.scrollIntoView(); + } else { + var video = next.$("video"); + if (video && !!+this.params.autoplay) { + video.play(); + } + } + } else { + // That should not happen + this.idx = -1; + // console.warn("Slide doesn't exist."); + } +} +Dz.setIncremental = function (aStep) { + this.step = aStep; + var incrementals = Array.prototype.slice.call(this.slides[this.idx - 1].$$('.next')).sort(function (a, b) { + return Number(a.getAttribute('next-order')) - Number(b.getAttribute('next-order')); + }); + var next = incrementals[this.step - 1]; + if (next) { + next.setAttribute('active', true); + } else { + this.setCursor(this.idx, 0); + } + return next; +} +Dz.goFullscreen = function () { + var html = $('html'), + requestFullscreen = html.requestFullscreen || html.requestFullScreen || html.mozRequestFullScreen || html.webkitRequestFullScreen; + if (requestFullscreen) { + requestFullscreen.apply(html); + } +} + +Dz.setProgress = function (aIdx, aStep) { + var slide = $(`${SLIDE_TAG}:nth-of-type(${aIdx})`); + if (!slide) + return; + var steps = slide.$$('.next').length + 1, + slideSize = 100 / (this.slides.length - 1), + stepSize = slideSize / steps; + this.progressBar.style.width = ((aIdx - 1) * slideSize + aStep * stepSize) + '%'; +} + +Dz.postMsg = function (aWin, aMsg) { // [arg0, [arg1...]] + aMsg = [aMsg]; + for (var i = 2; i < arguments.length; i++) + aMsg.push(encodeURIComponent(arguments[i])); + aWin.postMessage(aMsg.join(" "), "*"); +} + +function init() { + Dz.init(); + window.onkeydown = Dz.onkeydown.bind(Dz); + window.onresize = Dz.onresize.bind(Dz); + window.onhashchange = Dz.onhashchange.bind(Dz); + window.onmessage = Dz.onmessage.bind(Dz); +} +window.onload = init; +// Helpers +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + // closest thing possible to the ECMAScript 5 internal IsCallable + // function + if (typeof this !== "function") + throw new TypeError( + "Function.prototype.bind - what is trying to be fBound is not callable" + ); + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP ? this : oThis || window, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + return fBound; + }; +} +var $ = (HTMLElement.prototype.$ = function (aQuery) { + return this.querySelector(aQuery); +}).bind(document); +var $$ = (HTMLElement.prototype.$$ = function (aQuery) { + return this.querySelectorAll(aQuery); +}).bind(document); +$$.forEach = function (nodeList, fun) { + Array.prototype.forEach.call(nodeList, fun); +} \ No newline at end of file diff --git a/layouts/root.html b/layouts/root.html index b53db86..c3bc43a 100644 --- a/layouts/root.html +++ b/layouts/root.html @@ -7,19 +7,15 @@ @title - + -
-
- @slides -
-
- + @slides +
- - + +