#!/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"> |