diff --git a/public/_partials/layout.jade b/public/_partials/layout.jade index 4505acb..df3b3e0 100644 --- a/public/_partials/layout.jade +++ b/public/_partials/layout.jade @@ -117,6 +117,8 @@ html(id=id) include ../_partials/footer + include ../_partials/sw + script(src='/js/sticky-pagination-fixer.js') script(src='/js/raf.js') diff --git a/public/_partials/sw.jade b/public/_partials/sw.jade new file mode 100644 index 0000000..afdbbe1 --- /dev/null +++ b/public/_partials/sw.jade @@ -0,0 +1,76 @@ +script#service-worker-template(type='text/x-template') + | + |
+ |

+ |
+ + +div#service-worker.service-worker-wrapper + + +script. + if ('serviceWorker' in navigator) { + function swExists() { + document.querySelector('#service-worker').innerHTML = document.querySelector('#service-worker-template').innerHTML; + } + function swToggle(status) { + document.querySelector('#service-worker').classList.toggle('service-worker-wrapper--show', status); + } + function swMsg(msg) { + document.querySelector('#service-worker-message').innerHTML = msg; + swToggle(true); + } + swExists(); + + // Very fist activation + function onFirstLoad() { + var msg = 'Service Worker magic activated!'; + console.log(msg); + swMsg(msg); + } + + function onNewUpdate() { + var msg = 'New update available. Refresh!'; + console.log(msg); + swMsg(msg); + } + + function onInstalled() { + console.log('sw installed'); + } + + function onStateChange(newWorker) { + if (newWorker.state === 'activated') { + if (navigator.serviceWorker.controller) { + onNewUpdate(); + } + else { + onFirstLoad(); + } + } + else if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + onInstalled(); + } + } + + function onUpdateFound(registration) { + var newWorker = registration.installing; + + registration.installing.addEventListener('statechange', + () => onStateChange(newWorker)); + } + + navigator.serviceWorker.register('/sw.js') + .then(function(registration) { + // Registration was successful + registration.addEventListener('updatefound', () => onUpdateFound(registration)); + }) + .catch(function(err) { + // registration failed :( + console.log('ServiceWorker registration failed: ', err); + }); + } \ No newline at end of file diff --git a/public/css/_sw.scss b/public/css/_sw.scss new file mode 100644 index 0000000..aeaa230 --- /dev/null +++ b/public/css/_sw.scss @@ -0,0 +1,111 @@ +/* Service Worker + ========================================================================== */ +$c-bubble: #000; +$bubble-size: 30px; + +.service-worker-wrapper { + background: rgba(0, 0, 0, .75); + bottom: 0; + position: fixed; + padding: 10px 0 0; + transform: translate3d(0, 100%, 0); + transition: transform ease-in-out 0.5s; + width: 100%; + + & img { + display: block; + max-width: 320px; + width: 100% + } +} +.service-worker-bubble { + align-items: center; + background: $c-white; + border: 2px solid $c-bubble; + border-radius: 100%; + color: $c-bubble; + display: flex; + font-size: 12px; + height: 120px; + justify-content: center; + line-height: 1.4; + margin: 5px 5px 0 0; + padding: 1em; + position: absolute; + right: 0; + text-align: center; + top: 0; + width: 40%; + + @include mq-min-check(360px) { + & { + font-size: inherit; + line-height: inherit; + width: 170px; + } + } + @include mq-min-check(400px) { + & { + left: 200px + } + } + @include mq-min-check(768px) { + & { + height: 150px; + width: 200px; + } + } +} +.service-worker-bubble:before, +.service-worker-bubble:after { + border-color: transparent $c-bubble; + border-style: solid; + border-width: ($bubble-size / 2) $bubble-size ($bubble-size / 2) 0; + content: ''; + height: $bubble-size; + left: -26px; + position: absolute; + top: calc(67% - 15px); + transform: rotate(-20deg); + width: $bubble-size; +} +.service-worker-bubble:after { + border-color: transparent $c-white; + left: -22px; + top: calc(67% - 16px); +} +.service-worker-wrapper--show { + transform: translate3d(0, 0, 0); +} +.service-worker-dismiss { + background: $c-blue; + border: 0 none; + color: $c-white; + cursor: pointer; + display: block; + padding: 0; + position: absolute; + left: 0; + top: 0; + width: 32px; + -webkit-appearance: none; + + &:hover, &:focus { + background: $c-green; + outline: 0 none; + } + + & svg { + display: block; + fill: currentColor; + height: 100%; + width: 100%; + } + + @include mq-min-check(400px) { + & { + left: auto; + right: 0; + } + } +} \ No newline at end of file diff --git a/public/css/style.scss b/public/css/style.scss index 7e10ea2..24825db 100644 --- a/public/css/style.scss +++ b/public/css/style.scss @@ -83,6 +83,16 @@ ul, ol, li { display: inline-block; vertical-align: middle; width: 100%; + + &:after { + background: $c-grey-subtitle; + bottom: 0; + content: ''; + left: 0; + position: absolute; + right: 0; + top: 0; + } } @supports (object-fit: cover) { @@ -442,3 +452,4 @@ ul, ol, li { @import "mq"; +@import "sw"; diff --git a/public/images/sponsors/amex.png b/public/images/sponsors/amex.png old mode 100755 new mode 100644 diff --git a/public/images/sw.png b/public/images/sw.png new file mode 100644 index 0000000..91884a0 Binary files /dev/null and b/public/images/sw.png differ diff --git a/public/sw.js b/public/sw.js index c41e162..c442927 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,22 +1,80 @@ /* jshint esnext: true */ +var CACHE_NAME = 'ffconf-v1.2.6'; -// It's too early to implement offline support or other niceties +// TODO: decide which files +var urlsToCache = [ + '/', + '/workshops', + '/css/style.css', + '/js/script.js', + '/images/left-logic.svg' +]; -// https://remysharp.com/2016/03/22/the-copy--paste-guide-to-your-first-service-worker -const cacheName = 'v1::static'; -self.addEventListener('install', e => { - e.waitUntil( - caches.open(cacheName).then(cache => { - return cache.addAll([ - '/', - ]).then(() => self.skipWaiting()); - }) +self.addEventListener('install', function(event) { + // Perform install steps + event.waitUntil( + caches.open(CACHE_NAME) + .then(function(cache) { + return cache.addAll(urlsToCache); + }) + .then(function() { + return self.skipWaiting(); + }) ); }); -self.addEventListener('fetch', event => { + +self.addEventListener('fetch', function(event) { event.respondWith( - caches.match(event.request).then(res => res || fetch(event.request)) + caches.match(event.request) + .then(function(response) { + // Cache hit - return response + if (response) { + return response; + } + + // IMPORTANT: Clone the request. A request is a stream and + // can only be consumed once. Since we are consuming this + // once by cache and once by the browser for fetch, we need + // to clone the response + var fetchRequest = event.request.clone(); + + return fetch(fetchRequest).then( + function(response) { + // Check if we received a valid response + if(!response || response.status !== 200 || response.type !== 'basic') { + return response; + } + + // IMPORTANT: Clone the response. A response is a stream + // and because we want the browser to consume the response + // as well as the cache consuming the response, we need + // to clone it so we have 2 stream. + var responseToCache = response.clone(); + + caches.open(CACHE_NAME) + .then(function(cache) { + cache.put(event.request, responseToCache); + }); + + return response; + } + ); + }) + ); +}); + + +self.addEventListener('activate', function(event) { + event.waitUntil( + caches.keys() + .then(function(keyList) { + return Promise.all(keyList.map(function(key) { + if (key !== CACHE_NAME) { + return caches.delete(key); + } + })); + }) ); }); \ No newline at end of file