├── .jscsrc ├── LICENSE ├── README.md ├── index.html ├── perfectloops └── index.html ├── sideshow-example.gif ├── sideshow.css └── sideshow.js /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | 4 | "disallowImplicitTypeConversion": ["string"], 5 | "disallowMixedSpacesAndTabs": true, 6 | "disallowMultipleLineBreaks": true, 7 | "disallowMultipleVarDecl": true, 8 | "disallowNewlineBeforeBlockStatements": true, 9 | "disallowMultipleLineStrings": true, 10 | "disallowMultipleVarDecl": null, 11 | "disallowSpaceAfterPrefixUnaryOperators": true, 12 | "disallowSpacesInAnonymousFunctionExpression": { 13 | "beforeOpeningRoundBrace": true 14 | }, 15 | "disallowSpacesInsideObjectBrackets": null, 16 | "disallowSpacesInsideArrayBrackets": "all", 17 | "disallowSpacesInsideParentheses": true, 18 | "disallowTrailingWhitespace": true, 19 | 20 | "fileExtensions": [".js"], 21 | 22 | "maximumLineLength": { 23 | "value": 120, 24 | "allowComments": true, 25 | "allowRegex": true 26 | }, 27 | 28 | "requireCamelCaseOrUpperCaseIdentifiers": true, 29 | "requireCommaBeforeLineBreak": true, 30 | "requireCurlyBraces": [ 31 | "if", 32 | "else", 33 | "for", 34 | "while", 35 | "do", 36 | "try", 37 | "catch" 38 | ], 39 | "requireOperatorBeforeLineBreak": true, 40 | "requireSpaceAfterBinaryOperators": true, 41 | "requireSpaceAfterKeywords": [ 42 | "do", 43 | "for", 44 | "if", 45 | "else", 46 | "switch", 47 | "case", 48 | "try", 49 | "catch", 50 | "void", 51 | "while", 52 | "with", 53 | "return", 54 | "typeof" 55 | ], 56 | "requireSpaceBeforeBinaryOperators": [ 57 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 58 | "&=", "|=", "^=", "+=", 59 | 60 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 61 | "|", "^", "&&", "||", "===", "==", ">=", 62 | "<=", "<", ">", "!=", "!==" 63 | ], 64 | "requireSpacesInConditionalExpression": true, 65 | "requireSpaceBeforeBlockStatements": true, 66 | "requireSpacesInForStatement": true, 67 | "requireLineFeedAtFileEnd": true, 68 | "requireSpacesInFunctionExpression": { 69 | "beforeOpeningCurlyBrace": true 70 | }, 71 | 72 | "validateLineBreaks": "LF", 73 | "validateIndentation": 4, 74 | "validateQuoteMarks": { 75 | "mark": "\"", "escape": true 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mike Arvela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sideshow 2 | 3 | **A minimalistic dashboard-as-a-service** 4 | 5 | Sideshow is like a dashboard application, where instead of adding widgets you can have it show the sites you choose. It's perfect for showing bus timetables, weather and server status pages on your office kitchen, or funny GIFs in your screen saver. 6 | 7 | Sideshow is easy to use and customize. Just open the HTML page with a number of parameters and you're done. Should work with any modern browser. 8 | 9 | ![Screenshot](https://github.com/mieky/sideshow/raw/master/sideshow-example.gif) 10 | 11 | ## Live examples 12 | 13 | Three sites, 5-second delay, 30-second refresh:
[delay=5&refresh=30&sites=url1,url2](http://mike.fi/sideshow/?delay=5&refresh=30&sites=http://chilicorn.org,http://www.futurice.com) 14 | 15 | Two sites, one-minute delay, half-an-hour refresh (default):
[delay=60&sites=url1,url2](http://mike.fi/sideshow/?delay=60&sites=http://chilicorn.org,http://www.futurice.com) 16 | 17 | Showing URLs with special characters by URI-encoding them:
18 | [delay=60&sites=someEncodedUrl](http://mike.fi/sideshow/?delay=5&sites=http://mike.fi/sideshow/?delay=5&sites=https://www.djangopackages.com/search/%3Fq%3D%F0%9F%98%80%20emojis%2C%20baby) (*see link source*) 19 | 20 | You can also link to a .gif to have it display fullscreen!
21 | [delay=3&sites=gif1,gif2](http://mike.fi/sideshow/?delay=3&sites=http://cdn.gifbay.com/2013/01/1_for_perfect_loop-26256.gif,http://38.media.tumblr.com/aed842e1fa863d6e991def1b9505d77f/tumblr_nx1lhqQZDl1rxpytqo1_400.gif) 22 | 23 | ## Usage 24 | 25 | Navigate to the index.html, setting some query parameters: 26 | 27 | ``` 28 | http://mike.fi/sideshow/? 29 | delay=5 30 | &refresh=900 31 | &sites=http://chilicorn.org,http://www.futurice.com 32 | ``` 33 | 34 | - `delay` specifies seconds to wait before changing tab (defaults to 10) 35 | - `refresh` specifies seconds between reloading all pages (defaults to 1800, set 0 to disable) 36 | - `sites` is a comma-separated list of sites to cycle. URI encoding is supported. 37 | 38 | You can actually use the URL http://mike.fi/sideshow for your needs, because no data gets saved on a server. It's just static HTML files served via GitHub pages. 39 | 40 | Also see the note on [browser security restrictions.](#work-around-security-restrictions) 41 | 42 | ## How to... 43 | 44 | ### Install on my own server? 45 | 46 | Just drop the files to a web server. That's it. You might as well use it from the live demo at http://mike.fi/sideshow, because your local data isn't saved anywhere (there's Google Analytics installed, though.) 47 | 48 | ### Work around security restrictions? 49 | 50 | This is a common problem, and for good reasons. Many sites set an [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options) response header to tell the browser that this site should not be allowed inside a frame, and the browsers respect this. 51 | 52 | If you love speed and danger and are running Chrome, you can **at your own risk** work around this by either consciously [installing an extension that disables this security mechanism](https://chrome.google.com/webstore/detail/ignore-x-frame-headers/gleekbfjekiniecknbkamfmkohkpodhe), or starting Chrome with `--disable-web-security`. After this, you should be able to use sideshow with i.e. the GitHub status page. 53 | 54 | ### Use URLs with special characters? 55 | 56 | Worry not - you can URL encode your URLs, and sideshow knows to decode them. For example, try pasting this on your browser location bar: 57 | 58 | `javascript:alert(encodeURIComponent("http://mysite.com/?complexParameters=true"))` 59 | 60 | ### Using it in a screen saver? 61 | 62 | That's a great idea. In OS X you can use a [WebView screen saver](https://github.com/liquidx/webviewscreensaver) and then configure it with sideshow URLs. Just look up some cool [perfect loops](https://www.reddit.com/r/perfectloops) and your screen saver will be the coolest around. 63 | 64 | ## Todo 65 | 66 | - Add a generator page for easily populating the parameters 67 | 68 | ## Acknowledgements 69 | 70 | This project is a grateful recipient of the [Futurice Open Source sponsorship program](http://futurice.com/blog/sponsoring-free-time-open-source-activities). 71 | 72 | ## License 73 | 74 | [MIT](https://github.com/mieky/sideshow/blob/master/LICENSE) 75 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sideshow 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /perfectloops/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | perfectloops 5 | 6 | 7 | 8 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /sideshow-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mieky/sideshow/bfb2cdb6085ae3860737142fd2cdb3cb964d3c09/sideshow-example.gif -------------------------------------------------------------------------------- /sideshow.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | overflow: hidden; 6 | } 7 | 8 | iframe { 9 | border: 0; 10 | width: 100%; 11 | height: 100%; 12 | position: absolute; 13 | top: 0; 14 | bottom: 0; 15 | left: 0; 16 | right: 0; 17 | } 18 | 19 | .fullscreen { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | right: 0; 24 | bottom: 0; 25 | background-size: cover; 26 | background-position: center center; 27 | } 28 | -------------------------------------------------------------------------------- /sideshow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var defaultConfig = { 4 | delay: 10, 5 | refresh: 1800, 6 | sites: [ 7 | "http://www.yle.fi", 8 | "http://www.futurice.com", 9 | "http://www.alupark.fi" 10 | ] 11 | }; 12 | 13 | function isGifName(str) { 14 | return str.substring(str.lastIndexOf(".")) === ".gif"; 15 | } 16 | 17 | function getGifNameFromParams() { 18 | var p = (window.location.search.substring(1) || "=").split("="); 19 | var gifName = p[1]; 20 | if (p[0] === "gif" && isGifName(gifName)) { 21 | return gifName; 22 | } 23 | return null; 24 | } 25 | 26 | function mergeConfiguration() { 27 | var args = [].slice.call(arguments, 0); 28 | return args.reduce(function(acc, curr) { 29 | for (var prop in curr) { 30 | acc[prop] = curr[prop]; 31 | } 32 | return acc; 33 | }, {}); 34 | } 35 | 36 | function parseParameters() { 37 | var params = window.location.search.substring(1) || "="; 38 | return params.split("&").reduce(function(acc, curr) { 39 | var parts = curr.split("="); 40 | var value = parts[1].split(",").map(function(v) { 41 | return decodeURIComponent(v); 42 | }); 43 | acc[parts[0]] = value.length > 1 ? value : value[0]; 44 | return acc; 45 | }, {}); 46 | } 47 | 48 | function createIframes(sites) { 49 | if (typeof sites !== "object") { 50 | sites = [sites]; 51 | } 52 | return sites.map(function(url) { 53 | var iframe = document.createElement("iframe"); 54 | if (isGifName(url)) { 55 | var h = window.location.href, 56 | s = window.location.search; 57 | url = h.substring(0, h.indexOf(s)) + "?gif=" + url; 58 | } 59 | iframe.src = url; 60 | return iframe; 61 | }); 62 | } 63 | 64 | function addIframes(iframes) { 65 | iframes.forEach(function(i) { 66 | i.style.display = "none"; 67 | document.body.appendChild(i); 68 | }); 69 | } 70 | 71 | function startCycling(iframes, delay) { 72 | var currentIndex = -1; 73 | nextFrame(iframes, currentIndex); 74 | return setInterval(function() { 75 | currentIndex = nextFrame(iframes, currentIndex); 76 | }, delay * 1000); 77 | } 78 | 79 | function nextFrame(iframes, currentIndex) { 80 | var newIndex = (currentIndex + 1) % iframes.length; 81 | if (iframes[currentIndex]) { 82 | iframes[currentIndex].style.display = "none"; 83 | } 84 | iframes[newIndex].style.display = "block"; 85 | return newIndex; 86 | } 87 | 88 | function startGifMode() { 89 | console.log("GIF mode!"); 90 | var el = document.createElement("div"); 91 | el.innerHTML = '
'; 92 | document.body.appendChild(el.firstChild); 93 | } 94 | 95 | function startCycleMode() { 96 | var config = mergeConfiguration(defaultConfig, parseParameters()); 97 | console.log("Cycling sites with configuration:", config); 98 | 99 | var iframes = createIframes(config.sites); 100 | addIframes(iframes); 101 | startCycling(iframes, config.delay); 102 | 103 | if (config.refresh > 0) { 104 | setInterval(function() { 105 | iframes.forEach(function(i) { 106 | return i.src = i.src; 107 | }); 108 | }, config.refresh * 1000); 109 | } 110 | } 111 | 112 | var gifName = getGifNameFromParams(); 113 | if (gifName !== null) { 114 | startGifMode(); 115 | } else { 116 | startCycleMode(); 117 | } 118 | --------------------------------------------------------------------------------