Expand out offline app features for smoother cache clearing and version tracking (#1384)

* Add version number to bottom

* Get latest version number from GitHub

* Create versionManagement.js

Fetching the latest and local version number is now done through versionManagement.js

* Add popup to prompt for refresh when a new version is available

A new version is available whenever the sw.js file is changed.

* Add version number fixed in top right corner

* Fix Codeclimate issues

* Update versionManagement.js

* Update versionManagement.js

* Make update prompt appear at front of page

Changed z-index

* Delete unecessary code

* Create task to automatically update sw.js

Used grunt-text-replace

* Uninstall semver

* Add replace task to serve and production tasks

* Update demo.js

Make version statements more descriptive.

* Update versionManagement.js

Remove unused versionCompare function

* Change URL for getting latest version

Changed the URL for getting the latest NPM version to be based on the package.json file's attribute for "homepage".

* Update index.html

* Update demo.css

* Added explanatory comments

* Update versionManagement.js

* Update versionManagement.js

* Updates for readability

Changed single-line comments to multiline comments.

* Update versionManagement.js

* Update versionManagement.js

Co-authored-by: Harsh Khandeparkar <34770591+HarshKhandeparkar@users.noreply.github.com>
Co-authored-by: Jeffrey Warren <jeff@unterbahn.com>
This commit is contained in:
anthony-zhou
2020-01-07 13:56:34 -06:00
committed by Jeffrey Warren
parent ab793bcf4e
commit 00ed0f148d
9 changed files with 272 additions and 14 deletions

View File

@@ -2,6 +2,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-uglify-es'); grunt.loadNpmTasks('grunt-contrib-uglify-es');
grunt.loadNpmTasks('grunt-browser-sync'); grunt.loadNpmTasks('grunt-browser-sync');
grunt.loadNpmTasks('grunt-text-replace');
require('matchdep') require('matchdep')
.filterDev('grunt-*') .filterDev('grunt-*')
@@ -48,6 +49,17 @@ module.exports = function(grunt) {
} }
}, },
replace: {
version: {
src: ['examples/sw.js'],
overwrite: true,
replacements: [{
from: /image-sequencer-static-v.*/g,
to: "image-sequencer-static-v<%= pkg.version %>';"
}]
}
},
uglify: { uglify: {
core: { core: {
src: ['./dist/image-sequencer.js'], src: ['./dist/image-sequencer.js'],
@@ -78,10 +90,10 @@ module.exports = function(grunt) {
/* Default (development): Watch files and build on change. */ /* Default (development): Watch files and build on change. */
grunt.registerTask('default', ['watch']); grunt.registerTask('default', ['watch']);
grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'uglify:core', 'uglify:ui']); grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'replace:version', 'uglify:core', 'uglify:ui']);
grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'browserSync', 'watch']); grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'replace:version', 'browserSync', 'watch']);
grunt.registerTask('compile', ['browserify:core', 'browserify:ui']); grunt.registerTask('compile', ['browserify:core', 'browserify:ui']);
grunt.registerTask('production', ['browserify:prodcore', 'browserify:produi', 'uglify:prodcore', 'uglify:produi']); grunt.registerTask('production', ['browserify:prodcore', 'browserify:produi', 'replace:version', 'uglify:prodcore', 'uglify:produi']);
grunt.registerTask('tests', ['browserify:tests']); grunt.registerTask('tests', ['browserify:tests']);
}; };

View File

@@ -315,8 +315,55 @@ a.name-header{
color: #444; color: #444;
} }
#version-number-text {
text-align: center;
padding-top: 100px;
color: gray;
}
#version-number-top-right {
position: fixed;
right: 2%;
top: 5%;
color: lightgray;
}
/* Non float rightward alignment*/ /* Non float rightward alignment*/
.right { .right {
margin-left: auto; margin-left: auto;
display: block; display: block;
} }
#update-prompt-modal {
visibility: hidden;
min-width: 250px;
margin-left: -125px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 2px;
padding: 16px;
position: fixed;
z-index: 1000;
left: 10%;
top: 30px;
}
#update-prompt-modal.show {
visibility: visible;
-webkit-animation: fadein 0.5s;
animation: fadein 0.5s;
}
@-webkit-keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View File

@@ -3,7 +3,9 @@ var defaultHtmlSequencerUi = require('./lib/defaultHtmlSequencerUi.js'),
intermediateHtmlStepUi = require('./lib/intermediateHtmlStepUi.js'), intermediateHtmlStepUi = require('./lib/intermediateHtmlStepUi.js'),
DefaultHtmlStepUi = require('./lib/defaultHtmlStepUi.js'), DefaultHtmlStepUi = require('./lib/defaultHtmlStepUi.js'),
urlHash = require('./lib/urlHash.js'), urlHash = require('./lib/urlHash.js'),
insertPreview = require('./lib/insertPreview.js'); insertPreview = require('./lib/insertPreview.js'),
versionManagement = require('./lib/versionManagement.js');
window.onload = function () { window.onload = function () {
sequencer = ImageSequencer(); // Set the global sequencer variable sequencer = ImageSequencer(); // Set the global sequencer variable
@@ -28,6 +30,17 @@ window.onload = function () {
} }
}; };
versionManagement.getLatestVersionNumber(function(versionNumber) {
console.log("The latest NPM version number for Image Sequencer (from GitHub) is v" + versionNumber);
});
console.log("The local version number for Image Sequencer is v" + versionManagement.getLocalVersionNumber());
function displayVersionNumber() {
$('#version-number-text').text("Image Sequencer v" + versionManagement.getLocalVersionNumber());
$('#version-number-top-right').text("v" + versionManagement.getLocalVersionNumber());
}
displayVersionNumber();
function refreshOptions(options) { function refreshOptions(options) {
// Default options if parameter is empty. // Default options if parameter is empty.
if (options == undefined) options = { sortField: 'text' }; if (options == undefined) options = { sortField: 'text' };

View File

@@ -38,7 +38,7 @@
<!-- jspdf to enable save image as pdf --> <!-- jspdf to enable save image as pdf -->
<script src="../node_modules/jspdf/dist/jspdf.min.js" type="text/javascript" ></script> <script src="../node_modules/jspdf/dist/jspdf.min.js" type="text/javascript" ></script>
<script src="lib/scrollToTop.js"></script> <!-- <script src="lib/scrollToTop.js"></script> -->
<script src="../node_modules/selectize/dist/js/standalone/selectize.min.js"></script> <script src="../node_modules/selectize/dist/js/standalone/selectize.min.js"></script>
</head> </head>
@@ -56,6 +56,8 @@
<link href="./selectize.default.css" rel="stylesheet"> <link href="./selectize.default.css" rel="stylesheet">
<link rel="stylesheet" href="demo.css"> <link rel="stylesheet" href="demo.css">
<div id="update-prompt-modal">A new version of image sequencer is available. Click <a href="#" id="reload">here</a> to update.</div>
<div class="container-fluid"> <div class="container-fluid">
<header class="text-center" style="min-width: 450px"> <header class="text-center" style="min-width: 450px">
@@ -73,6 +75,7 @@
</a> </a>
by <a href="https://publiclab.org" title="Publiclab Website"><i class="fa fa-globe"></i> Publiclab</a> by <a href="https://publiclab.org" title="Publiclab Website"><i class="fa fa-globe"></i> Publiclab</a>
</p> </p>
<span id="version-number-top-right"></span>
</header> </header>
<div id="dropzone" class="dropzone"> <div id="dropzone" class="dropzone">
@@ -228,6 +231,9 @@
</p> </p>
</div> </div>
</div> </div>
<div class="row">
<p id="version-number-text">Unable to load version number</p>
</div>
</footer> </footer>
<button id="move-up"><i class="fa fa-arrow-circle-o-up"></i></button> <button id="move-up"><i class="fa fa-arrow-circle-o-up"></i></button>

View File

@@ -1,7 +1,41 @@
var setupCache = function() { var setupCache = function() {
let newWorker; // When sw.js is changed, this is the new service worker generated.
// Toggle a CSS class to display a popup prompting the user to fetch a new version.
function showUpdateModal() {
$('#update-prompt-modal').addClass('show');
}
/**
* When a new service worker has been loaded, the button in the update prompt
* modal should trigger the skipWaiting event to replace the current
* service worker with the new one.
*/
$('#reload').on('click', function() {
newWorker.postMessage({ action: 'skipWaiting' });
});
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
// Register the service worker.
navigator.serviceWorker.register('sw.js', { scope: '/examples/' }) navigator.serviceWorker.register('sw.js', { scope: '/examples/' })
.then(function(registration) { .then(function(registration) {
registration.addEventListener('updatefound', () => {
// When sw.js has been changed, get a reference to the new service worker.
newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
// Check if service worker state has changed.
switch(newWorker.state) {
case 'installed':
if(navigator.serviceWorker.controller) {
// New service worker available; prompt the user to update.
showUpdateModal();
}
// No updates available; do nothing.
break;
}
});
});
const installingWorker = registration.installing; const installingWorker = registration.installing;
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
console.log(installingWorker); console.log(installingWorker);
@@ -14,6 +48,17 @@ var setupCache = function() {
.catch(function(error) { .catch(function(error) {
console.log('Service worker registration failed, error:', error); console.log('Service worker registration failed, error:', error);
}); });
/**
* This is the event listener for when the service worker updates.
* When the service worker updates, reload the page.
*/
let refreshing;
navigator.serviceWorker.addEventListener('controllerchange', function() {
if(refreshing) return;
window.location.reload();
refreshing = true;
});
} }
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
@@ -34,6 +79,11 @@ var setupCache = function() {
} }
location.reload(); location.reload();
}); });
}; };
module.exports = setupCache; module.exports = setupCache;

View File

@@ -0,0 +1,43 @@
/**
* Functions for getting version information.
* Note: these functions are not used by the service worker to check for updates;
* the service worker updates whenever sw.js has changed.
* sw.js is changed when grunt replace:version is run. This task is run during
* grunt build, serve, and productions tasks.
*/
const package = require('../../package.json');
/**
* Get the current version number from package.json on the homepage.
* @param {function} callback The function that uses the version number.
*/
function getLatestVersionNumber(callback) {
// Get the homepage reference from the local package.json.
var homepage = package.homepage;
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
var response = JSON.parse(this.responseText);
var latestVersionNumber = response.version;
// Do something with the version number using a callback function.
if (callback)
callback(latestVersionNumber);
}
}
// Get the package.json file from online using a GET request.
request.open("GET", homepage + "/package.json", true);
request.send();
}
// Get the version number from the local package.json file.
function getLocalVersionNumber() {
return package.version;
}
module.exports = {
getLatestVersionNumber,
getLocalVersionNumber
}

View File

@@ -1,5 +1,4 @@
const staticCacheName = 'image-sequencer-static-v3'; const staticCacheName = 'image-sequencer-static-v3.5.1';
self.addEventListener('install', event => { self.addEventListener('install', event => {
console.log('Attempting to install service worker'); console.log('Attempting to install service worker');
}); });
@@ -33,3 +32,10 @@ self.addEventListener('fetch', function(event) {
}) })
); );
}); });
// When the update modal sends a 'skipWaiting' message, call the skipWaiting method.
self.addEventListener('message', function(event) {
if(event.data.action === 'skipWaiting') {
self.skipWaiting();
}
});

90
package-lock.json generated
View File

@@ -152,6 +152,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
} }
} }
}, },
@@ -1714,6 +1720,13 @@
"bin-version": "^3.0.0", "bin-version": "^3.0.0",
"semver": "^5.6.0", "semver": "^5.6.0",
"semver-truncate": "^1.1.2" "semver-truncate": "^1.1.2"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
} }
}, },
"bin-wrapper": { "bin-wrapper": {
@@ -3348,6 +3361,13 @@
"semver": "^5.5.0", "semver": "^5.5.0",
"shebang-command": "^1.2.0", "shebang-command": "^1.2.0",
"which": "^1.2.9" "which": "^1.2.9"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
} }
}, },
"crypto-browserify": { "crypto-browserify": {
@@ -4149,6 +4169,12 @@
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true "dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
} }
} }
}, },
@@ -6995,6 +7021,12 @@
"which": "~1.3.0" "which": "~1.3.0"
} }
}, },
"grunt-text-replace": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/grunt-text-replace/-/grunt-text-replace-0.4.0.tgz",
"integrity": "sha1-252c5Z4v5J2id+nbwZXD4Rz7FsI=",
"dev": true
},
"gzip-size": { "gzip-size": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz",
@@ -7500,6 +7532,14 @@
"resolve": "^1.10.0", "resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5", "semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1" "validate-npm-package-license": "^3.0.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
} }
}, },
"parse-json": { "parse-json": {
@@ -8404,6 +8444,14 @@
"requires": { "requires": {
"pify": "^4.0.1", "pify": "^4.0.1",
"semver": "^5.6.0" "semver": "^5.6.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
} }
}, },
"pify": { "pify": {
@@ -8467,6 +8515,14 @@
"requires": { "requires": {
"pify": "^4.0.1", "pify": "^4.0.1",
"semver": "^5.6.0" "semver": "^5.6.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
} }
}, },
"ms": { "ms": {
@@ -11070,6 +11126,13 @@
"integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==", "integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==",
"requires": { "requires": {
"semver": "^5.4.1" "semver": "^5.4.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
} }
}, },
"node-bitmap": { "node-bitmap": {
@@ -11125,6 +11188,14 @@
"semver": "^5.5.0", "semver": "^5.5.0",
"shellwords": "^0.1.1", "shellwords": "^0.1.1",
"which": "^1.3.0" "which": "^1.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
} }
}, },
"node-png": { "node-png": {
@@ -11154,6 +11225,13 @@
"is-builtin-module": "^1.0.0", "is-builtin-module": "^1.0.0",
"semver": "2 || 3 || 4 || 5", "semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1" "validate-npm-package-license": "^3.0.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
} }
}, },
"normalize-path": { "normalize-path": {
@@ -13220,11 +13298,6 @@
"sifter": "^0.5.1" "sifter": "^0.5.1"
} }
}, },
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
},
"semver-compare": { "semver-compare": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
@@ -13242,6 +13315,13 @@
"integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=", "integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=",
"requires": { "requires": {
"semver": "^5.3.0" "semver": "^5.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}
} }
}, },
"send": { "send": {

View File

@@ -87,6 +87,7 @@
"grunt-contrib-concat": "^1.0.1", "grunt-contrib-concat": "^1.0.1",
"grunt-contrib-uglify-es": "^3.3.0", "grunt-contrib-uglify-es": "^3.3.0",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"grunt-text-replace": "^0.4.0",
"husky": "^3.0.5", "husky": "^3.0.5",
"image-filter-core": "~2.0.2", "image-filter-core": "~2.0.2",
"image-filter-threshold": "~2.0.1", "image-filter-threshold": "~2.0.1",