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/
--- /dev/null
+<?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>
<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>
--- /dev/null
+{
+ "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"
+}
}
$(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);
+ });
+ });
+}
--- /dev/null
+/* 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);
+ }
+
+});