| #!/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 |
| { | |||||
| "slides": "slides", | |||||
| "interval": 5000 | |||||
| } |
| <!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> |
| 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); |
| 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); |
| <h1> Viktig informasjon om greier </h1> | |||||
| <p> Et eller annet </p> |
| <h1> This is the second slide </h1> |
| <h1> This is the third slide </h1> |
| <img class="fullscreen" src="image.svg"> |