mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-08 20:06:31 +00:00
Implement update notifier modal (#406)
Auto-update functionality is not ready for usage. As a workaround to prevent users staying with older versions, we now check for updates at startup, and if the user is not running the latest version, we present a modal informing the user of the availiblity of a new version, and provide a call to action to open the Etcher website in his web browser. Extra features: - The user can skip the update, and tell the program to delay the notification for 7 days. Misc changes: - Center modal with flexbox, to allow more flexibility on the modal height. interacting with the S3 server. - Implement `ManifestBindService`, which now serves as a backend for the `manifest-bind` directive to allow the directive's functionality to be re-used by other services. - Namespace checkbox styles that are specific to the settings page. Fixes: https://github.com/resin-io/etcher/issues/396 Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
parent
662c589ab9
commit
a4e2639c00
@ -1254,7 +1254,7 @@ mark,
|
|||||||
.text-right, .section-header {
|
.text-right, .section-header {
|
||||||
text-align: right; }
|
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-align: center; }
|
||||||
|
|
||||||
.text-justify {
|
.text-justify {
|
||||||
@ -5909,9 +5909,6 @@ html {
|
|||||||
position: initial;
|
position: initial;
|
||||||
margin-right: 2px; }
|
margin-right: 2px; }
|
||||||
|
|
||||||
.checkbox input[type="checkbox"]:not(:checked) + * {
|
|
||||||
color: #ddd; }
|
|
||||||
|
|
||||||
.modal-backdrop.in {
|
.modal-backdrop.in {
|
||||||
opacity: 0; }
|
opacity: 0; }
|
||||||
|
|
||||||
@ -6116,6 +6113,184 @@ button.btn:focus, button.progress-button:focus {
|
|||||||
background-color: #d9534f;
|
background-color: #d9534f;
|
||||||
border-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
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
.modal-content {
|
.page-settings .checkbox input[type="checkbox"]:not(:checked) + * {
|
||||||
display: flex;
|
color: #ddd; }
|
||||||
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; }
|
|
||||||
|
|
||||||
.icon-caption {
|
.icon-caption {
|
||||||
margin-top: 10px; }
|
margin-top: 10px; }
|
||||||
|
@ -40,6 +40,7 @@ const app = angular.module('Etcher', [
|
|||||||
require('./components/progress-button/progress-button'),
|
require('./components/progress-button/progress-button'),
|
||||||
require('./components/drive-selector/drive-selector'),
|
require('./components/drive-selector/drive-selector'),
|
||||||
require('./components/svg-icon/svg-icon'),
|
require('./components/svg-icon/svg-icon'),
|
||||||
|
require('./components/update-notifier/update-notifier'),
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
require('./pages/finish/finish'),
|
require('./pages/finish/finish'),
|
||||||
@ -85,6 +86,7 @@ app.controller('AppController', function(
|
|||||||
ImageWriterService,
|
ImageWriterService,
|
||||||
AnalyticsService,
|
AnalyticsService,
|
||||||
DriveSelectorService,
|
DriveSelectorService,
|
||||||
|
UpdateNotifierService,
|
||||||
OSWindowProgressService,
|
OSWindowProgressService,
|
||||||
OSNotificationService,
|
OSNotificationService,
|
||||||
OSDialogService
|
OSDialogService
|
||||||
@ -96,6 +98,24 @@ app.controller('AppController', function(
|
|||||||
this.settings = SettingsModel.data;
|
this.settings = SettingsModel.data;
|
||||||
this.success = true;
|
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
|
// This catches the case where the user enters
|
||||||
// the settings screen when a flash finished
|
// the settings screen when a flash finished
|
||||||
// and goes back to the main screen with the back button.
|
// and goes back to the main screen with the back button.
|
||||||
|
@ -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();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
@ -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'));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
@ -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;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<div class="modal-body update-notifier-modal-body">
|
||||||
|
<div class="update-notifier-modal-body__content">
|
||||||
|
<h4 class="update-notifier-modal-body__title">New Update Available!</h4>
|
||||||
|
<p>A new version of Etcher is available for download</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="update-notifier-modal-body__menu">
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
os-open-external="http://etcher.io">DOWNLOAD</button>
|
||||||
|
<button class="btn btn-default"
|
||||||
|
ng-click="modal.closeModal()">SKIP</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox text-right">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" ng-model="modal.settings.sleepUpdateCheck">
|
||||||
|
<span>Remind me again in 7 days</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
38
lib/gui/components/update-notifier/update-notifier.js
Normal file
38
lib/gui/components/update-notifier/update-notifier.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 Etcher.Components.UpdateNotifier
|
||||||
|
*/
|
||||||
|
|
||||||
|
const angular = require('angular');
|
||||||
|
const MODULE_NAME = 'Etcher.Components.UpdateNotifier';
|
||||||
|
const UpdateNotifier = angular.module(MODULE_NAME, [
|
||||||
|
require('angular-ui-bootstrap'),
|
||||||
|
require('../../models/settings'),
|
||||||
|
require('../../utils/manifest-bind/manifest-bind'),
|
||||||
|
require('../../os/open-external/open-external')
|
||||||
|
]);
|
||||||
|
|
||||||
|
UpdateNotifier.constant('UPDATE_NOTIFIER_URL', 'https://resin-production-downloads.s3.amazonaws.com');
|
||||||
|
UpdateNotifier.constant('UPDATE_NOTIFIER_SLEEP_TIME', 7 * 24 * 60 * 60 * 100);
|
||||||
|
UpdateNotifier.controller('UpdateNotifierController', require('./controllers/update-notifier'));
|
||||||
|
UpdateNotifier.service('UpdateNotifierService', require('./services/update-notifier'));
|
||||||
|
UpdateNotifier.service('UpdateNotifierS3Service', require('./services/update-notifier-s3'));
|
||||||
|
|
||||||
|
module.exports = MODULE_NAME;
|
@ -37,7 +37,8 @@ SettingsModel.service('SettingsModel', function($localStorage) {
|
|||||||
this.data = $localStorage.$default({
|
this.data = $localStorage.$default({
|
||||||
errorReporting: true,
|
errorReporting: true,
|
||||||
unmountOnSuccess: true,
|
unmountOnSuccess: true,
|
||||||
validateWriteOnSuccess: true
|
validateWriteOnSuccess: true,
|
||||||
|
sleepUpdateCheck: false
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
20
lib/gui/pages/settings/styles/_settings.scss
Normal file
20
lib/gui/pages/settings/styles/_settings.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.page-settings .checkbox input[type="checkbox"]:not(:checked) + * {
|
||||||
|
color: $gray-light;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
<div class="text-left">
|
<div class="page-settings text-left">
|
||||||
<h1 class="space-bottom-large">Settings</h1>
|
<h1 class="space-bottom-large">Settings</h1>
|
||||||
|
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
@ -93,6 +93,18 @@
|
|||||||
padding-right: 0 !important;
|
padding-right: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-fat-and-short {
|
||||||
|
width: 400px;
|
||||||
|
|
||||||
|
// Move it a bit to the top for
|
||||||
|
// aesthetic reasons
|
||||||
|
margin-top: -10px;
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
height: 245px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
@ -101,3 +113,18 @@
|
|||||||
.modal .btn-primary[disabled] {
|
.modal .btn-primary[disabled] {
|
||||||
background-color: darken($gray-lighter, 10%);
|
background-color: darken($gray-lighter, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Center the modal using Flexbox so we can
|
||||||
|
// freely use any height.
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
margin: 0;
|
||||||
|
position: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -41,10 +41,12 @@ $alert-padding: 13px;
|
|||||||
@import "./components/caption";
|
@import "./components/caption";
|
||||||
@import "./components/button";
|
@import "./components/button";
|
||||||
@import "./components/tick";
|
@import "./components/tick";
|
||||||
@import "../components/progress-button/styles/progress-button";
|
|
||||||
@import "../components/svg-icon/styles/svg-icon";
|
|
||||||
@import "./components/modal";
|
@import "./components/modal";
|
||||||
@import "./components/alert-ribbon";
|
@import "./components/alert-ribbon";
|
||||||
|
@import "../components/update-notifier/styles/update-notifier";
|
||||||
|
@import "../components/progress-button/styles/progress-button";
|
||||||
|
@import "../components/svg-icon/styles/svg-icon";
|
||||||
|
@import "../pages/settings/styles/settings";
|
||||||
|
|
||||||
.icon-caption {
|
.icon-caption {
|
||||||
@extend .caption;
|
@extend .caption;
|
||||||
|
@ -28,10 +28,6 @@ html {
|
|||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox input[type="checkbox"]:not(:checked) + * {
|
|
||||||
color: $gray-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable modal opacity
|
// Disable modal opacity
|
||||||
.modal-backdrop.in {
|
.modal-backdrop.in {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('lodash');
|
|
||||||
const packageJSON = require('../../../../../package.json');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary ManifestBind directive
|
* @summary ManifestBind directive
|
||||||
* @function
|
* @function
|
||||||
@ -28,17 +25,18 @@ const packageJSON = require('../../../../../package.json');
|
|||||||
* This directive provides an attribute to bind the current
|
* This directive provides an attribute to bind the current
|
||||||
* element value to a property in `package.json`.
|
* element value to a property in `package.json`.
|
||||||
*
|
*
|
||||||
|
* @param {Object} ManifestBindService - ManifestBindService
|
||||||
* @returns {Object} directive
|
* @returns {Object} directive
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* <span manifest-bind="version"></button>
|
* <span manifest-bind="version"></button>
|
||||||
*/
|
*/
|
||||||
module.exports = function() {
|
module.exports = function(ManifestBindService) {
|
||||||
return {
|
return {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
scope: false,
|
scope: false,
|
||||||
link: function(scope, element, attributes) {
|
link: function(scope, element, attributes) {
|
||||||
const value = _.get(packageJSON, attributes.manifestBind);
|
const value = ManifestBindService.get(attributes.manifestBind);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new Error('ManifestBind: Unknown property `' + attributes.manifestBind + '`');
|
throw new Error('ManifestBind: Unknown property `' + attributes.manifestBind + '`');
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
const angular = require('angular');
|
const angular = require('angular');
|
||||||
const MODULE_NAME = 'Etcher.Utils.ManifestBind';
|
const MODULE_NAME = 'Etcher.Utils.ManifestBind';
|
||||||
const ManifestBind = angular.module(MODULE_NAME, []);
|
const ManifestBind = angular.module(MODULE_NAME, []);
|
||||||
|
ManifestBind.service('ManifestBindService', require('./services/manifest-bind'));
|
||||||
ManifestBind.directive('manifestBind', require('./directives/manifest-bind'));
|
ManifestBind.directive('manifestBind', require('./directives/manifest-bind'));
|
||||||
|
|
||||||
module.exports = MODULE_NAME;
|
module.exports = MODULE_NAME;
|
||||||
|
39
lib/gui/utils/manifest-bind/services/manifest-bind.js
Normal file
39
lib/gui/utils/manifest-bind/services/manifest-bind.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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 packageJSON = require('../../../../../package.json');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get a package.json property
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @param {String} attribute - attribute
|
||||||
|
* @returns {*} property value
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const version = ManifestBindService.get('version');
|
||||||
|
*/
|
||||||
|
this.get = function(attribute) {
|
||||||
|
return _.get(packageJSON, attribute);
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
@ -65,10 +65,12 @@
|
|||||||
"resin-cli-errors": "^1.2.0",
|
"resin-cli-errors": "^1.2.0",
|
||||||
"resin-cli-form": "^1.4.1",
|
"resin-cli-form": "^1.4.1",
|
||||||
"resin-cli-visuals": "^1.2.8",
|
"resin-cli-visuals": "^1.2.8",
|
||||||
|
"semver": "^5.1.0",
|
||||||
"sudo-prompt": "^3.1.0",
|
"sudo-prompt": "^3.1.0",
|
||||||
"trackjs": "^2.1.16",
|
"trackjs": "^2.1.16",
|
||||||
"umount": "^1.1.3",
|
"umount": "^1.1.3",
|
||||||
"username": "^2.1.0",
|
"username": "^2.1.0",
|
||||||
|
"xml2js": "^0.4.16",
|
||||||
"yargs": "^4.6.0"
|
"yargs": "^4.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
325
tests/gui/components/update-notifier.spec.js
Normal file
325
tests/gui/components/update-notifier.spec.js
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const m = require('mochainon');
|
||||||
|
const angular = require('angular');
|
||||||
|
require('angular-mocks');
|
||||||
|
|
||||||
|
describe('Browser: UpdateNotifier', function() {
|
||||||
|
|
||||||
|
beforeEach(angular.mock.module(
|
||||||
|
require('../../../lib/gui/components/update-notifier/update-notifier')
|
||||||
|
));
|
||||||
|
|
||||||
|
describe('UpdateNotifierService', function() {
|
||||||
|
|
||||||
|
describe('.shouldCheckForUpdates()', function() {
|
||||||
|
|
||||||
|
let UpdateNotifierService;
|
||||||
|
let SettingsModel;
|
||||||
|
let UPDATE_NOTIFIER_SLEEP_TIME;
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(function(_UpdateNotifierService_, _SettingsModel_, _UPDATE_NOTIFIER_SLEEP_TIME_) {
|
||||||
|
UpdateNotifierService = _UpdateNotifierService_;
|
||||||
|
SettingsModel = _SettingsModel_;
|
||||||
|
UPDATE_NOTIFIER_SLEEP_TIME = _UPDATE_NOTIFIER_SLEEP_TIME_;
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('given the `sleepUpdateCheck` is disabled', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
SettingsModel.data.sleepUpdateCheck = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', function() {
|
||||||
|
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||||
|
m.chai.expect(result).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given the `sleepUpdateCheck` is enabled', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
SettingsModel.data.sleepUpdateCheck = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given the `lastUpdateNotify` was never updated', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
SettingsModel.data.lastUpdateNotify = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', function() {
|
||||||
|
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||||
|
m.chai.expect(result).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given the `lastUpdateNotify` was very recently updated', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
SettingsModel.data.lastUpdateNotify = Date.now() + 1000;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', function() {
|
||||||
|
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||||
|
m.chai.expect(result).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given the `lastUpdateNotify` was updated long ago', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
SettingsModel.data.lastUpdateNotify = Date.now() + UPDATE_NOTIFIER_SLEEP_TIME + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', function() {
|
||||||
|
const result = UpdateNotifierService.shouldCheckForUpdates();
|
||||||
|
m.chai.expect(result).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unset the `sleepUpdateCheck` setting', function() {
|
||||||
|
m.chai.expect(SettingsModel.data.sleepUpdateCheck).to.be.true;
|
||||||
|
UpdateNotifierService.shouldCheckForUpdates();
|
||||||
|
m.chai.expect(SettingsModel.data.sleepUpdateCheck).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.isLatestVersion()', function() {
|
||||||
|
|
||||||
|
describe('given the latest version is equal to the current version', function() {
|
||||||
|
|
||||||
|
let $q;
|
||||||
|
let $rootScope;
|
||||||
|
let UpdateNotifierService;
|
||||||
|
let ManifestBindService;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
angular.mock.module(function($provide) {
|
||||||
|
$provide.value('UpdateNotifierS3Service', {
|
||||||
|
getLatestVersion: function() {
|
||||||
|
return $q.resolve(ManifestBindService.get('version'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_, _ManifestBindService_) {
|
||||||
|
$q = _$q_;
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
UpdateNotifierService = _UpdateNotifierService_;
|
||||||
|
ManifestBindService = _ManifestBindService_;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should resolve true', function() {
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||||
|
result = isLatestVersion;
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$apply();
|
||||||
|
m.chai.expect(result).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given the latest version is greater than the current version', function() {
|
||||||
|
|
||||||
|
let $q;
|
||||||
|
let $rootScope;
|
||||||
|
let UpdateNotifierService;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
angular.mock.module(function($provide) {
|
||||||
|
$provide.value('UpdateNotifierS3Service', {
|
||||||
|
getLatestVersion: function() {
|
||||||
|
return $q.resolve('99999.9.9');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) {
|
||||||
|
$q = _$q_;
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
UpdateNotifierService = _UpdateNotifierService_;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should resolve false', function() {
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||||
|
result = isLatestVersion;
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$apply();
|
||||||
|
m.chai.expect(result).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('given the latest version is less than the current version', function() {
|
||||||
|
|
||||||
|
let $q;
|
||||||
|
let $rootScope;
|
||||||
|
let UpdateNotifierService;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
angular.mock.module(function($provide) {
|
||||||
|
$provide.value('UpdateNotifierS3Service', {
|
||||||
|
getLatestVersion: function() {
|
||||||
|
return $q.resolve('0.0.0');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) {
|
||||||
|
$q = _$q_;
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
UpdateNotifierService = _UpdateNotifierService_;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should resolve true', function() {
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
UpdateNotifierService.isLatestVersion().then(function(isLatestVersion) {
|
||||||
|
result = isLatestVersion;
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$apply();
|
||||||
|
m.chai.expect(result).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UpdateNotifierS3Service', function() {
|
||||||
|
|
||||||
|
let UpdateNotifierS3Service;
|
||||||
|
let $rootScope;
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(function(_$rootScope_, _UpdateNotifierS3Service_) {
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
UpdateNotifierS3Service = _UpdateNotifierS3Service_;
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('given a mocked S3 XML response', function() {
|
||||||
|
|
||||||
|
let $httpBackend;
|
||||||
|
let UPDATE_NOTIFIER_URL;
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(function($injector) {
|
||||||
|
$httpBackend = $injector.get('$httpBackend');
|
||||||
|
UPDATE_NOTIFIER_URL = $injector.get('UPDATE_NOTIFIER_URL');
|
||||||
|
|
||||||
|
$httpBackend.whenGET(UPDATE_NOTIFIER_URL).respond(`
|
||||||
|
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||||
|
<Name>resin-production-downloads</Name>
|
||||||
|
<Prefix/>
|
||||||
|
<Marker/>
|
||||||
|
<MaxKeys>1000</MaxKeys>
|
||||||
|
<IsTruncated>false</IsTruncated>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.0/Etcher-darwin-x64.dmg</Key>
|
||||||
|
<LastModified>2016-03-10T17:34:21.000Z</LastModified>
|
||||||
|
<ETag>"5a715255aa25686688bf1e23bc1d3fc6"</ETag>
|
||||||
|
<Size>46109720</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.1/Etcher-darwin-x64.dmg</Key>
|
||||||
|
<LastModified>2016-04-08T20:12:03.000Z</LastModified>
|
||||||
|
<ETag>"cc1d6d9d53385e3edd099416fcd894c1"</ETag>
|
||||||
|
<Size>47071474</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.2/Etcher-darwin-x64.dmg</Key>
|
||||||
|
<LastModified>2016-04-08T19:03:18.000Z</LastModified>
|
||||||
|
<ETag>"5f1849f7781197ce2ee6129c16bcd498"</ETag>
|
||||||
|
<Size>48650090</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.3/Etcher-darwin-x64.dmg</Key>
|
||||||
|
<LastModified>2016-04-18T01:32:09.000Z</LastModified>
|
||||||
|
<ETag>"c173895886f44d115c66e7206ce3dff8"</ETag>
|
||||||
|
<Size>50585335</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.3/Etcher-darwin-x64.zip</Key>
|
||||||
|
<LastModified>2016-04-18T01:42:37.000Z</LastModified>
|
||||||
|
<ETag>"e9f6e957e65373b232530215d98df141"</ETag>
|
||||||
|
<Size>129327442</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.4/Etcher-darwin-x64.dmg</Key>
|
||||||
|
<LastModified>2016-04-22T17:29:49.000Z</LastModified>
|
||||||
|
<ETag>"bccb0024c58747a9b7516cbdfc5a7ecb"</ETag>
|
||||||
|
<Size>55240852</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.4/Etcher-darwin-x64.zip</Key>
|
||||||
|
<LastModified>2016-04-22T17:43:27.000Z</LastModified>
|
||||||
|
<ETag>"c93e26e68b3c4f2b7e8e88e6befc8e64"</ETag>
|
||||||
|
<Size>135443284</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.5/Etcher-darwin-x64.dmg</Key>
|
||||||
|
<LastModified>2016-05-04T08:27:11.000Z</LastModified>
|
||||||
|
<ETag>"fb596bfdb8bbaf09807b5fc4a940ce14"</ETag>
|
||||||
|
<Size>77757305</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>etcher/1.0.0-beta.5/Etcher-darwin-x64.zip</Key>
|
||||||
|
<LastModified>2016-05-04T08:39:56.000Z</LastModified>
|
||||||
|
<ETag>"3f11c1b6f06644f9ceb2aea4b1947fdf"</ETag>
|
||||||
|
<Size>157933876</Size>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
</ListBucketResult>
|
||||||
|
`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
$httpBackend.verifyNoOutstandingExpectation();
|
||||||
|
$httpBackend.verifyNoOutstandingRequest();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve the latest version', function() {
|
||||||
|
$httpBackend.expectGET(UPDATE_NOTIFIER_URL);
|
||||||
|
|
||||||
|
let latestVersion = null;
|
||||||
|
UpdateNotifierS3Service.getLatestVersion().then(function(result) {
|
||||||
|
latestVersion = result;
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$apply();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
m.chai.expect(latestVersion).to.equal('1.0.0-beta.5');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -35,6 +35,31 @@ describe('Browser: ManifestBind', function() {
|
|||||||
$rootScope = _$rootScope_;
|
$rootScope = _$rootScope_;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
describe('ManifestBindService', function() {
|
||||||
|
|
||||||
|
let ManifestBindService;
|
||||||
|
|
||||||
|
beforeEach(angular.mock.inject(function(_ManifestBindService_) {
|
||||||
|
ManifestBindService = _ManifestBindService_;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be able to fetch top level properties', function() {
|
||||||
|
const value = ManifestBindService.get('version');
|
||||||
|
m.chai.expect(value).to.equal(packageJSON.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to fetch nested properties', function() {
|
||||||
|
const value = ManifestBindService.get('repository.type');
|
||||||
|
m.chai.expect(value).to.equal(packageJSON.repository.type);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if the property does not exist', function() {
|
||||||
|
const value = ManifestBindService.get('foo.bar');
|
||||||
|
m.chai.expect(value).to.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('manifestBind', function() {
|
describe('manifestBind', function() {
|
||||||
|
|
||||||
it('should bind to top level properties', function() {
|
it('should bind to top level properties', function() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user