add progressive web app stuff (manifest & service worker)
authorFrédéric Péters <fpeters@0d.be>
Tue, 21 Feb 2017 18:16:09 +0000 (19:16 +0100)
committerFrédéric Péters <fpeters@0d.be>
Sat, 1 Apr 2017 10:37:18 +0000 (12:37 +0200)
README
img/launcher-icon-1x.png [new file with mode: 0644]
img/launcher-icon-2x.png [new file with mode: 0644]
img/launcher-icon-4x.png [new file with mode: 0644]
img/launcher-icon.svg [new file with mode: 0644]
index.html
manifest.json [new file with mode: 0644]
nanofun.js
service-worker.js [new file with mode: 0644]

diff --git a/README b/README
index f998416..5891b17 100644 (file)
--- a/README
+++ b/README
@@ -26,5 +26,9 @@ A copy of the jQuery JavaScript library is included, it's
   Dual licensed under the MIT and GPL licenses.
   http://docs.jquery.com/License
 
+Service worker support has been done using this example:
+  https://github.com/lyzadanger/serviceworker-example/
+  MIT-licensed.
+
 External led mode support is based on a sysex dump recorded by the Overtone
 project; MIT licensed; https://github.com/overtone/overtone/
diff --git a/img/launcher-icon-1x.png b/img/launcher-icon-1x.png
new file mode 100644 (file)
index 0000000..ca96239
Binary files /dev/null and b/img/launcher-icon-1x.png differ
diff --git a/img/launcher-icon-2x.png b/img/launcher-icon-2x.png
new file mode 100644 (file)
index 0000000..a8a5eee
Binary files /dev/null and b/img/launcher-icon-2x.png differ
diff --git a/img/launcher-icon-4x.png b/img/launcher-icon-4x.png
new file mode 100644 (file)
index 0000000..0907d8c
Binary files /dev/null and b/img/launcher-icon-4x.png differ
diff --git a/img/launcher-icon.svg b/img/launcher-icon.svg
new file mode 100644 (file)
index 0000000..18f717e
--- /dev/null
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="210mm"
+   height="210mm"
+   viewBox="0 0 210 210"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92.1 r15371"
+   sodipodi:docname="launcher-icon.svg"
+   inkscape:export-filename="/home/fred/image.png"
+   inkscape:export-xdpi="5.8099999"
+   inkscape:export-ydpi="5.8099999">
+  <defs
+     id="defs2">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient6057">
+      <stop
+         style="stop-color:#2bff4b;stop-opacity:1;"
+         offset="0"
+         id="stop6053" />
+      <stop
+         style="stop-color:#2bff4b;stop-opacity:0;"
+         offset="1"
+         id="stop6055" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4599"
+       osb:paint="gradient">
+      <stop
+         style="stop-color:#2bff4b;stop-opacity:1;"
+         offset="0"
+         id="stop4595" />
+      <stop
+         style="stop-color:#2bff4b;stop-opacity:0;"
+         offset="1"
+         id="stop4597" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4570">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop4566" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop4568" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4570"
+       id="linearGradient4572"
+       x1="18.577377"
+       y1="130.59598"
+       x2="338.45535"
+       y2="288.21207"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6057"
+       id="linearGradient6061"
+       x1="17.443447"
+       y1="151.38467"
+       x2="414.42859"
+       y2="226.9799"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.7"
+     inkscape:cx="308.52781"
+     inkscape:cy="396.25129"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1600"
+     inkscape:window-height="836"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-87)">
+    <g
+       id="g4564"
+       style="stroke:url(#linearGradient4572);fill:url(#linearGradient6061);fill-opacity:1;opacity:1;fill-rule:nonzero">
+      <g
+         style="stroke:url(#linearGradient4572);stroke-opacity:1;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;fill:url(#linearGradient6061);fill-opacity:1;fill-rule:nonzero"
+         transform="translate(0.30057716)"
+         id="g50">
+        <rect
+           rx="17.764881"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient6061);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient4572);stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+           id="rect10-3-6"
+           width="92.98214"
+           height="92.98214"
+           x="6.8035851"
+           y="94.404762"
+           ry="17.764881" />
+        <rect
+           rx="17.764881"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient6061);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient4572);stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+           id="rect10-3-7"
+           width="92.98214"
+           height="92.98214"
+           x="109.61312"
+           y="94.782745"
+           ry="17.764881" />
+      </g>
+      <g
+         style="stroke:url(#linearGradient4572);stroke-opacity:1;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;fill:url(#linearGradient6061);fill-opacity:1;fill-rule:nonzero"
+         transform="translate(0.30057716,102.24256)"
+         id="g50-5">
+        <rect
+           rx="17.764881"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient6061);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient4572);stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+           id="rect10-3-6-3"
+           width="92.98214"
+           height="92.98214"
+           x="6.8035851"
+           y="94.404762"
+           ry="17.764881" />
+        <rect
+           rx="17.764881"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient6061);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient4572);stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+           id="rect10-3-7-5"
+           width="92.98214"
+           height="92.98214"
+           x="109.61312"
+           y="94.782745"
+           ry="17.764881" />
+      </g>
+    </g>
+  </g>
+</svg>
index 674cd61..140c994 100644 (file)
@@ -3,6 +3,7 @@
  <head>
   <meta charset="UTF-8"><!-- ♫ -->
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link rel="manifest" href="manifest.json">
   <title>nanoFUN</title>
   <script src="jquery.min.js"></script>
   <script src="nanofun.js"></script>
diff --git a/manifest.json b/manifest.json
new file mode 100644 (file)
index 0000000..1c2af07
--- /dev/null
@@ -0,0 +1,25 @@
+{
+  "short_name": "nanoFUN",
+  "name": "nanoFUN MIDI sampler",
+  "icons": [
+    {
+      "src": "img/launcher-icon-1x.png",
+      "type": "image/png",
+      "sizes": "48x48"
+    },
+    {
+      "src": "img/launcher-icon-2x.png",
+      "type": "image/png",
+      "sizes": "96x96"
+    },
+    {
+      "src": "img/launcher-icon-4x.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    }
+  ],
+  "start_url": "index.html",
+  "display": "standalone",
+  "background_color": "#2196F3",
+  "theme_color": "#2196F3"
+}
index 627a47b..3fd313d 100644 (file)
@@ -407,3 +407,15 @@ var nanofun = function() {
 }
 
 $(function() { nanofun(); });
+
+if ('serviceWorker' in navigator) {
+  window.addEventListener('load', function() {
+    navigator.serviceWorker.register('service-worker.js').then(function(registration) {
+      // Registration was successful
+      console.log('ServiceWorker registration successful with scope: ', registration.scope);
+    }).catch(function(err) {
+      // registration failed :(
+      console.log('ServiceWorker registration failed: ', err);
+    });
+  });
+}
diff --git a/service-worker.js b/service-worker.js
new file mode 100644 (file)
index 0000000..4d25f26
--- /dev/null
@@ -0,0 +1,117 @@
+/* global self, caches, fetch, URL, Response */
+'use strict';
+
+var config = {
+  version: 'v1',
+  staticCacheItems: [
+    'index.html',
+    'jquery.min.js',
+    'nanofun.js',
+    'nanofun.css'
+  ]
+};
+
+function cacheName (key, opts) {
+  return `${opts.version}-${key}`;
+}
+
+function addToCache (cacheKey, request, response) {
+  if (response.ok) {
+    var copy = response.clone();
+    caches.open(cacheKey).then( cache => {
+      cache.put(request, copy);
+    });
+  }
+  return response;
+}
+
+function fetchFromCache (event) {
+  return caches.match(event.request).then(response => {
+    if (!response) {
+      throw Error(`${event.request.url} not found in cache`);
+    }
+    return response;
+  });
+}
+
+function offlineResponse (resourceType, opts) {
+  return undefined;
+}
+
+self.addEventListener('install', event => {
+  function onInstall (event, opts) {
+    var cacheKey = cacheName('static', opts);
+    return caches.open(cacheKey)
+      .then(cache => cache.addAll(opts.staticCacheItems));
+  }
+
+  event.waitUntil(
+    onInstall(event, config).then( () => self.skipWaiting() )
+  );
+});
+
+self.addEventListener('activate', event => {
+  function onActivate (event, opts) {
+    return caches.keys()
+      .then(cacheKeys => {
+        var oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
+        var deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
+        return Promise.all(deletePromises);
+      });
+  }
+
+  event.waitUntil(
+    onActivate(event, config)
+      .then( () => self.clients.claim() )
+  );
+});
+
+self.addEventListener('fetch', event => {
+
+  function shouldHandleFetch (event, opts) {
+    var request            = event.request;
+    var url                = new URL(request.url);
+    var criteria           = {
+      isGETRequest      : request.method === 'GET',
+      isFromMyOrigin    : url.origin === self.location.origin
+    };
+    var failingCriteria    = Object.keys(criteria)
+      .filter(criteriaKey => !criteria[criteriaKey]);
+    return !failingCriteria.length;
+  }
+
+  function onFetch (event, opts) {
+    var request = event.request;
+    var acceptHeader = request.headers.get('Accept');
+    var resourceType = 'static';
+    var cacheKey;
+
+    if (acceptHeader.indexOf('text/html') !== -1) {
+      resourceType = 'content';
+    } else if (acceptHeader.indexOf('image') !== -1) {
+      resourceType = 'image';
+    }
+
+    cacheKey = cacheName(resourceType, opts);
+
+    if (resourceType === 'content') {
+      event.respondWith(
+        fetch(request)
+          .then(response => addToCache(cacheKey, request, response))
+          .catch(() => fetchFromCache(event))
+          .catch(() => offlineResponse(resourceType, opts))
+      );
+    } else {
+      event.respondWith(
+        fetchFromCache(event)
+          .catch(() => fetch(request))
+            .then(response => addToCache(cacheKey, request, response))
+          .catch(() => offlineResponse(resourceType, opts))
+      );
+    }
+  }
+  if (shouldHandleFetch(event, config)) {
+    onFetch(event, config);
+  }
+
+});