| @@ -1,59 +0,0 @@ | |||
| #!/bin/sh | |||
| REMOTE='http://localhost:8080' | |||
| INTERVAL=1 | |||
| requirecmd() | |||
| { | |||
| which "$1" 2>1 > /dev/null | |||
| if [ "$?" -ne 0 ]; then | |||
| echo "Missing command: $1" | |||
| exit 1 | |||
| fi | |||
| } | |||
| requirecmd curl | |||
| requirecmd fbv | |||
| requirecmd shasum | |||
| calchash() | |||
| { | |||
| shasum --algorithm 1 "$1" 2>/dev/null | cut -d ' ' -f 1 | |||
| } | |||
| PICFILE="pic" | |||
| PICHASH=$(calchash "$PICFILE") | |||
| PROGPID=$$ | |||
| # Loop responsible for requesting to the server | |||
| # and updating the picture if necessary | |||
| requestloop() | |||
| { | |||
| while :; do | |||
| URL="$REMOTE/$PICHASH" | |||
| curl --silent "$URL" > refresh | |||
| # Empty response means the file hasn't changed | |||
| if [ $(file refresh | cut -d ' ' -f 2) = "empty" ]; then | |||
| rm refresh | |||
| # If the response is non-empty, we received an image file. | |||
| # We move that file to $PICFILE, then display it | |||
| else | |||
| mv refresh "$PICFILE" | |||
| PICHASH=$(calchash "$PICFILE") | |||
| pkill --parent "$PROGPID" fbv | |||
| fi | |||
| sleep "$INTERVAL" | |||
| done | |||
| } | |||
| requestloop & | |||
| while :; do | |||
| if [ -f "$PICFILE" ]; then | |||
| fbv --noclear --noinfo "$PICFILE" | |||
| else | |||
| sleep 2; | |||
| fi | |||
| done | |||
| @@ -0,0 +1,4 @@ | |||
| { | |||
| "slides": "slides", | |||
| "interval": 5000 | |||
| } | |||
| @@ -0,0 +1,101 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <title>Slides</title> | |||
| <style> | |||
| html, body { | |||
| margin: 0px; | |||
| padding: 0px; | |||
| height: 100%; | |||
| overflow: hidden; | |||
| } | |||
| #_overlay { | |||
| z-index: 2; | |||
| } | |||
| #_main { | |||
| z-index: 1; | |||
| } | |||
| ._content { | |||
| background: white; | |||
| position: absolute; | |||
| width: 100%; | |||
| height: 100%; | |||
| top: 0px; | |||
| left: 0px; | |||
| } | |||
| ._content { | |||
| text-align: center; | |||
| } | |||
| ._content h1 { font-size: 5em } | |||
| ._content h2 { font-size: 4.5em } | |||
| ._content h3 { font-size: 4em } | |||
| ._content p { font-size: 2.2em } | |||
| ._content .fullscreen { | |||
| position: absolute; | |||
| width: auto; | |||
| height: 100%; | |||
| top: 0px; | |||
| left: 50%; | |||
| -moz-transform: translateX(-50%); | |||
| -ms-transform: translateX(-50%); | |||
| -webkit-transform: translateX(-50%); | |||
| transform: translateX(-50%); | |||
| } | |||
| #_overlay { | |||
| transition: opacity 1s; | |||
| opacity: 1; | |||
| } | |||
| #_overlay.hidden { | |||
| opacity: 0; | |||
| } | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <div id="_main" class="_content"></div> | |||
| <div id="_overlay" class="_content"></div> | |||
| <script> | |||
| var overlay = () => document.querySelector("#_overlay"); | |||
| var main = () => document.querySelector("#_main"); | |||
| // Swap the IDs of two elements | |||
| function swap(elem1, elem2) { | |||
| var tmp = elem1.id; | |||
| elem1.id = elem2.id; | |||
| elem2.id = tmp; | |||
| } | |||
| // Change slides with a transition | |||
| function update() { | |||
| overlay().innerHTML = ""; | |||
| overlay().className = "_content"; | |||
| swap(main(), overlay()); | |||
| fetch("/slide") | |||
| .then(response => response.text()) | |||
| .then(text => { | |||
| setTimeout(() => { | |||
| main().innerHTML = text; | |||
| overlay().className = "_content hidden"; | |||
| }, 1000); | |||
| }) | |||
| .catch(err => console.error(err)); | |||
| // Wait for the next slide change, then update again | |||
| fetch("/await") | |||
| .then(response => update()) | |||
| .catch(err => { console.error(err); update(); }); | |||
| } | |||
| update(); | |||
| </script> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,112 @@ | |||
| var fs = require("fs"); | |||
| var http = require("http"); | |||
| var crypto = require("crypto"); | |||
| var pathlib = require("path"); | |||
| var urllib = require("url"); | |||
| var index = fs.readFileSync("index.html"); | |||
| var conf = JSON.parse(fs.readFileSync("conf.json")); | |||
| function error(res, err) { | |||
| console.trace(err); | |||
| console.log(res.toString()); | |||
| } | |||
| // The individual slide | |||
| function Slide(dir) { | |||
| var self = {}; | |||
| self.dir = dir; | |||
| function serve(parts, res) { | |||
| if (parts.pathname === "/slide") { | |||
| fs.createReadStream(pathlib.join(dir, "index.html")) | |||
| .on("error", err => error(res, err)) | |||
| .pipe(res); | |||
| } else { | |||
| console.log("loading "+parts.pathname+" from "+dir); | |||
| fs.createReadStream(pathlib.join(dir, parts.pathname)) | |||
| .on("error", err => error(res, err)) | |||
| .pipe(res); | |||
| } | |||
| } | |||
| self.serve = function(parts, res) { | |||
| try { | |||
| serve(parts, res); | |||
| } catch (err) { | |||
| if (err.code && err.code === "ENOENT") | |||
| res.writeHead(404); | |||
| error(res, err); | |||
| } | |||
| } | |||
| return self; | |||
| } | |||
| // The slideshow, whose job it is to manage all slides | |||
| // and tell the client whether it has to update or not | |||
| function Slideshow(dir, changeInterval) { | |||
| var self = {}; | |||
| var currentSlide = null; | |||
| var awaiters = []; | |||
| self.serve = function(req, res) { | |||
| console.log(currentSlide ? currentSlide.dir : ""); | |||
| var parts = urllib.parse(req.url); | |||
| // /: Send the base site to the client | |||
| if (parts.pathname === "/") { | |||
| res.end(index); | |||
| // /await: long polling, request won't end before a new slide comes | |||
| } else if (parts.pathname === "/await") { | |||
| awaiters.push(res); | |||
| // There's a current slide: leave serving files up to the slide | |||
| } else if (currentSlide) { | |||
| currentSlide.serve(parts, res); | |||
| // There's no current slide show | |||
| } else { | |||
| res.end("No current slideshow."); | |||
| } | |||
| } | |||
| // This function starts the slideshow and goes through the slides | |||
| // one by one. When done, it starts again by calling this function again. | |||
| function init() { | |||
| var slides = fs.readdirSync(dir) | |||
| .sort() | |||
| .map(file => Slide(pathlib.join(dir, file))); | |||
| var slideIndex = 0; | |||
| currentSlide = slides[slideIndex]; | |||
| var interval = setInterval(() => { | |||
| slideIndex += 1; | |||
| // Go to the next slide, or restart | |||
| if (slideIndex >= slides.length) { | |||
| clearInterval(interval); | |||
| init(); | |||
| } else { | |||
| currentSlide = slides[slideIndex]; | |||
| } | |||
| // End all awaiting connections to notify slide change | |||
| awaiters.forEach(res => res.end()); | |||
| }, changeInterval); | |||
| } | |||
| init(); | |||
| return self; | |||
| } | |||
| var slideshow = Slideshow(conf.slides, conf.interval); | |||
| http.createServer((req, res) => { | |||
| slideshow.serve(req, res); | |||
| }).listen(8080); | |||
| @@ -1,29 +0,0 @@ | |||
| var fs = require("fs"); | |||
| var http = require("http"); | |||
| var crypto = require("crypto"); | |||
| function Pic(path) { | |||
| var content = fs.readFileSync(path); | |||
| var hash = crypto.createHash("sha1").update(content).digest("hex"); | |||
| var self = { | |||
| path: path, | |||
| hash: hash, | |||
| content: content | |||
| } | |||
| return self; | |||
| } | |||
| var currentPic = Pic("/home/martin/background.jpg"); | |||
| http.createServer((req, res) => { | |||
| var hash = req.url.substring(1); // Remove the first / to get only hash | |||
| console.log("got hash "+hash+", existing hash is "+currentPic.hash); | |||
| if (hash === currentPic.hash) { | |||
| res.end(); | |||
| } else { | |||
| res.end(currentPic.content); | |||
| } | |||
| }).listen(8080); | |||
| @@ -0,0 +1,3 @@ | |||
| <h1> Viktig informasjon om greier </h1> | |||
| <p> Et eller annet </p> | |||
| @@ -0,0 +1 @@ | |||
| <h1> This is the second slide </h1> | |||
| @@ -0,0 +1 @@ | |||
| <h1> This is the third slide </h1> | |||
| @@ -0,0 +1 @@ | |||
| <img class="fullscreen" src="image.svg"> | |||