add script - jscancer - Javascript crap (relatively small) | |
git clone git://git.codemadness.org/jscancer | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit ba166782d82d92a2dcf4ee22b96da2285874a4b3 | |
parent 9ba8da523384de2bdf895531b8145f93441ceec9 | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Thu, 15 Feb 2024 19:18:22 +0100 | |
add script | |
Diffstat: | |
A narrowcasting/README | 27 +++++++++++++++++++++++++++ | |
A narrowcasting/index.html | 48 +++++++++++++++++++++++++++++… | |
A narrowcasting/script.js | 619 +++++++++++++++++++++++++++++… | |
A narrowcasting/style.css | 237 +++++++++++++++++++++++++++++… | |
4 files changed, 931 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/narrowcasting/README b/narrowcasting/README | |
@@ -0,0 +1,27 @@ | |
+Narrowcasting | |
+------------- | |
+ | |
+This is a simple script for some "narrowcasting" screen. | |
+It allows to defined HTML elements with data attributes for their configuratio… | |
+The script will then handle the widgets and slides logic. | |
+ | |
+ | |
+Some features: | |
+ | |
+* Support embedding of data in <iframe>'s and refresh using some timer. | |
+* Support embedding of data in <embed> and update using some timer. | |
+* Support updating parts of the screen data using XMLHttpRequest (AJAX) using | |
+ some timer. | |
+* Date / clock widget, format it with a strftime()-like syntax. | |
+ Supports any locale forthe weekday and month names. | |
+* Support embedded videos, automatically pausing and continueing them when it | |
+ skips to the next slide or looping them. | |
+* A mechanism to poll some file for changes using some timer. When this data | |
+ changes the page is reloaded (forcing also a cache flush). This is useful for | |
+ remotely updating the layouts or scripts. | |
+* Show a newsticker. This uses the JSON Feed format. | |
+ sfeed_json could be used to convert from RSS/Atom to this format. This data | |
+ could be updated using a cronjob. | |
+* Progressbar to show the slides remaining duration. | |
+* Hotkeys to skip slides, useful for debugging also. | |
+* Uses vanilla JS with no dependencies on frameworks, etc. | |
diff --git a/narrowcasting/index.html b/narrowcasting/index.html | |
@@ -0,0 +1,48 @@ | |
+<!DOCTYPE html> | |
+<html> | |
+<head> | |
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |
+<title>Narrowcasting</title> | |
+<link rel="stylesheet" type="text/css" href="style.css" /> | |
+<link rel="icon" type="image/png" href="favicon.png" /> | |
+<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
+<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
+</head> | |
+<body> | |
+ | |
+<div class="screen check-updates" data-update="120000" data-url="data/check_up… | |
+ <div class="logo"></div> | |
+ <div class="topbar-right"> | |
+ <span class="datetime time" data-format="%H:%M" data-update="6… | |
+ <span class="datetime date" data-format="<span>%A <b>%e %B</b>… | |
+ </div> | |
+ | |
+ <div class="topbar"></div> | |
+ | |
+ <div class="topbar-info-dashboard"></div> | |
+ | |
+ <div class="slides"> | |
+ <div class="slide slide-1" id="slide-1" data-displaynext="3000… | |
+ <!-- widgets slide 1 --> | |
+ <div class="widget xhr-content" data-url="data/1" data… | |
+ <div class="widget xhr-content" data-url="data/2" data… | |
+ <div class="widget xhr-content" data-url="data/3" data… | |
+ | |
+ <div class="widget widget-lichess embed-content" data-… | |
+ <iframe src="https://lichess.org/training/fram… | |
+ </div> | |
+ | |
+ <div class="news-ticker ticker1" data-url="data/news1.… | |
+ <div class="news-ticker ticker2" data-url="data/news2.… | |
+ </div> | |
+ | |
+ <div class="slide slide-2" id="slide-2" data-displaynext="3000… | |
+ </div> | |
+ | |
+ <div class="progressbar"></div> | |
+ </div> | |
+</div> | |
+<script type="text/javascript" src="script.js"> | |
+</script> | |
+</body> | |
+</html> | |
diff --git a/narrowcasting/script.js b/narrowcasting/script.js | |
@@ -0,0 +1,619 @@ | |
+// run atleast once, then every n milliseconds. | |
+// if n is 0 just run once. | |
+function updateevery(n, fn) { | |
+ // schedule in parallel now. | |
+ setTimeout(function() { | |
+ fn(); | |
+ }, 0); | |
+ if (!n) | |
+ return null; | |
+ return setInterval(fn, n); | |
+} | |
+ | |
+function pad0(n) { | |
+ return (parseInt(n) < 10 ? "0" : "") + n.toString(); | |
+} | |
+ | |
+function padspace(n) { | |
+ return (parseInt(n) < 10 ? " " : "") + n.toString(); | |
+} | |
+ | |
+// dutch weekdays and months. | |
+//var strftime_weekdays = [ "Zondag", "Maandag", "Dinsdag", "Woensdag", "Donde… | |
+//var strftime_months = [ "Januari", "Februari", "Maart", "April", "Mei", "Ju… | |
+ | |
+// subset of strftime() | |
+function strftime(fmt, d) { | |
+ //var locale = locale || navigator.language || "en"; | |
+ var locale = "nl-NL"; // dutch | |
+ d = d || new Date(); | |
+ | |
+ var mon = d.getMonth(); // starts at 0 | |
+ var day = d.getDay(); // weekday, starts at 0 (sunday). | |
+ | |
+ var s = fmt; | |
+ s = s.replace(/%Y/g, d.getFullYear().toString()); | |
+ s = s.replace(/%m/g, pad0(mon + 1)); | |
+ s = s.replace(/%d/g, pad0(d.getDate())); | |
+ s = s.replace(/%e/g, padspace(d.getDate())); | |
+ s = s.replace(/%H/g, pad0(d.getHours())); | |
+ s = s.replace(/%M/g, pad0(d.getMinutes())); | |
+ s = s.replace(/%S/g, pad0(d.getSeconds())); | |
+ | |
+ s = s.replace(/%A/g, (new Intl.DateTimeFormat(locale, { weekday: "long… | |
+ s = s.replace(/%a/g, (new Intl.DateTimeFormat(locale, { weekday: "shor… | |
+ s = s.replace(/%B/g, (new Intl.DateTimeFormat(locale, { month: "long" … | |
+ s = s.replace(/%b/g, (new Intl.DateTimeFormat(locale, { month: "short"… | |
+ | |
+ //s = s.replace(/%A/g, strftime_weekdays[day]); // full weekday name | |
+ //s = s.replace(/%a/g, strftime_weekdays[day].substring(0, 3)); // abb… | |
+ //s = s.replace(/%B/g, strftime_months[mon]); // full month name | |
+ //s = s.replace(/%b/g, strftime_months[mon].substring(0, 3)); // abbre… | |
+ | |
+ return s; | |
+} | |
+ | |
+// XMLHttpRequest helper function. | |
+function xhr(url, fn, timeout) { | |
+ var x = new(XMLHttpRequest); | |
+ x.open("get", url, true); // async | |
+ x.setRequestHeader("X-Requested-With", "XMLHttpRequest"); | |
+ x.timeout = parseInt(timeout || 10000); // default: 10 seconds | |
+ x.onreadystatechange = function () { | |
+ if (x.readyState != 4) | |
+ return; | |
+ fn(x); | |
+ }; | |
+ var data = ""; | |
+ x.send(data); | |
+} | |
+ | |
+// find direct descendent child element with a certain classname. | |
+function getdirectchilds(parent, classname) { | |
+ var els = []; | |
+ | |
+ for (var i = 0; i < parent.children.length; i++) { | |
+ var el = parent.children[i]; | |
+ if (el.classList.contains(classname)) | |
+ els.push(el); | |
+ } | |
+ | |
+ return els; | |
+} | |
+ | |
+// news tickers. | |
+var tickerels = document.getElementsByClassName("news-ticker"); | |
+var tickers = []; | |
+for (var i = 0; i < tickerels.length; i++) { | |
+ var url = tickerels[i].getAttribute("data-url") || ""; | |
+ if (url == "") | |
+ continue; | |
+ | |
+ // bind element to context | |
+ var t = (function(tickerel, delay) { | |
+ var displayfn = function(ticker) { | |
+ var parent = ticker.el; | |
+ var items = ticker.items || []; | |
+ var counter = ticker.counter || 0; | |
+ | |
+ var node = document.createElement("div"); | |
+ node.innerText = items[counter] || ""; | |
+ parent.appendChild(node); // add new. | |
+ counter = (counter + 1) % items.length; | |
+ | |
+ ticker.counter = counter; // update counter | |
+ | |
+ if (parent.children.length) { | |
+ // schedule to be removed. | |
+ setTimeout(function() { | |
+ if (parent.children.length) | |
+ parent.children[0].className =… | |
+ setTimeout(function() { | |
+ if (parent.children.length > 1) | |
+ parent.removeChild(par… | |
+ }, ticker.fadein); | |
+ }, ticker.display); | |
+ } | |
+ }; | |
+ | |
+ var processfn = function(x) { | |
+ var feed = JSON.parse(x.responseText || ""); | |
+ var items = []; | |
+ var feeditems = feed.items || []; | |
+ | |
+ for (var j = 0; j < feeditems.length; j++) { | |
+ var title = feeditems[j].title; | |
+ var ts = feeditems[j].date_published || ""; | |
+ // if there is a timestamp prefix it in the ti… | |
+ if (ts.length) { | |
+ var d = new Date(Date.parse(ts)); | |
+ title = strftime("%H:%M", d) + " " + t… | |
+ } | |
+ items.push(title); | |
+ } | |
+ ticker.items = items; | |
+ }; | |
+ | |
+ // ticker config / context. | |
+ var ticker = { | |
+ el: tickerel, | |
+ url: (tickerel.getAttribute("data-url") || ""), | |
+ display: parseInt(tickerel.getAttribute("data-displayn… | |
+ fadein: parseInt(tickerel.getAttribute("data-fadein"))… | |
+ update: parseInt(tickerel.getAttribute("data-update"))… | |
+ }; | |
+ | |
+ ticker.processfn = processfn; | |
+ ticker.displayfn = displayfn; | |
+ | |
+ // reload RSS/Atom data. | |
+ var updater = function() { | |
+ xhr(ticker.url, function(x) { | |
+ ticker.processfn(x); | |
+ | |
+ // reset and stop previous timer for cycling i… | |
+ if (ticker.displaytimer) | |
+ clearInterval(ticker.displaytimer); | |
+ | |
+ ticker.counter = 0; // reset item counter | |
+ ticker.el.innerHTML = ""; // clear contents | |
+ ticker.displayfn(ticker); // immediately updat… | |
+ | |
+ // display / cycle existing items. | |
+ ticker.displaytimer = setInterval(function() {… | |
+ }); | |
+ }; | |
+ | |
+ ticker.updater = updater; | |
+ ticker.updatetimer = setInterval(updater, ticker.update); // u… | |
+ setTimeout(updater, delay); // small delay so the newstickers … | |
+ | |
+ return ticker; | |
+ })(tickerels[i], 1000 * i); | |
+ | |
+ tickers.push(t); | |
+} | |
+ | |
+// elements showing simple HTML content in a placeholder, refresh using XMLHTT… | |
+var xhrcontentels = document.getElementsByClassName("xhr-content"); | |
+for (var i = 0; i < xhrcontentels.length; i++) { | |
+ var xhrcontentel = xhrcontentels[i]; | |
+ var url = xhrcontentel.getAttribute("data-url") || ""; | |
+ if (url == "") | |
+ continue; | |
+ | |
+ // bind element to context. | |
+ (function(bindel) { | |
+ var updatefn = function(config) { | |
+ var parent = config.el; | |
+ var curcontent = parent.childNodes.length ? parent.chi… | |
+ var newcontent = config.data || ""; | |
+ // content changed. | |
+ if (curcontent === newcontent) | |
+ return; | |
+ | |
+ // remove previous nodes. | |
+ while (parent.childNodes.length > 0) | |
+ parent.removeChild(parent.childNodes[0]); | |
+ | |
+ // add nodes to force a transition. | |
+ var node = document.createElement("div"); | |
+ node.innerHTML = newcontent; | |
+ if (config.animate !== "") { | |
+ node.classList.add("animate-once"); | |
+ node.classList.add(config.animateclass); | |
+ } | |
+ parent.appendChild(node); // add new. | |
+ }; | |
+ | |
+ var processfn = function(x) { | |
+ var data = ""; | |
+ if (x.status >= 200 && x.status < 400) | |
+ data = x.responseText || ""; | |
+ else | |
+ data = ""; // "ERROR"; | |
+ config.data = data; | |
+ }; | |
+ | |
+ // config / context. | |
+ var config = { | |
+ animateclass: bindel.getAttribute("data-animate-class"… | |
+ el: bindel, | |
+ processfn: processfn, | |
+ timeout: parseInt(bindel.getAttribute("data-timeout"))… | |
+ update: parseInt(bindel.getAttribute("data-update")) |… | |
+ updatefn: updatefn, | |
+ url: (bindel.getAttribute("data-url") || "") | |
+ }; | |
+ | |
+ // reload data. | |
+ var updater = function() { | |
+ xhr(config.url, function(x) { | |
+ // process data. | |
+ config.processfn(x); | |
+ // update and display data. | |
+ config.updatefn(config); | |
+ }, config.timeout); | |
+ }; | |
+ | |
+ setInterval(updater, config.update); | |
+ // run almost immediately once the first time. | |
+ setTimeout(updater, 1); | |
+ })(xhrcontentel); | |
+} | |
+ | |
+// object or iframe elements that simply show some HTML and refresh in an inte… | |
+var embedcontentels = document.getElementsByClassName("embed-content"); | |
+for (var i = 0; i < embedcontentels.length; i++) { | |
+ var embedcontentel = embedcontentels[i]; | |
+ | |
+ // must have one child node of type "object" or "iframe". | |
+ if (embedcontentel.children.length <= 0) | |
+ continue; | |
+ | |
+ var child = embedcontentel.children[0]; | |
+ | |
+ // bind element to context. | |
+ (function(bindel, child) { | |
+ var url = ""; | |
+ var type = ""; | |
+ var tagname = child.tagName; | |
+ if (tagname === "OBJECT") { | |
+ url = child.data || ""; | |
+ type = child.getAttribute("data-type") || "text/html"; | |
+ } else if (tagname === "IFRAME") { | |
+ url = child.getAttribute("src") || ""; | |
+ } | |
+ | |
+ // config / context. | |
+ var config = { | |
+ el: bindel, | |
+ update: parseInt(bindel.getAttribute("data-update")) |… | |
+ url: url, | |
+ type: type | |
+ }; | |
+ | |
+ var updater; | |
+ if (tagname === "OBJECT") { | |
+ // reload data. | |
+ updater = function() { | |
+ var objects = Array.from(bindel.childNodes); /… | |
+ | |
+ var object = document.createElement("object"); | |
+ object.data = config.url; // reload | |
+ object.type = config.type; | |
+ object.style.visibility = "hidden"; | |
+ object.onload = function() { | |
+ // quickly swap out the old object(s) … | |
+ // this prevents some flickering. | |
+ for (var j = 0; j < objects.length; j+… | |
+ bindel.removeChild(objects[j]); | |
+ this.style.visibility = "visible"; | |
+ }; | |
+ bindel.appendChild(object); | |
+ }; | |
+ } else if (tagname === "IFRAME") { | |
+ // reload data. | |
+ updater = function() { | |
+ child.contentWindow.location.reload(); | |
+ }; | |
+ } | |
+ | |
+ setInterval(updater, config.update); | |
+ // run almost immediately once the first time. | |
+ setTimeout(updater, 1); | |
+ })(embedcontentel, child); | |
+} | |
+ | |
+// pause videos inside element. | |
+function pausevideos(el) { | |
+ var videos = el.getElementsByTagName("video"); | |
+ for (var i = 0; i < videos.length; i++) { | |
+ videos[i].pause(); | |
+ if ((videos[i].getAttribute("data-reset-on-pause") || "") === … | |
+ videos[i].currentTime = 0; // reset time / seek to sta… | |
+ } | |
+} | |
+ | |
+// play / resume videos inside element. | |
+function playvideos(el) { | |
+ var videos = el.getElementsByTagName("video"); | |
+ for (var i = 0; i < videos.length; i++) { | |
+ videos[i].muted = true; // mute audio by default. | |
+ if ((videos[i].getAttribute("data-reset-on-play") || "") === "… | |
+ videos[i].currentTime = 0; // reset time / seek to sta… | |
+ videos[i].play(); // NOTE: auto-play must be enabled by the cl… | |
+ } | |
+} | |
+ | |
+// date / clocks. | |
+var dates = document.getElementsByClassName("datetime"); | |
+for (var i = 0; i < dates.length; i++) { | |
+ var format = dates[i].getAttribute("data-format") || ""; | |
+ var freq = dates[i].getAttribute("data-update") || 1000; // default: 1… | |
+ | |
+ var fn = (function(el, freq, format) { | |
+ return function() { | |
+ el.innerHTML = strftime(format); // allow HTML in form… | |
+ }; | |
+ })(dates[i], freq, format); | |
+ updateevery(freq, fn); | |
+} | |
+ | |
+// init slides and timings. | |
+function slides_init(rootel) { | |
+ rootel = rootel || document; | |
+ var slides = { | |
+ current: 0, | |
+ prev: -1, | |
+ prevprev: -1, | |
+ slides: [] | |
+ }; | |
+ | |
+ // find direct descendent element with class "slide". | |
+ var slideels = getdirectchilds(rootel, "slide"); | |
+ for (var i = 0; i < slideels.length; i++) { | |
+ var attrtiming = slideels[i].getAttribute("data-displaynext") … | |
+ var timing = parseInt(attrtiming); | |
+ if (timing <= 0) | |
+ timing = 5000; | |
+ | |
+ var slide = { | |
+ timing: timing, | |
+ el: slideels[i] | |
+ }; | |
+ slides.slides.push(slide); | |
+ } | |
+ return slides; | |
+} | |
+ | |
+function slides_showcurrent(slides) { | |
+ var el = slides.slides[slides.current].el; | |
+ if (el === null) | |
+ return; | |
+ el.classList.add("visible"); | |
+ playvideos(el); | |
+} | |
+ | |
+function slides_change(slides) { | |
+ if (typeof(slides.prev) !== "undefined") | |
+ slides.slides[slides.prev].elapsed = 0; | |
+ slides.slides[slides.current].elapsed = 0; | |
+ | |
+ slides_showcurrent(slides); | |
+ if (slides.onchange) | |
+ slides.onchange(slides.prev, slides.prevprev); // current, prev | |
+ | |
+ // completely hidden. | |
+ if (slides.prevprev !== -1) { | |
+ var el = slides.slides[slides.prevprev].el; | |
+ el.classList.remove("pause"); | |
+ } | |
+ | |
+ // pause / fade out. | |
+ if (slides.prev !== -1) { | |
+ var el = slides.slides[slides.prev].el; | |
+ el.classList.remove("visible"); | |
+ el.classList.add("pause"); | |
+ pausevideos(el); | |
+ } | |
+} | |
+ | |
+function slides_go(slides, n) { | |
+ // out-of-bounds or the same: do nothing. | |
+ if (n < 0 || n >= slides.slides.length || n == slides.current) | |
+ return; | |
+ slides.prevprev = slides.prev; | |
+ slides.prev = slides.current; | |
+ slides.current = n; | |
+ slides_change(slides); | |
+} | |
+ | |
+function slides_prev(slides) { | |
+ var n = slides.current - 1; | |
+ if (n < 0 && slides.slides.length) | |
+ n = slides.slides.length - 1; | |
+ slides_go(slides, n); | |
+ slides_change(slides); | |
+} | |
+ | |
+function slides_next(slides) { | |
+ var n = slides.current + 1; | |
+ if (n >= slides.slides.length) | |
+ n = 0; | |
+ slides_go(slides, n); | |
+ slides_change(slides); | |
+} | |
+ | |
+function slides_pause(slides) { | |
+ slides.paused = true; | |
+ clearTimeout(slides.timernextslide); | |
+ clearTimeout(slides.timercurrentslide); | |
+ pausevideos(slides.slides[slides.current].el); | |
+} | |
+ | |
+function slides_play(slides) { | |
+ slides_continue(slides); | |
+} | |
+ | |
+function slides_continue(slides) { | |
+ slides.paused = false; | |
+ slides_updater(slides); | |
+ slides_showcurrent(slides); | |
+} | |
+ | |
+function slides_updater(slides) { | |
+ // need more than 1 slide to change it. | |
+ if (slides.slides.length <= 1) | |
+ return; | |
+ var fn = function() { | |
+ slides_next(slides); | |
+ var currentslide = slides.slides[slides.current]; | |
+ var timing = currentslide.timing || 5000; // default: 5 seconds | |
+ timing -= (currentslide.elapsed || 0); // minus played time fo… | |
+ slides.timernextslide = setTimeout(fn, timing); | |
+ }; | |
+ var currentslide = slides.slides[slides.current]; | |
+ var timing = currentslide.timing || 5000; // default: 5 seconds | |
+ timing -= (currentslide.elapsed || 0); // minus played time for this s… | |
+ slides.timercurrentslide = setTimeout(fn, timing); | |
+} | |
+ | |
+function progressbar_init(slides, progressbar) { | |
+ if (slides.slides.length <= 1) | |
+ return; | |
+ | |
+ // config: show total progress or progress per slide. | |
+ var usetotalprogress = parseInt(progressbar.getAttribute("data-total-p… | |
+ | |
+ function progressbar_update(slides) { | |
+ var currentslide = slides.slides[slides.current]; | |
+ var total = currentslide.timing || 5000; // default: 5 seconds | |
+ | |
+ if (usetotalprogress) { | |
+ total = 0; | |
+ for (var i = 0; i < slides.slides.length; i++) { | |
+ total += parseInt(slides.slides[i].timing) || … | |
+ } | |
+ } | |
+ | |
+ currentslide.elapsed = currentslide.elapsed || 0; | |
+ | |
+ var perfnow = window.performance.now() | |
+ slides.prevprogress = slides.prevprogress || 0; | |
+ | |
+ if (!slides.paused) { | |
+ currentslide.elapsed += perfnow - slides.prevprogress; | |
+ } | |
+ slides.prevprogress = perfnow; | |
+ | |
+ var elapsed = currentslide.elapsed; | |
+ if (usetotalprogress) { | |
+ // current slide progress + all finished previous ones. | |
+ for (var i = 0; i < slides.current; i++) | |
+ elapsed += parseInt(slides.slides[i].timing) |… | |
+ } | |
+ | |
+ // generate linear gradient with ticks for the total progress. | |
+ if (usetotalprogress) { | |
+ var total = 0; | |
+ for (var i = 0; i < slides.slides.length; i++) { | |
+ total += parseInt(slides.slides[i].timing) || … | |
+ } | |
+ | |
+ var pattern = []; | |
+ var timing = 0; | |
+ for (var i = 0; i < slides.slides.length; i++) { | |
+ timing += parseInt(slides.slides[i].timing) ||… | |
+ var percent = (timing / total) * 100.0; | |
+ | |
+ pattern.push("rgba(255, 255, 255, 0.0) " + (Ma… | |
+ | |
+ // don't show tick for last. | |
+ if (i !== slides.slides.length - 1) { | |
+ // tick color | |
+ pattern.push("rgba(255,255,255, 1.0) "… | |
+ pattern.push("rgba(255,255,255, 0.0) "… | |
+ } | |
+ } | |
+ | |
+ // ticks gradient; | |
+ var gradient = "linear-gradient(90deg, " + pattern.joi… | |
+ // progress gradient. | |
+ var percent = (elapsed / total) * 100.0; | |
+ gradient += ", linear-gradient(90deg, rgba(255, 255, 2… | |
+ "%, rgba(255, 255, 255, 0) " + (percent + 0.1)… | |
+ "%, rgba(255, 255, 255, 0) 100%)" // actual pr… | |
+ progressbar.style.background = gradient; | |
+ progressbar.style.width = "100%"; | |
+ } else { | |
+ var percent = (elapsed / total) * 100.0; | |
+ progressbar.style.width = percent + "%"; | |
+ } | |
+ } | |
+ | |
+ function progressbar_updater() { | |
+ var fn = function() { | |
+ progressbar_update(slides); | |
+ }; | |
+ setInterval(fn, 16); // 16ms, update at ~60fps | |
+ } | |
+ | |
+ progressbar_updater(slides); | |
+} | |
+ | |
+// initialize slides, can be nested. | |
+var rootels = document.getElementsByClassName("slides") || []; | |
+for (var i = 0; i < rootels.length; i++) { | |
+ var rootel = rootels[i]; | |
+ | |
+ var slides = slides_init(rootel); | |
+ slides_play(slides); | |
+ | |
+ /* progressbar: shows current slide progress */ | |
+ var progressbars = getdirectchilds(rootel, "progressbar"); | |
+ if (progressbars.length) // use first progressbar. | |
+ progressbar_init(slides, progressbars[0]); | |
+} | |
+ | |
+/* mechanism to poll if screen data is updated and needs a full refresh | |
+ ignoring the cache. */ | |
+var lasthash = null; | |
+var els = document.getElementsByClassName("check-updates"); | |
+for (var i = 0; i < els.length; i++) { | |
+ var el = els[i]; | |
+ var interval = parseInt(el.getAttribute("data-update")); | |
+ if (interval <= 0) | |
+ continue; | |
+ var url = el.getAttribute("data-url") || ""; | |
+ if (url === "") | |
+ continue; | |
+ | |
+ var fn = function(x) { | |
+ if (x.status >= 200 && x.status < 400) { | |
+ var newhash = x.responseText || ""; | |
+ if (lasthash === null) { | |
+ lasthash = newhash; | |
+ } else if (newhash !== lasthash) { | |
+ // reload, ignore cache: works in Chrome and F… | |
+ window.location.reload(true); | |
+ } | |
+ } | |
+ }; | |
+ | |
+ setInterval(function() { | |
+ xhr(url, fn, 10000); | |
+ }, interval); | |
+ xhr(url, fn, 10000); | |
+ | |
+ break; | |
+} | |
+ | |
+window.addEventListener("keyup", function(e) { | |
+ switch (e.keyCode) { | |
+ case 32: // space: toggle pause/play. | |
+ if (slides.paused) | |
+ slides_continue(slides); | |
+ else | |
+ slides_pause(slides); | |
+ break; | |
+ case 37: // left arrow: previous slide. | |
+ slides_prev(slides); | |
+ break; | |
+ case 13: // return or right arrow next slide. | |
+ case 39: | |
+ slides_next(slides); | |
+ break; | |
+ case 49: // '1': go to slide 1 | |
+ case 50: // '2': go to slide 2 | |
+ case 51: // '3': go to slide 3 | |
+ case 52: // '4': go to slide 4 | |
+ case 53: // '5': go to slide 5 | |
+ case 54: // '6': go to slide 6 | |
+ case 55: // '7': go to slide 7 | |
+ case 56: // '8': go to slide 8 | |
+ case 57: // '9': go to slide 9 | |
+ slides_go(slides, e.keyCode - 49); | |
+ break; | |
+ } | |
+}, false); | |
diff --git a/narrowcasting/style.css b/narrowcasting/style.css | |
@@ -0,0 +1,237 @@ | |
+@keyframes fade-in { | |
+ 0% { | |
+ opacity: 0; | |
+ } | |
+ 100% { | |
+ opacity: 1; | |
+ } | |
+} | |
+@keyframes fade-out { | |
+ 0% { | |
+ opacity: 1; | |
+ } | |
+ 100% { | |
+ opacity: 0; | |
+ } | |
+} | |
+@keyframes news-slide-in { | |
+ 0% { | |
+ margin-left: 100%; | |
+ opacity: 0; | |
+ } | |
+ 100% { | |
+ margin-left: 0; | |
+ opacity: 1; | |
+ } | |
+} | |
+@keyframes news-slide-out { | |
+ 0% { | |
+ margin-left: 0; | |
+ opacity: 1; | |
+ } | |
+ 100% { | |
+ opacity: 0; | |
+ margin-left: -100%; | |
+ } | |
+} | |
+body { | |
+ font-family: sans-serif; | |
+} | |
+html, body { | |
+ overflow: hidden; | |
+} | |
+h1 { | |
+ font-size: 120%; | |
+ font-weight: bold; | |
+ margin: 5px 0; | |
+ padding: 0; | |
+} | |
+ul { | |
+ margin: 0; | |
+ padding: 0; | |
+} | |
+iframe { | |
+ border: 0; | |
+} | |
+.news-ticker { | |
+ position: fixed; | |
+ background-color: #eee; | |
+ line-height: 90px; | |
+ height: 90px; | |
+ overflow: hidden; | |
+} | |
+.news-ticker div { | |
+ position: absolute; | |
+ padding: 0 0 0 5px; | |
+ line-height: 90px; | |
+ height: 90px; | |
+ animation: ease-out news-slide-in 1s; | |
+} | |
+ | |
+.news-ticker div.out { | |
+ animation: ease-out news-slide-out 2s; | |
+} | |
+.ticker1 { | |
+ z-index: 9999; | |
+ bottom: 90px; | |
+ left: 0; | |
+ right: 0; | |
+ font-size: 40px; | |
+ background-color: #333; | |
+ color: #fff; | |
+ font-weight: bold; | |
+} | |
+.ticker2 { | |
+ z-index: 9999; | |
+ bottom: 0; | |
+ left: 0; | |
+ right: 0; | |
+ font-size: 40px; | |
+ background-color: #555; | |
+ color: #fff; | |
+ font-weight: bold; | |
+} | |
+.logo { | |
+ width: 854px; | |
+ background-image: url('logo.png'); | |
+ background-repeat: no-repeat; | |
+ background-position: center left; | |
+ background-size: auto 160px; | |
+ height: 180px; | |
+ position: absolute; | |
+ top: 0; | |
+ left: 25px; | |
+ z-index: 999; | |
+} | |
+.screen { | |
+ position: absolute; | |
+ top: 0; | |
+ left: 0; | |
+ right: 0; | |
+ bottom: 0; | |
+ | |
+ background-repeat: no-repeat; | |
+} | |
+.topbar { | |
+ z-index: 997; | |
+ | |
+ position: absolute; | |
+ top: 0; | |
+ left: 0; | |
+ height: 115px; | |
+ right: 0; | |
+ background-color: #333; | |
+} | |
+.topbar-right { | |
+ z-index: 999; | |
+ position: absolute; | |
+ top: 0; | |
+ right: 0; | |
+ height: 115px; | |
+ background-color: #333; | |
+ background-repeat: no-repeat; | |
+ text-align: left; | |
+} | |
+ | |
+.topbar-info-dashboard { | |
+ z-index: 999; | |
+ position: absolute; | |
+ top: 0; | |
+ width: 343px; | |
+ left: 100px; | |
+ height: 158px; | |
+ background-image: url('img/image.png'); | |
+ background-repeat: no-repeat; | |
+ text-align: left; | |
+} | |
+ | |
+.date { | |
+ float: right; | |
+ color: #fff; | |
+ font-size: 60px; | |
+ line-height: 115px; | |
+ padding-right: 70px; | |
+ white-space: nowrap; | |
+} | |
+.time { | |
+ margin-left: 50px; | |
+ padding-right: 50px; | |
+ float: right; | |
+ color: #fff; | |
+ font-weight: bold; | |
+ font-size: 90px; | |
+ line-height: 115px; | |
+ height: 115px; | |
+} | |
+.slide { | |
+ display: none; | |
+ background-repeat: no-repeat; | |
+ /* default background color, must be set for overlapping slides for tr… | |
+ /*background-color: #fff;*/ | |
+} | |
+ | |
+body > .screen > .slides > .slide { | |
+ position: absolute; | |
+ top: 0; | |
+ right: 0; | |
+ bottom: 0; | |
+ left: 0; | |
+ height: 100%; | |
+ width: 100%; | |
+} | |
+body > .screen > .slides > .slide-2 { | |
+ background-color: #333; | |
+} | |
+ | |
+.visible { | |
+ z-index: 995; /* visible has more preference over paused */ | |
+ display: block; | |
+ | |
+ animation: ease-in fade-in 1s; | |
+ animation-play-state: running; | |
+} | |
+ | |
+.pause { | |
+ z-index: 990; | |
+ display: block; | |
+ opacity: 0; | |
+ | |
+ animation: ease-out fade-out 1s; | |
+ animation-play-state: running; | |
+} | |
+ | |
+.animate-once { | |
+ animation-iteration-count: 1; | |
+} | |
+.progressbar { | |
+ z-index: 9999; | |
+ position: absolute; | |
+ bottom: 0; | |
+ height: 3px; | |
+ left: 0; | |
+ width: 0%; | |
+ background-color: #fff; | |
+ opacity: 1.0 !important; | |
+} | |
+ | |
+.widget { | |
+ padding: 0; | |
+ margin: 0; | |
+ color: #333; | |
+ font-size: 14pt; | |
+} | |
+.widget-header { | |
+ font-size: 30px; | |
+ font-weight: bold; | |
+ background-repeat: no-repeat; | |
+ color: #fff; | |
+ line-height: 60px; | |
+ padding: 0 10px; | |
+ text-transform: uppercase; | |
+} | |
+.widget-body { | |
+ padding: 5px 10px; | |
+} | |
+.widget-body ul { | |
+ padding: 0 20px; | |
+} |