diff --git a/build/css/main.css b/build/css/main.css index e48759d1..cf217d32 100644 --- a/build/css/main.css +++ b/build/css/main.css @@ -1254,7 +1254,7 @@ mark, .text-right, .section-header { text-align: right; } -.text-center, .alert, .alert-ribbon, .section-footer { +.text-center, .alert, .alert-ribbon, .update-notifier-modal-body__content, .section-footer { text-align: center; } .text-justify { @@ -5909,9 +5909,6 @@ html { position: initial; margin-right: 2px; } -.checkbox input[type="checkbox"]:not(:checked) + * { - color: #ddd; } - .modal-backdrop.in { opacity: 0; } @@ -6116,6 +6113,184 @@ button.btn:focus, button.progress-button:focus { background-color: #d9534f; border-color: #d9534f; } +/* + * Copyright 2016 Resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.modal-content { + display: flex; + flex-direction: column; } + +.modal-header { + display: flex; + align-items: baseline; + text-transform: uppercase; + font-size: 11px; } + +.modal-title { + font-size: inherit; + flex-grow: 1; } + +.modal-header { + color: #b3b3b3; + padding: 11px 20px; + flex-grow: 0; } + +.modal-body { + flex-grow: 1; + color: #666; + padding: 0 20px; + max-height: 250px; + overflow: auto; } + +.modal-content { + height: 320px; } + +.modal-body .list-group-item { + display: flex; + align-items: center; + border-left: none; + border-right: none; + border-radius: 0; + border-color: #eee; + padding: 12px 0; } + .modal-body .list-group-item > .tick { + font-size: 11px; } + +.modal-body .list-group-item-heading { + font-size: 13px; } + +.modal-body .list-group-item-text { + line-height: 1; + font-size: 11px; + color: #aaa; } + +.modal-body .list-group-item :first-child { + flex-grow: 1; } + +.modal-body .list-group-item:first-child { + border-top: none; } + +.modal-open { + padding-right: 0 !important; } + +.modal-fat-and-short { + width: 400px; + margin-top: -10px; } + .modal-fat-and-short .modal-content { + height: 245px; } + +.modal-footer { + flex-grow: 0; + border: 0; } + +.modal .btn-primary[disabled], .modal [disabled].progress-button--primary { + background-color: #d5d5d5; } + +.modal { + display: flex !important; + justify-content: center; + align-items: center; } + +.modal-dialog { + margin: 0; + position: initial; } + +/* + * Copyright 2016 Resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.alert-ribbon { + width: 60%; + position: fixed; + left: 0; + right: 0; + margin: 0 auto; + border-top-left-radius: 0; + border-top-right-radius: 0; + top: -100%; + transition: top 0.5s; } + .alert-ribbon > .glyphicon:first-child, .alert-ribbon > .tick:first-child { + margin-right: 5px; } + .alert-ribbon > .glyphicon:last-child, .alert-ribbon > .tick:last-child { + margin-left: 5px; } + .alert-ribbon .btn-link { + padding: 0; + font-size: inherit; + vertical-align: baseline; + border-radius: 0; + border-bottom: 1px solid; } + .alert-ribbon.alert-warning .btn-link { + border-color: #f7dbc3; + color: #fff; } + .alert-ribbon.alert-warning .btn-link:hover { + color: #e6e6e6; + border-color: #f2c096; } + +.alert-ribbon--open { + top: 0; } + +/* + * Copyright 2016 Resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.update-notifier-modal-body { + padding: 30px 35px; } + +.update-notifier-modal-body__content { + padding-bottom: 15px; + margin-bottom: 25px; + border-bottom: 1px solid #eeeeee; } + +.update-notifier-modal-body__title { + margin-bottom: 15px; } + +.update-notifier-modal-body__menu { + display: flex; + justify-content: center; } + .update-notifier-modal-body__menu > .btn, .update-notifier-modal-body__menu > .progress-button { + flex-grow: 1; + width: 0; } + .update-notifier-modal-body__menu > .btn + .btn, .update-notifier-modal-body__menu > .progress-button + .btn, .update-notifier-modal-body__menu > .btn + .progress-button, .update-notifier-modal-body__menu > .progress-button + .progress-button { + margin-left: 10px; } + +.update-notifier-modal-body .checkbox { + color: #959595; + font-size: 11px; } + /* * Copyright 2016 Resin.io * @@ -6231,114 +6406,8 @@ button.btn:focus, button.progress-button:focus { * See the License for the specific language governing permissions and * limitations under the License. */ -.modal-content { - display: flex; - flex-direction: column; } - -.modal-header { - display: flex; - align-items: baseline; - text-transform: uppercase; - font-size: 11px; } - -.modal-title { - font-size: inherit; - flex-grow: 1; } - -.modal-header { - color: #b3b3b3; - padding: 11px 20px; - flex-grow: 0; } - -.modal-body { - flex-grow: 1; - color: #666; - padding: 0 20px; - max-height: 250px; - overflow: auto; } - -.modal-content { - height: 320px; } - -.modal-body .list-group-item { - display: flex; - align-items: center; - border-left: none; - border-right: none; - border-radius: 0; - border-color: #eee; - padding: 12px 0; } - .modal-body .list-group-item > .tick { - font-size: 11px; } - -.modal-body .list-group-item-heading { - font-size: 13px; } - -.modal-body .list-group-item-text { - line-height: 1; - font-size: 11px; - color: #aaa; } - -.modal-body .list-group-item :first-child { - flex-grow: 1; } - -.modal-body .list-group-item:first-child { - border-top: none; } - -.modal-open { - padding-right: 0 !important; } - -.modal-footer { - flex-grow: 0; - border: 0; } - -.modal .btn-primary[disabled], .modal [disabled].progress-button--primary { - background-color: #d5d5d5; } - -/* - * Copyright 2016 Resin.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -.alert-ribbon { - width: 60%; - position: fixed; - left: 0; - right: 0; - margin: 0 auto; - border-top-left-radius: 0; - border-top-right-radius: 0; - top: -100%; - transition: top 0.5s; } - .alert-ribbon > .glyphicon:first-child, .alert-ribbon > .tick:first-child { - margin-right: 5px; } - .alert-ribbon > .glyphicon:last-child, .alert-ribbon > .tick:last-child { - margin-left: 5px; } - .alert-ribbon .btn-link { - padding: 0; - font-size: inherit; - vertical-align: baseline; - border-radius: 0; - border-bottom: 1px solid; } - .alert-ribbon.alert-warning .btn-link { - border-color: #f7dbc3; - color: #fff; } - .alert-ribbon.alert-warning .btn-link:hover { - color: #e6e6e6; - border-color: #f2c096; } - -.alert-ribbon--open { - top: 0; } +.page-settings .checkbox input[type="checkbox"]:not(:checked) + * { + color: #ddd; } .icon-caption { margin-top: 10px; } diff --git a/lib/gui/app.js b/lib/gui/app.js index 3e03eda4..a63babe7 100644 --- a/lib/gui/app.js +++ b/lib/gui/app.js @@ -40,6 +40,7 @@ const app = angular.module('Etcher', [ require('./components/progress-button/progress-button'), require('./components/drive-selector/drive-selector'), require('./components/svg-icon/svg-icon'), + require('./components/update-notifier/update-notifier'), // Pages require('./pages/finish/finish'), @@ -85,6 +86,7 @@ app.controller('AppController', function( ImageWriterService, AnalyticsService, DriveSelectorService, + UpdateNotifierService, OSWindowProgressService, OSNotificationService, OSDialogService @@ -96,6 +98,24 @@ app.controller('AppController', function( this.settings = SettingsModel.data; this.success = true; + if (UpdateNotifierService.shouldCheckForUpdates()) { + AnalyticsService.logEvent('Checking for updates'); + + UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) { + + // In case the internet connection is not good and checking the + // latest published version takes too long, only show notify + // the user about the new version if he didn't start the flash + // process (e.g: selected an image), otherwise such interruption + // might be annoying. + if (!isLatestVersion && !SelectionStateModel.hasImage()) { + + AnalyticsService.logEvent('Notifying update'); + UpdateNotifierService.notify(); + } + }); + } + // This catches the case where the user enters // the settings screen when a flash finished // and goes back to the main screen with the back button. diff --git a/lib/gui/components/update-notifier/controllers/update-notifier.js b/lib/gui/components/update-notifier/controllers/update-notifier.js new file mode 100644 index 00000000..311076e0 --- /dev/null +++ b/lib/gui/components/update-notifier/controllers/update-notifier.js @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +module.exports = function($uibModalInstance, SettingsModel) { + + // We update this value in this controller since its the only place + // where we can be sure the modal was really presented to the user. + // If the controller is instantiated, means the modal was shown. + // Compare that to `UpdateNotifierService.notify()`, which could + // have been called, but the modal could have failed to be shown. + SettingsModel.data.lastUpdateNotify = Date.now(); + + /** + * @summary Settings data + * @type Object + * @public + */ + this.settings = SettingsModel.data; + + /** + * @summary Close the modal + * @function + * @public + * + * @example + * UpdateNotifierController.closeModal(); + */ + this.closeModal = function() { + return $uibModalInstance.dismiss(); + }; + +}; diff --git a/lib/gui/components/update-notifier/services/update-notifier-s3.js b/lib/gui/components/update-notifier/services/update-notifier-s3.js new file mode 100644 index 00000000..b77329db --- /dev/null +++ b/lib/gui/components/update-notifier/services/update-notifier-s3.js @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const _ = require('lodash'); +const semver = require('semver'); +const xml = require('xml2js'); + +module.exports = function($q, $http, UPDATE_NOTIFIER_URL) { + + /** + * @summary Get the latest published Etcher version + * @function + * @public + * + * @description + * This function performs its job by querying the publicily accessible + * S3 bucket where we store the builds and uses the `node-semver` module + * to determine which is the latest one. + * + * @fulfil {String} - latest version + * @returns {Promise} + * + * @example + * UpdateNotifierS3Service.getLatestVersion().then(function(latestVersion) { + * console.log('The latest version is: ' + latestVersion); + * }); + */ + this.getLatestVersion = function() { + return $http.get(UPDATE_NOTIFIER_URL).then(function(response) { + return $q(function(resolve, reject) { + xml.parseString(response.data, function(error, result) { + if (error) { + return reject(error); + } + + const bucketEntries = result.ListBucketResult.Contents; + return resolve(_.reduce(bucketEntries, function(latest, entry) { + const version = _.chain(entry.Key) + .first() + .split('/') + .nth(1) + .value(); + + return semver.gt(version, latest) ? version : latest; + + // This is a good accumulator default value since + // every version is semantically greater than this. + }, '0.0.0')); + + }); + }); + }); + }; + +}; diff --git a/lib/gui/components/update-notifier/services/update-notifier.js b/lib/gui/components/update-notifier/services/update-notifier.js new file mode 100644 index 00000000..0ee83186 --- /dev/null +++ b/lib/gui/components/update-notifier/services/update-notifier.js @@ -0,0 +1,90 @@ +/* + * Copyright 2016 Resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const semver = require('semver'); + +module.exports = function($uibModal, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindService, UpdateNotifierS3Service, SettingsModel) { + + /** + * @summary Check if the current version is the latest version + * @function + * @public + * + * @fulfil {Boolean} - is latest version + * @returns {Promise} + * + * @example + * UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) { + * if (!isLatestVersion) { + * console.log('There is an update available'); + * } + * }); + */ + this.isLatestVersion = function() { + return UpdateNotifierS3Service.getLatestVersion().then(function(version) { + return semver.gte(ManifestBindService.get('version'), version); + }); + }; + + /** + * @summary Determine if its time to check for updates + * @function + * @public + * + * @returns {Boolean} should check for updates + * + * @example + * if (UpdateNotifierService.shouldCheckForUpdates()) { + * console.log('We should check for updates!'); + * } + */ + this.shouldCheckForUpdates = function() { + const lastUpdateNotify = SettingsModel.data.lastUpdateNotify; + + if (!SettingsModel.data.sleepUpdateCheck || !lastUpdateNotify) { + return true; + } + + if (lastUpdateNotify - Date.now() > UPDATE_NOTIFIER_SLEEP_TIME) { + SettingsModel.data.sleepUpdateCheck = false; + return true; + } + + return false; + }; + + /** + * @summary Open the update notifier widget + * @function + * @public + * + * @returns {Promise} + * + * @example + * UpdateNotifierService.notify(); + */ + this.notify = function() { + return $uibModal.open({ + animation: true, + templateUrl: './components/update-notifier/templates/update-notifier-modal.tpl.html', + controller: 'UpdateNotifierController as modal', + size: 'fat-and-short' + }).result; + }; + +}; diff --git a/lib/gui/components/update-notifier/styles/_update-notifier.scss b/lib/gui/components/update-notifier/styles/_update-notifier.scss new file mode 100644 index 00000000..4d578431 --- /dev/null +++ b/lib/gui/components/update-notifier/styles/_update-notifier.scss @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.update-notifier-modal-body { + padding: 30px 35px; +} + +.update-notifier-modal-body__content { + @extend .text-center; + + padding-bottom: 15px; + margin-bottom: 25px; + border-bottom: 1px solid $gray-lighter; +} + +.update-notifier-modal-body__title { + margin-bottom: 15px; +} + +.update-notifier-modal-body__menu { + display: flex; + justify-content: center; + + > .btn { + flex-grow: 1; + + + // This causes flex children buttons to be + // equally resized independently of the + // button text length + width: 0; + + } + + > .btn + .btn { + margin-left: 10px; + } +} + +.update-notifier-modal-body .checkbox { + color: lighten($gray, 25%); + font-size: 11px; +} diff --git a/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html b/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html new file mode 100644 index 00000000..c89eb4f1 --- /dev/null +++ b/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html @@ -0,0 +1,20 @@ +
A new version of Etcher is available for download
+