diff --git a/dictionary b/dictionary index 1a873938..551c8596 100644 --- a/dictionary +++ b/dictionary @@ -1,3 +1,4 @@ boolen->boolean aknowledge->acknowledge seleted->selected +reming->remind diff --git a/lib/gui/app.js b/lib/gui/app.js index 57de7653..889b6135 100644 --- a/lib/gui/app.js +++ b/lib/gui/app.js @@ -39,6 +39,7 @@ const flashState = require('./models/flash-state'); const settings = require('./models/settings'); const windowProgress = require('./os/window-progress'); const analytics = require('./modules/analytics'); +const updateNotifier = require('./components/update-notifier'); const Store = require('./models/store'); @@ -57,7 +58,6 @@ const app = angular.module('Etcher', [ // Components require('./components/svg-icon/svg-icon'), - require('./components/update-notifier/update-notifier'), require('./components/warning-modal/warning-modal'), require('./components/safe-webview/safe-webview'), @@ -89,19 +89,21 @@ app.run(() => { ].join('\n')); }); -app.run((ErrorService, UpdateNotifierService, SelectionStateModel) => { +app.run((ErrorService, SelectionStateModel) => { analytics.logEvent('Application start'); const currentVersion = packageJSON.version; - const currentReleaseType = release.getReleaseType(currentVersion); - const shouldCheckForUpdates = UpdateNotifierService.shouldCheckForUpdates({ - ignoreSleepUpdateCheck: currentReleaseType !== release.RELEASE_TYPE.PRODUCTION + const shouldCheckForUpdates = updateNotifier.shouldCheckForUpdates({ + currentVersion: packageJSON.version, + lastSleptUpdateNotifier: settings.get('lastSleptUpdateNotifier'), + lastSleptUpdateNotifierVersion: settings.get('lastSleptUpdateNotifierVersion') }); + const currentReleaseType = release.getReleaseType(currentVersion); + if (_.some([ !shouldCheckForUpdates, - process.env.ETCHER_DISABLE_UPDATES, - currentReleaseType === release.RELEASE_TYPE.UNKNOWN + process.env.ETCHER_DISABLE_UPDATES ])) { analytics.logEvent('Not checking for updates', { shouldCheckForUpdates, @@ -148,7 +150,7 @@ app.run((ErrorService, UpdateNotifierService, SelectionStateModel) => { latestVersion }); - return UpdateNotifierService.notify(latestVersion, { + return updateNotifier.notify(latestVersion, { allowSleepUpdateCheck: currentReleaseType === release.RELEASE_TYPE.PRODUCTION }); }).catch(ErrorService.reportException); diff --git a/lib/gui/components/update-notifier.js b/lib/gui/components/update-notifier.js new file mode 100644 index 00000000..e4965d1c --- /dev/null +++ b/lib/gui/components/update-notifier.js @@ -0,0 +1,148 @@ +/* + * 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 electron = require('electron'); +const Bluebird = require('bluebird'); +const _ = require('lodash'); +const settings = require('../models/settings'); +const analytics = require('../modules/analytics'); +const units = require('../../shared/units'); +const release = require('../../shared/release'); +const packageJSON = require('../../../package.json'); + +/** + * @summary The number of days the update notifier can be put to sleep + * @constant + * @private + * @type {Number} + */ +exports.UPDATE_NOTIFIER_SLEEP_DAYS = packageJSON.updates.sleepDays; + +/** + * @summary The current Electron browser window + * @constant + * @private + * @type {Object} + */ +const currentWindow = electron.remote.getCurrentWindow(); + +/** + * @summary Determine if it's time to check for updates + * @function + * @public + * + * @param {Object} options - options + * @param {Number} [options.lastSleptUpdateNotifier] - last slept update notifier time + * @param {String} [options.lastSleptUpdateNotifierVersion] - last slept update notifier version + * @param {String} options.currentVersion - current version + * @returns {Boolean} should check for updates + * + * @example + * if (updateNotifier.shouldCheckForUpdates({ + * lastSleptUpdateNotifier: Date.now(), + * lastSleptUpdateNotifierVersion: '1.0.0', + * currentVersion: '1.0.0' + * })) { + * console.log('We should check for updates!'); + * } + */ +exports.shouldCheckForUpdates = (options) => { + _.defaults(options, { + lastSleptUpdateNotifierVersion: options.currentVersion + }); + + if (_.some([ + !options.lastSleptUpdateNotifier, + release.getReleaseType(options.currentVersion) !== release.RELEASE_TYPE.PRODUCTION, + options.currentVersion !== options.lastSleptUpdateNotifierVersion + ])) { + return true; + } + + return Date.now() - options.lastSleptUpdateNotifier > units.daysToMilliseconds(this.UPDATE_NOTIFIER_SLEEP_DAYS); +}; + +/** + * @summary Open the update notifier widget + * @function + * @public + * + * @param {String} version - version + * @param {Object} [options] - options + * @param {Boolean} [options.allowSleepUpdateCheck=true] - allow sleeping the update check + * @returns {Promise} + * + * @example + * updateNotifier.notify('1.0.0-beta.16', { + * allowSleepUpdateCheck: true + * }); + */ +exports.notify = (version, options = {}) => { + const BUTTONS = [ + 'Download', + 'Skip' + ]; + + const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, _.first(BUTTONS)); + const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, _.last(BUTTONS)); + + const dialogOptions = { + type: 'info', + buttons: BUTTONS, + defaultId: BUTTON_CONFIRMATION_INDEX, + cancelId: BUTTON_REJECTION_INDEX, + title: 'New Update Available!', + message: `Etcher ${version} is available for download` + }; + + if (_.get(options, [ 'allowSleepUpdateCheck' ], true)) { + _.merge(dialogOptions, { + checkboxLabel: `Remind me again in ${this.UPDATE_NOTIFIER_SLEEP_DAYS} days`, + checkboxChecked: false + }); + } + + return new Bluebird((resolve) => { + electron.remote.dialog.showMessageBox(currentWindow, dialogOptions, (response, checkboxChecked) => { + return resolve({ + agreed: response === BUTTON_CONFIRMATION_INDEX, + sleepUpdateCheck: checkboxChecked || false + }); + }); + }).then((results) => { + + // Only update the last slept update timestamp if the + // user ticked the "Remind me again in ..." checkbox, + // but didn't agree. + if (results.sleepUpdateCheck && !results.agreed) { + settings.set('lastSleptUpdateNotifier', Date.now()); + settings.set('lastSleptUpdateNotifierVersion', packageJSON.version); + } + + analytics.logEvent('Close update modal', { + sleepUpdateCheck: results.sleepUpdateCheck, + notifyVersion: version, + currentVersion: packageJSON.version, + agreed: results.agreed + }); + + if (results.agreed) { + electron.shell.openExternal('https://etcher.io?ref=etcher_update'); + } + }); +}; diff --git a/lib/gui/components/update-notifier/controllers/update-notifier.js b/lib/gui/components/update-notifier/controllers/update-notifier.js deleted file mode 100644 index ba12aa7a..00000000 --- a/lib/gui/components/update-notifier/controllers/update-notifier.js +++ /dev/null @@ -1,70 +0,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. - */ - -'use strict'; - -const settings = require('../../../models/settings'); -const analytics = require('../../../modules/analytics'); - -module.exports = function($uibModalInstance, UPDATE_NOTIFIER_SLEEP_DAYS, options) { - - // 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. - settings.set('lastUpdateNotify', Date.now()); - - /** - * @summary The number of days the update notified can be put to sleep - * @constant - * @public - * @type {Number} - */ - this.sleepDays = UPDATE_NOTIFIER_SLEEP_DAYS; - - /** - * @summary Settings model - * @type {Object} - * @public - */ - this.settings = settings; - - /** - * @summary Modal options - * @type {Object} - * @public - */ - this.options = options; - - /** - * @summary Close the modal - * @function - * @public - * - * @example - * UpdateNotifierController.closeModal(); - */ - this.closeModal = () => { - analytics.logEvent('Close update modal', { - sleepUpdateCheck: this.sleepUpdateCheck, - notifyVersion: options.version - }); - - $uibModalInstance.dismiss(); - }; - -}; diff --git a/lib/gui/components/update-notifier/services/update-notifier.js b/lib/gui/components/update-notifier/services/update-notifier.js deleted file mode 100644 index 8958c786..00000000 --- a/lib/gui/components/update-notifier/services/update-notifier.js +++ /dev/null @@ -1,89 +0,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. - */ - -'use strict'; - -const _ = require('lodash'); -const units = require('../../../../shared/units'); -const settings = require('../../../models/settings'); - -module.exports = function(ModalService, UPDATE_NOTIFIER_SLEEP_DAYS) { - - /** - * @summary Determine if it's time to check for updates - * @function - * @public - * - * @param {Object} [options] - options - * @param {Boolean} [options.ignoreSleepUpdateCheck] - ignore sleep update check - * @returns {Boolean} should check for updates - * - * @example - * if (UpdateNotifierService.shouldCheckForUpdates({ - * ignoreSleepUpdateCheck: false - * })) { - * console.log('We should check for updates!'); - * } - */ - this.shouldCheckForUpdates = (options = {}) => { - const lastUpdateNotify = settings.get('lastUpdateNotify'); - - if (_.some([ - !settings.get('sleepUpdateCheck'), - !lastUpdateNotify, - _.get(options, [ 'ignoreSleepUpdateCheck' ], false) - ])) { - return true; - } - - if (lastUpdateNotify - Date.now() > units.daysToMilliseconds(UPDATE_NOTIFIER_SLEEP_DAYS)) { - settings.set('sleepUpdateCheck', false); - return true; - } - - return false; - }; - - /** - * @summary Open the update notifier widget - * @function - * @public - * - * @param {String} version - version - * @param {Object} [options] - options - * @param {Boolean} [options.allowSleepUpdateCheck=true] - allow sleeping the update check - * @returns {Promise} - * - * @example - * UpdateNotifierService.notify('1.0.0-beta.16', { - * allowSleepUpdateCheck: true - * }); - */ - this.notify = (version, options = {}) => { - return ModalService.open({ - template: './components/update-notifier/templates/update-notifier-modal.tpl.html', - controller: 'UpdateNotifierController as modal', - size: 'update-notifier', - resolve: { - options: _.constant({ - version, - allowSleepUpdateCheck: _.get(options, [ 'allowSleepUpdateCheck' ], true) - }) - } - }).result; - }; - -}; diff --git a/lib/gui/components/update-notifier/styles/_update-notifier.scss b/lib/gui/components/update-notifier/styles/_update-notifier.scss deleted file mode 100644 index cf8c7b7c..00000000 --- a/lib/gui/components/update-notifier/styles/_update-notifier.scss +++ /dev/null @@ -1,22 +0,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. - */ - -.modal-update-notifier .checkbox { - color: $palette-theme-light-soft-foreground; - font-size: 11px; - margin-bottom: 0; -} - 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 deleted file mode 100644 index 82d81775..00000000 --- a/lib/gui/components/update-notifier/templates/update-notifier-modal.tpl.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - diff --git a/lib/gui/components/update-notifier/update-notifier.js b/lib/gui/components/update-notifier/update-notifier.js deleted file mode 100644 index e82e9687..00000000 --- a/lib/gui/components/update-notifier/update-notifier.js +++ /dev/null @@ -1,42 +0,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. - */ - -'use strict'; - -/** - * @module Etcher.Components.UpdateNotifier - */ - -const angular = require('angular'); -const MODULE_NAME = 'Etcher.Components.UpdateNotifier'; -const UpdateNotifier = angular.module(MODULE_NAME, [ - require('../modal/modal'), - require('../../os/open-external/open-external') -]); - -/** - * @summary The number of days the update notifier can be put to sleep - * @constant - * @private - * @type {Number} - */ -const UPDATE_NOTIFIER_SLEEP_DAYS = 7; - -UpdateNotifier.constant('UPDATE_NOTIFIER_SLEEP_DAYS', UPDATE_NOTIFIER_SLEEP_DAYS); -UpdateNotifier.controller('UpdateNotifierController', require('./controllers/update-notifier')); -UpdateNotifier.service('UpdateNotifierService', require('./services/update-notifier')); - -module.exports = MODULE_NAME; diff --git a/lib/gui/css/main.css b/lib/gui/css/main.css index b0dc5e7f..6f18bb1e 100644 --- a/lib/gui/css/main.css +++ b/lib/gui/css/main.css @@ -6274,26 +6274,6 @@ body { position: initial; max-width: 50%; } -/* - * 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-update-notifier .checkbox { - color: #b3b3b3; - font-size: 11px; - margin-bottom: 0; } - /* * Copyright 2016 resin.io * diff --git a/lib/gui/models/store.js b/lib/gui/models/store.js index bb4193e0..6744348a 100644 --- a/lib/gui/models/store.js +++ b/lib/gui/models/store.js @@ -49,9 +49,9 @@ const DEFAULT_STATE = Immutable.fromJS({ errorReporting: true, unmountOnSuccess: true, validateWriteOnSuccess: true, - sleepUpdateCheck: false, includeUnstableUpdateChannel: !release.isStableRelease(packageJSON.version), - lastUpdateNotify: null + lastSleptUpdateNotifier: null, + lastSleptUpdateNotifierVersion: null } }); diff --git a/lib/gui/pages/settings/controllers/settings.js b/lib/gui/pages/settings/controllers/settings.js index cd1be236..746560e9 100644 --- a/lib/gui/pages/settings/controllers/settings.js +++ b/lib/gui/pages/settings/controllers/settings.js @@ -55,6 +55,13 @@ module.exports = function(WarningModalService, ErrorService) { */ this.model = settings; + /** + * @summary Environment + * @type {Object} + * @public + */ + this.disableUpdates = Boolean(process.env.ETCHER_DISABLE_UPDATES); + /** * @summary Toggle setting * @function diff --git a/lib/gui/pages/settings/templates/settings.tpl.html b/lib/gui/pages/settings/templates/settings.tpl.html index 0516b317..5348cc4e 100644 --- a/lib/gui/pages/settings/templates/settings.tpl.html +++ b/lib/gui/pages/settings/templates/settings.tpl.html @@ -40,7 +40,7 @@ -
+