diff --git a/Gruntfile.js b/Gruntfile.js index 2b568e81..771422f7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,6 +2,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-contrib-uglify-es'); grunt.loadNpmTasks('grunt-browser-sync'); + grunt.loadNpmTasks('grunt-text-replace'); require('matchdep') .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: { core: { src: ['./dist/image-sequencer.js'], @@ -78,10 +90,10 @@ module.exports = function(grunt) { /* Default (development): Watch files and build on change. */ grunt.registerTask('default', ['watch']); - grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'uglify:core', 'uglify:ui']); - grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'browserSync', 'watch']); + grunt.registerTask('build', ['browserify:core', 'browserify:ui', 'replace:version', 'uglify:core', 'uglify:ui']); + grunt.registerTask('serve', ['browserify:core', 'browserify:ui', 'replace:version', 'browserSync', 'watch']); 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']); }; diff --git a/examples/demo.css b/examples/demo.css index 2b9c9f90..71de368a 100644 --- a/examples/demo.css +++ b/examples/demo.css @@ -315,8 +315,55 @@ a.name-header{ 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*/ .right { margin-left: auto; 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; + } +} diff --git a/examples/demo.js b/examples/demo.js index e6551e92..cce2a12b 100644 --- a/examples/demo.js +++ b/examples/demo.js @@ -3,7 +3,9 @@ var defaultHtmlSequencerUi = require('./lib/defaultHtmlSequencerUi.js'), intermediateHtmlStepUi = require('./lib/intermediateHtmlStepUi.js'), DefaultHtmlStepUi = require('./lib/defaultHtmlStepUi.js'), urlHash = require('./lib/urlHash.js'), - insertPreview = require('./lib/insertPreview.js'); + insertPreview = require('./lib/insertPreview.js'), + versionManagement = require('./lib/versionManagement.js'); + window.onload = function () { 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) { // Default options if parameter is empty. if (options == undefined) options = { sortField: 'text' }; @@ -310,7 +323,7 @@ window.onload = function () { step.options.step.imgElement.src = reader.result; else step.imgElement.src = reader.result; - + insertPreview.updatePreviews(reader.result, document.querySelector('#addStep')); DefaultHtmlStepUi(sequencer).updateDimensions(step); }, diff --git a/examples/index.html b/examples/index.html index 606ad0cb..b92b9022 100644 --- a/examples/index.html +++ b/examples/index.html @@ -38,7 +38,7 @@ - + @@ -56,6 +56,8 @@ +
A new version of image sequencer is available. Click here to update.
+
@@ -73,6 +75,7 @@ by Publiclab

+
@@ -228,6 +231,9 @@

+
+

Unable to load version number

+
@@ -241,4 +247,4 @@ - \ No newline at end of file + diff --git a/examples/lib/cache.js b/examples/lib/cache.js index cec27dec..a402e303 100644 --- a/examples/lib/cache.js +++ b/examples/lib/cache.js @@ -1,7 +1,41 @@ 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) { + // Register the service worker. navigator.serviceWorker.register('sw.js', { scope: '/examples/' }) .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; installingWorker.onstatechange = () => { console.log(installingWorker); @@ -14,6 +48,17 @@ var setupCache = function() { .catch(function(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) { @@ -34,6 +79,11 @@ var setupCache = function() { } location.reload(); }); + + + + + }; module.exports = setupCache; diff --git a/examples/lib/versionManagement.js b/examples/lib/versionManagement.js new file mode 100644 index 00000000..5c922fa0 --- /dev/null +++ b/examples/lib/versionManagement.js @@ -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 +} diff --git a/examples/sw.js b/examples/sw.js index 92b8606f..c52fce9e 100644 --- a/examples/sw.js +++ b/examples/sw.js @@ -1,5 +1,4 @@ -const staticCacheName = 'image-sequencer-static-v3'; - +const staticCacheName = 'image-sequencer-static-v3.5.1'; self.addEventListener('install', event => { 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(); + } +}); diff --git a/package-lock.json b/package-lock.json index ea21f3a4..125af3b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,6 +152,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "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", "semver": "^5.6.0", "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": { @@ -3348,6 +3361,13 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "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": { @@ -4149,6 +4169,12 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "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" } }, + "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", @@ -7500,6 +7532,14 @@ "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "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": { @@ -8404,6 +8444,14 @@ "requires": { "pify": "^4.0.1", "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": { @@ -8467,6 +8515,14 @@ "requires": { "pify": "^4.0.1", "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": { @@ -11070,6 +11126,13 @@ "integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==", "requires": { "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": { @@ -11125,6 +11188,14 @@ "semver": "^5.5.0", "shellwords": "^0.1.1", "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": { @@ -11154,6 +11225,13 @@ "is-builtin-module": "^1.0.0", "semver": "2 || 3 || 4 || 5", "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": { @@ -13220,11 +13298,6 @@ "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", @@ -13242,6 +13315,13 @@ "integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=", "requires": { "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": { diff --git a/package.json b/package.json index 15cc1d05..c8a4b81b 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "grunt-contrib-concat": "^1.0.1", "grunt-contrib-uglify-es": "^3.3.0", "grunt-contrib-watch": "^1.1.0", + "grunt-text-replace": "^0.4.0", "husky": "^3.0.5", "image-filter-core": "~2.0.2", "image-filter-threshold": "~2.0.1",