diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index c3ee1ddc..65eef47c 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,6 @@ const settings = require('./models/settings') const windowProgress = require('./os/window-progress') const analytics = require('./modules/analytics') const availableDrives = require('./models/available-drives') -const selectionState = require('./models/selection-state') const driveScanner = require('./modules/drive-scanner') const osDialog = require('./os/dialog') const exceptionReporter = require('./modules/exception-reporter') @@ -52,7 +51,7 @@ const updateLock = require('./modules/update-lock') // See https://github.com/visionmedia/debug#browser-support // // Enable drivelist debugging information -// See https://github.com/resin-io-modules/drivelist +// See https://github.com/balena-io-modules/drivelist process.env.DRIVELIST_DEBUG = /drivelist|^\*$/i.test(process.env.DEBUG) ? '1' : '' window.localStorage.debug = process.env.DEBUG @@ -87,21 +86,11 @@ const app = angular.module('Etcher', [ // Components require('./components/svg-icon'), - require('./components/warning-modal/warning-modal'), require('./components/safe-webview'), - require('./components/file-selector'), // Pages - require('./pages/main/main'), - require('./pages/finish/finish'), - require('./components/settings/index.ts').MODULE_NAME, - - // OS - require('./os/open-external/open-external'), - require('./os/dropzone/dropzone'), - - // Utils - require('./utils/manifest-bind/manifest-bind') + require('./pages/main/main.ts').MODULE_NAME, + require('./components/finish/index.ts').MODULE_NAME ]) app.run(() => { @@ -421,40 +410,6 @@ app.config(($locationProvider) => { }) }) -app.controller('HeaderController', function (OSOpenExternalService) { - /** - * @summary Open help page - * @function - * @public - * - * @description - * This application will open either the image's support url, declared - * in the archive `manifest.json`, or the default Etcher help page. - * - * @example - * HeaderController.openHelpPage(); - */ - this.openHelpPage = () => { - const DEFAULT_SUPPORT_URL = 'https://github.com/resin-io/etcher/blob/master/SUPPORT.md' - const supportUrl = selectionState.getImageSupportUrl() || DEFAULT_SUPPORT_URL - OSOpenExternalService.open(supportUrl) - } - - /** - * @summary Whether to show the help link - * @function - * @public - * - * @returns {Boolean} - * - * @example - * HeaderController.shouldShowHelp() - */ - this.shouldShowHelp = () => { - return !settings.get('disableExternalLinks') - } -}) - app.controller('StateController', function ($rootScope, $scope) { const unregisterStateChange = $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => { this.previousName = fromState.name @@ -492,13 +447,6 @@ app.controller('StateController', function ($rootScope, $scope) { this.currentName = null }) -// Handle keyboard shortcut to open the settings -app.run(($state) => { - electron.ipcRenderer.on('menu:preferences', () => { - $state.go('settings') - }) -}) - // Ensure user settings are loaded before // we bootstrap the Angular.js application angular.element(document).ready(() => { diff --git a/lib/gui/app/components/confirm-modal/confirm-modal.js b/lib/gui/app/components/confirm-modal/confirm-modal.js deleted file mode 100644 index c5c09a89..00000000 --- a/lib/gui/app/components/confirm-modal/confirm-modal.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018 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.ConfirmModal - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Components.ConfirmModal' -const ConfirmModal = angular.module(MODULE_NAME, [ - require('../modal/modal') -]) - -ConfirmModal.controller('ConfirmModalController', require('./controllers/confirm-modal')) -ConfirmModal.service('ConfirmModalService', require('./services/confirm-modal')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/confirm-modal/controllers/confirm-modal.js b/lib/gui/app/components/confirm-modal/controllers/confirm-modal.js deleted file mode 100644 index 983f7db7..00000000 --- a/lib/gui/app/components/confirm-modal/controllers/confirm-modal.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018 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, options) { - /** - * @summary Modal options - * @type {Object} - * @public - */ - this.options = options - - /** - * @summary Reject the warning prompt - * @function - * @public - * - * @example - * WarningModalController.reject(); - */ - this.reject = () => { - $uibModalInstance.close(false) - } - - /** - * @summary Accept the warning prompt - * @function - * @public - * - * @example - * WarningModalController.accept(); - */ - this.accept = () => { - $uibModalInstance.close(true) - } -} diff --git a/lib/gui/app/components/confirm-modal/services/confirm-modal.js b/lib/gui/app/components/confirm-modal/services/confirm-modal.js deleted file mode 100644 index 497b98e1..00000000 --- a/lib/gui/app/components/confirm-modal/services/confirm-modal.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2018 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') - -module.exports = function ($sce, ModalService) { - /** - * @summary show the confirm modal - * @function - * @public - * - * @param {Object} options - options - * @param {String} options.description - danger message - * @param {String} options.confirmationLabel - confirmation button text - * @param {String} options.rejectionLabel - rejection button text - * @fulfil {Boolean} - whether the user accepted or rejected the confirm - * @returns {Promise} - * - * @example - * ConfirmModalService.show({ - * description: 'Don\'t do this!', - * confirmationLabel: 'Yes, continue!' - * }); - */ - this.show = (options = {}) => { - options.description = $sce.trustAsHtml(options.description) - return ModalService.open({ - name: 'confirm', - template: require('../templates/confirm-modal.tpl.html'), - controller: 'ConfirmModalController as modal', - size: 'confirm-modal', - resolve: { - options: _.constant(options) - } - }).result - } -} diff --git a/lib/gui/app/components/confirm-modal/styles/confirm-modal.scss b/lib/gui/app/components/confirm-modal/styles/confirm-modal.scss deleted file mode 100644 index b13cafa7..00000000 --- a/lib/gui/app/components/confirm-modal/styles/confirm-modal.scss +++ /dev/null @@ -1,28 +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-confirm-modal .modal-content { - width: 350px; -} - -.modal-confirm-modal .modal-title .glyphicon { - color: $palette-theme-danger-background; -} - -.modal-confirm-modal .modal-body { - max-height: 200px; - overflow-y: auto; -} diff --git a/lib/gui/app/components/confirm-modal/templates/confirm-modal.tpl.html b/lib/gui/app/components/confirm-modal/templates/confirm-modal.tpl.html deleted file mode 100644 index d0f5cf04..00000000 --- a/lib/gui/app/components/confirm-modal/templates/confirm-modal.tpl.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - diff --git a/lib/gui/app/components/drive-selector/DriveSelectorModal.jsx b/lib/gui/app/components/drive-selector/DriveSelectorModal.jsx new file mode 100644 index 00000000..ac47c2ac --- /dev/null +++ b/lib/gui/app/components/drive-selector/DriveSelectorModal.jsx @@ -0,0 +1,337 @@ +/* + * Copyright 2019 balena.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 React = require('react') +const { Modal } = require('rendition') +const { + isDriveValid, + getDriveImageCompatibilityStatuses, + hasListDriveImageCompatibilityStatus, + COMPATIBILITY_STATUS_TYPES +} = require('../../../../shared/drive-constraints') +const store = require('../../models/store') +const analytics = require('../../modules/analytics') +const availableDrives = require('../../models/available-drives') +const selectionState = require('../../models/selection-state') +const { bytesToClosestUnit } = require('../../../../shared/units') +const utils = require('../../../../shared/utils') +const { open: openExternal } = require('../../os/open-external/services/open-external') + +/** + * @summary Determine if we can change a drive's selection state + * @function + * @private + * + * @param {Object} drive - drive + * @returns {Promise} + * + * @example + * shouldChangeDriveSelectionState(drive) + * .then((shouldChangeDriveSelectionState) => { + * if (shouldChangeDriveSelectionState) doSomething(); + * }); + */ +const shouldChangeDriveSelectionState = (drive) => { + return isDriveValid(drive, selectionState.getImage()) +} + +/** + * @summary Toggle a drive selection + * @function + * @public + * + * @param {Object} drive - drive + * @returns {void} + * + * @example + * toggleDrive({ + * device: '/dev/disk2', + * size: 999999999, + * name: 'Cruzer USB drive' + * }); + */ +const toggleDrive = (drive) => { + const canChangeDriveSelectionState = shouldChangeDriveSelectionState(drive) + + if (canChangeDriveSelectionState) { + analytics.logEvent('Toggle drive', { + drive, + previouslySelected: selectionState.isCurrentDrive(availableDrives.device), + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + + selectionState.toggleDrive(drive.device) + } +} + +/** + * @summary Memoized getDrives function + * @function + * @public + * + * @returns {Array} - memoized list of drives + * + * @example + * const drives = getDrives() + * // Do something with drives + */ +const getDrives = utils.memoize(availableDrives.getDrives, _.isEqual) + +/** + * @summary Get a drive's compatibility status object(s) + * @function + * @public + * + * @description + * Given a drive, return its compatibility status with the selected image, + * containing the status type (ERROR, WARNING), and accompanying + * status message. + * + * @returns {Object[]} list of objects containing statuses + * + * @example + * const statuses = getDriveStatuses(drive); + * + * for ({ type, message } of statuses) { + * // do something + * } + */ +const getDriveStatuses = utils.memoize((drive) => { + return getDriveImageCompatibilityStatuses(drive, selectionState.getImage()) +}, _.isEqual) + +/** + * @summary Keyboard event drive toggling + * @function + * @public + * + * @description + * Keyboard-event specific entry to the toggleDrive function. + * + * @param {Object} drive - drive + * @param {Object} evt - event + * + * @example + *
+ * Tab-select me and press enter or space! + *
+ */ +const keyboardToggleDrive = (drive, evt) => { + const ENTER = 13 + const SPACE = 32 + if (_.includes([ ENTER, SPACE ], evt.keyCode)) { + toggleDrive(drive) + } +} + +const DriveSelectorModal = ({ close }) => { + const [ confirmModal, setConfirmModal ] = React.useState({ open: false }) + const [ drives, setDrives ] = React.useState(getDrives()) + + React.useEffect(() => { + const unsubscribe = store.subscribe(() => { + setDrives(availableDrives.getDrives()) + }) + return unsubscribe + }) + + /** + * @summary Prompt the user to install missing usbboot drivers + * @function + * @public + * + * @param {Object} drive - drive + * @returns {void} + * + * @example + * installMissingDrivers({ + * linkTitle: 'Go to example.com', + * linkMessage: 'Examples are great, right?', + * linkCTA: 'Call To Action', + * link: 'https://example.com' + * }); + */ + const installMissingDrivers = (drive) => { + if (drive.link) { + analytics.logEvent('Open driver link modal', { + url: drive.link, + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + + setConfirmModal({ + open: true, + options: { + width: 400, + title: drive.linkTitle, + cancel: () => setConfirmModal({ open: false }), + done: async (shouldContinue) => { + try { + if (shouldContinue) { + openExternal(drive.link) + } else { + setConfirmModal({ open: false }) + } + } catch (error) { + analytics.logException(error) + } + }, + action: 'Yes, continue', + cancelButtonProps: { + children: 'Cancel' + }, + children: drive.linkMessage || `Etcher will open ${drive.link} in your browser` + } + }) + } + } + + /** + * @summary Select a drive and close the modal + * @function + * @public + * + * @param {Object} drive - drive + * @returns {void} + * + * @example + * selectDriveAndClose({ + * device: '/dev/disk2', + * size: 999999999, + * name: 'Cruzer USB drive' + * }); + */ + const selectDriveAndClose = async (drive) => { + const canChangeDriveSelectionState = await shouldChangeDriveSelectionState(drive) + + if (canChangeDriveSelectionState) { + selectionState.selectDrive(drive.device) + + analytics.logEvent('Drive selected (double click)', { + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + + close() + } + } + + const hasStatus = hasListDriveImageCompatibilityStatus(selectionState.getSelectedDrives(), selectionState.getImage()) + + return ( + +
+
    + {_.map(drives, (drive, index) => { + return ( +
  • selectDriveAndClose(drive, close)} + onClick={() => toggleDrive(drive)} + > + {drive.icon && Drive device type logo} +
    keyboardToggleDrive(drive, evt)}> + +
    + { drive.description } + {drive.size && - { bytesToClosestUnit(drive.size) }} +
    + {!drive.link &&

    + { drive.displayName } +

    } + {drive.link &&

    + { drive.displayName } - installMissingDrivers(drive)}>{ drive.linkCTA } +

    } + +
    + {_.map(getDriveStatuses(drive), (status, idx) => { + const className = { + [COMPATIBILITY_STATUS_TYPES.WARNING]: 'label-warning', + [COMPATIBILITY_STATUS_TYPES.ERROR]: 'label-danger' + } + return ( + + { status.message } + + ) + })} +
    + {Boolean(drive.progress) && ( + + + )} +
    + + {isDriveValid(drive, selectionState.getImage()) && ( + + + )} +
  • + ) + })} + {!availableDrives.hasAvailableDrives() &&
  • +
    + Connect a drive! +
    No removable drive detected.
    +
    +
  • } +
+
+ + {confirmModal.open && + + } +
+ ) +} + +module.exports = DriveSelectorModal diff --git a/lib/gui/app/components/drive-selector/controllers/drive-selector.js b/lib/gui/app/components/drive-selector/controllers/drive-selector.js deleted file mode 100644 index 35bc3c19..00000000 --- a/lib/gui/app/components/drive-selector/controllers/drive-selector.js +++ /dev/null @@ -1,265 +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 angular = require('angular') -const _ = require('lodash') -const Bluebird = require('bluebird') -const constraints = require('../../../../../shared/drive-constraints') -const store = require('../../../models/store') -const analytics = require('../../../modules/analytics') -const availableDrives = require('../../../models/available-drives') -const selectionState = require('../../../models/selection-state') -const utils = require('../../../../../shared/utils') - -module.exports = function ( - $q, - $uibModalInstance, - ConfirmModalService, - OSOpenExternalService -) { - /** - * @summary The drive selector state - * @type {Object} - * @public - */ - this.state = selectionState - - /** - * @summary Static methods to check a drive's properties - * @type {Object} - * @public - */ - this.constraints = constraints - - /** - * @summary The drives model - * @type {Object} - * @public - * - * @description - * We expose the whole service instead of the `.drives` - * property, which is the one we're interested in since - * this allows the property to be automatically updated - * when `availableDrives` detects a change in the drives. - */ - this.drives = availableDrives - - /** - * @summary Determine if we can change a drive's selection state - * @function - * @private - * - * @param {Object} drive - drive - * @returns {Promise} - * - * @example - * DriveSelectorController.shouldChangeDriveSelectionState(drive) - * .then((shouldChangeDriveSelectionState) => { - * if (shouldChangeDriveSelectionState) doSomething(); - * }); - */ - const shouldChangeDriveSelectionState = (drive) => { - return $q.resolve(constraints.isDriveValid(drive, selectionState.getImage())) - } - - /** - * @summary Toggle a drive selection - * @function - * @public - * - * @param {Object} drive - drive - * @returns {Promise} - resolved promise - * - * @example - * DriveSelectorController.toggleDrive({ - * device: '/dev/disk2', - * size: 999999999, - * name: 'Cruzer USB drive' - * }); - */ - this.toggleDrive = (drive) => { - return shouldChangeDriveSelectionState(drive).then((canChangeDriveSelectionState) => { - if (canChangeDriveSelectionState) { - analytics.logEvent('Toggle drive', { - drive, - previouslySelected: selectionState.isCurrentDrive(drive.device), - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - - selectionState.toggleDrive(drive.device) - } - - return Bluebird.resolve() - }) - } - - /** - * @summary Prompt the user to install missing usbboot drivers - * @function - * @public - * - * @param {Object} drive - drive - * @returns {Promise} - resolved promise - * - * @example - * DriveSelectorController.installMissingDrivers({ - * linkTitle: 'Go to example.com', - * linkMessage: 'Examples are great, right?', - * linkCTA: 'Call To Action', - * link: 'https://example.com' - * }); - */ - this.installMissingDrivers = (drive) => { - if (drive.link) { - analytics.logEvent('Open driver link modal', { - url: drive.link, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - - return ConfirmModalService.show({ - confirmationLabel: 'Yes, continue', - rejectionLabel: 'Cancel', - title: drive.linkTitle, - confirmButton: 'primary', - message: drive.linkMessage || `Etcher will open ${drive.link} in your browser` - }).then((shouldContinue) => { - if (shouldContinue) { - OSOpenExternalService.open(drive.link) - } - }).catch((error) => { - analytics.logException(error) - }) - } - - return Bluebird.resolve() - } - - /** - * @summary Close the modal and resolve the selected drive - * @function - * @public - * - * @example - * DriveSelectorController.closeModal(); - */ - this.closeModal = () => { - const selectedDrive = selectionState.getCurrentDrive() - - // Sanity check to cover the case where a drive is selected, - // the drive is then unplugged from the computer and the modal - // is resolved with a non-existent drive. - if (!selectedDrive || !_.includes(this.drives.getDrives(), selectedDrive)) { - $uibModalInstance.close() - } else { - $uibModalInstance.close(selectedDrive) - } - } - - /** - * @summary Select a drive and close the modal - * @function - * @public - * - * @param {Object} drive - drive - * @returns {Promise} - resolved promise - * - * @example - * DriveSelectorController.selectDriveAndClose({ - * device: '/dev/disk2', - * size: 999999999, - * name: 'Cruzer USB drive' - * }); - */ - this.selectDriveAndClose = (drive) => { - return shouldChangeDriveSelectionState(drive).then((canChangeDriveSelectionState) => { - if (canChangeDriveSelectionState) { - selectionState.selectDrive(drive.device) - - analytics.logEvent('Drive selected (double click)', { - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - - this.closeModal() - } - }) - } - - /** - * @summary Memoized getDrives function - * @function - * @public - * - * @returns {Array} - memoized list of drives - * - * @example - * const drives = DriveSelectorController.getDrives() - * // Do something with drives - */ - this.getDrives = utils.memoize(this.drives.getDrives, angular.equals) - - /** - * @summary Get a drive's compatibility status object(s) - * @function - * @public - * - * @description - * Given a drive, return its compatibility status with the selected image, - * containing the status type (ERROR, WARNING), and accompanying - * status message. - * - * @returns {Object[]} list of objects containing statuses - * - * @example - * const statuses = DriveSelectorController.getDriveStatuses(drive); - * - * for ({ type, message } of statuses) { - * // do something - * } - */ - this.getDriveStatuses = utils.memoize((drive) => { - return this.constraints.getDriveImageCompatibilityStatuses(drive, this.state.getImage()) - }, angular.equals) - - /** - * @summary Keyboard event drive toggling - * @function - * @public - * - * @description - * Keyboard-event specific entry to the toggleDrive function. - * - * @param {Object} drive - drive - * @param {Object} $event - event - * - * @example - *
- * Tab-select me and press enter or space! - *
- */ - this.keyboardToggleDrive = (drive, $event) => { - console.log($event.keyCode) - const ENTER = 13 - const SPACE = 32 - if (_.includes([ ENTER, SPACE ], $event.keyCode)) { - this.toggleDrive(drive) - } - } -} diff --git a/lib/gui/app/components/drive-selector/drive-selector.js b/lib/gui/app/components/drive-selector/drive-selector.js deleted file mode 100644 index adf0ed6b..00000000 --- a/lib/gui/app/components/drive-selector/drive-selector.js +++ /dev/null @@ -1,35 +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.DriveSelector - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Components.DriveSelector' -const DriveSelector = angular.module(MODULE_NAME, [ - require('../modal/modal'), - require('../confirm-modal/confirm-modal'), - require('../../utils/byte-size/byte-size'), - require('../../os/open-external/open-external') -]) - -DriveSelector.controller('DriveSelectorController', require('./controllers/drive-selector')) -DriveSelector.service('DriveSelectorService', require('./services/drive-selector')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/drive-selector/services/drive-selector.js b/lib/gui/app/components/drive-selector/services/drive-selector.js deleted file mode 100644 index dcb7c9c7..00000000 --- a/lib/gui/app/components/drive-selector/services/drive-selector.js +++ /dev/null @@ -1,66 +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.exports = function (ModalService, $q) { - let modal = null - - /** - * @summary Open the drive selector widget - * @function - * @public - * - * @fulfil {(Object|Undefined)} - selected drive - * @returns {Promise} - * - * @example - * DriveSelectorService.open().then((drive) => { - * console.log(drive); - * }); - */ - this.open = () => { - modal = ModalService.open({ - name: 'drive-selector', - template: require('../templates/drive-selector-modal.tpl.html'), - controller: 'DriveSelectorController as modal', - size: 'drive-selector-modal' - }) - - return modal.result - } - - /** - * @summary Close the drive selector widget - * @function - * @public - * - * @fulfil {Undefined} - * @returns {Promise} - * - * @example - * DriveSelectorService.close(); - */ - this.close = () => { - if (modal) { - return modal.close() - } - - // Resolve `undefined` if the modal - // was already closed for consistency - return $q.resolve() - } -} diff --git a/lib/gui/app/components/drive-selector/styles/_drive-selector.scss b/lib/gui/app/components/drive-selector/styles/_drive-selector.scss index 1a1a98a0..809f693f 100644 --- a/lib/gui/app/components/drive-selector/styles/_drive-selector.scss +++ b/lib/gui/app/components/drive-selector/styles/_drive-selector.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,10 +54,13 @@ .list-group-item-section-expanded { flex-grow: 1; + margin-left: 15px; } .list-group-item-section + .list-group-item-section { margin-left: 10px; + display: inline-block; + vertical-align: middle; } > .tick { @@ -72,7 +75,7 @@ color: $palette-theme-light-soft-foreground; } - progress { + .drive-init-progress { appearance: none; width: 100%; height: 2.5px; @@ -80,13 +83,13 @@ border-radius: 50% 50%; } - progress::-webkit-progress-bar { + .drive-init-progress::-webkit-progress-bar { background-color: $palette-theme-default-background; border: none; outline: none; } - progress::-webkit-progress-value { + .drive-init-progress::-webkit-progress-value { border-bottom: 1px solid darken($palette-theme-primary-background, 15); background-color: $palette-theme-primary-background; } diff --git a/lib/gui/app/components/drive-selector/target-selector.jsx b/lib/gui/app/components/drive-selector/target-selector.jsx index 9706b89a..83e564a1 100644 --- a/lib/gui/app/components/drive-selector/target-selector.jsx +++ b/lib/gui/app/components/drive-selector/target-selector.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2019 resin.io + * Copyright 2019 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,7 @@ const { ChangeButton, DetailsText, StepButton, - StepNameButton, - ThemedProvider + StepNameButton } = require('./../../styled-components') const { Txt } = require('rendition') const middleEllipsis = require('./../../utils/middle-ellipsis') @@ -66,7 +65,7 @@ const TargetSelector = (props) => { if (targets.length === 1) { const target = targets[0] return ( - + { {/* eslint-disable no-magic-numbers */} { middleEllipsis(target.description, 20) } - { !props.flashing && + {!props.flashing && { } { bytesToClosestUnit(target.size) } - + ) } @@ -118,7 +117,7 @@ const TargetSelector = (props) => { )) } return ( - + { } {targetsTemplate} - + ) } return ( - - 0) ? -1 : 2 } - disabled={props.disabled} - onClick={props.openDriveSelector} - > - Select target - - + 0) ? -1 : 2 } + disabled={props.disabled} + onClick={props.openDriveSelector} + > + Select target + ) } TargetSelector.propTypes = { + targets: propTypes.array, disabled: propTypes.bool, openDriveSelector: propTypes.func, selection: propTypes.object, diff --git a/lib/gui/app/components/drive-selector/templates/drive-selector-modal.tpl.html b/lib/gui/app/components/drive-selector/templates/drive-selector-modal.tpl.html deleted file mode 100644 index f8df0dd1..00000000 --- a/lib/gui/app/components/drive-selector/templates/drive-selector-modal.tpl.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - diff --git a/lib/gui/app/components/featured-project/featured-project.jsx b/lib/gui/app/components/featured-project/featured-project.jsx index 0fca321b..24bfd6a6 100644 --- a/lib/gui/app/components/featured-project/featured-project.jsx +++ b/lib/gui/app/components/featured-project/featured-project.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/featured-project/index.js b/lib/gui/app/components/featured-project/index.js deleted file mode 100644 index 033edb9e..00000000 --- a/lib/gui/app/components/featured-project/index.js +++ /dev/null @@ -1,34 +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.FeaturedProject - */ - -const angular = require('angular') -const { react2angular } = require('react2angular') - -const MODULE_NAME = 'Etcher.Components.FeaturedProject' -const FeaturedProject = angular.module(MODULE_NAME, []) - -FeaturedProject.component( - 'featuredProject', - react2angular(require('./featured-project.jsx')) -) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/file-selector/controllers/file-selector.js b/lib/gui/app/components/file-selector/controllers/file-selector.js deleted file mode 100644 index 5463ec68..00000000 --- a/lib/gui/app/components/file-selector/controllers/file-selector.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2018 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 os = require('os') -const settings = require('../../../models/settings') -const utils = require('../../../../../shared/utils') -const angular = require('angular') - -/* eslint-disable lodash/prefer-lodash-method */ - -module.exports = function ( - $uibModalInstance -) { - /** - * @summary Close the modal - * @function - * @public - * - * @example - * FileSelectorController.close(); - */ - this.close = () => { - $uibModalInstance.close() - } - - /** - * @summary Folder to constrain the file picker to - * @function - * @public - * - * @returns {String} - folder to constrain by - * - * @example - * FileSelectorController.getFolderConstraint() - */ - this.getFolderConstraint = utils.memoize(() => { - return settings.has('fileBrowserConstraintPath') - ? settings.get('fileBrowserConstraintPath') - : '' - }, angular.equals) - - /** - * @summary Get initial path - * @function - * @public - * - * @returns {String} - path - * - * @example - * - */ - this.getPath = () => { - const constraintFolderPath = this.getFolderConstraint() - return _.isEmpty(constraintFolderPath) ? os.homedir() : constraintFolderPath - } -} diff --git a/lib/gui/app/components/file-selector/file-selector/colors.js b/lib/gui/app/components/file-selector/file-selector/colors.js deleted file mode 100644 index fab0281e..00000000 --- a/lib/gui/app/components/file-selector/file-selector/colors.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2018 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' - -/** - * @summary Color scheme - * @constant - * @private - */ -const colors = { - primary: { - color: '#3a3c41', - background: '#ffffff', - subColor: '#ababab', - faded: '#c3c4c6' - }, - secondary: { - color: '#1c1d1e', - background: '#ebeff4', - title: '#b3b6b9' - }, - highlight: { - color: 'white', - background: '#2297de' - }, - soft: { - color: '#4d5056' - } -} - -module.exports = colors diff --git a/lib/gui/app/components/file-selector/file-selector/file-list.jsx b/lib/gui/app/components/file-selector/file-selector/file-list.jsx deleted file mode 100644 index 60f0ad11..00000000 --- a/lib/gui/app/components/file-selector/file-selector/file-list.jsx +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2018 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 React = require('react') -const propTypes = require('prop-types') -const styled = require('styled-components').default -const rendition = require('rendition') -const colors = require('./colors') - -const prettyBytes = require('pretty-bytes') -const files = require('../../../models/files') -const middleEllipsis = require('../../../utils/middle-ellipsis') -const supportedFormats = require('../../../../../shared/supported-formats') - -const debug = require('debug')('etcher:gui:file-selector') - -/** - * @summary Character limit of a filename before a middle-ellipsis is added - * @constant - * @private - */ -const FILENAME_CHAR_LIMIT = 20 - -/** - * @summary Pattern to match all supported formats for highlighting - * @constant - * @private - */ -const SUPPORTED_FORMATS_PATTERN = new RegExp(`^\\.(${supportedFormats.getAllExtensions().join('|')})$`, 'i') - -/** - * @summary Flex styled component - * @function - * @type {ReactElement} - */ -const Flex = styled.div` - display: flex; - flex: ${ props => props.flex }; - flex-direction: ${ props => props.direction }; - justify-content: ${ props => props.justifyContent }; - align-items: ${ props => props.alignItems }; - flex-wrap: ${ props => props.wrap }; - flex-grow: ${ props => props.grow }; -` - -/** - * @summary Anchor flex styled component - * @function - * @type {ReactElement} - */ -const ClickableFlex = styled.a` - display: flex; - flex: ${ props => props.flex }; - flex-direction: ${ props => props.direction }; - justify-content: ${ props => props.justifyContent }; - align-items: ${ props => props.alignItems }; - flex-wrap: ${ props => props.wrap }; - flex-grow: ${ props => props.grow }; -` - -/** - * @summary FileList scroll wrapper element - * @class - * @type {ReactElement} - */ -class UnstyledFileListWrap extends React.PureComponent { - constructor (props) { - super(props) - this.scrollElem = null - } - - render () { - return ( - - { this.props.children } - - ) - } - - setScrollElem (element) { - this.scrollElem = element - } - - componentDidUpdate (prevProps) { - if (this.scrollElem) { - this.scrollElem.scrollTop = 0 - } - } - -} - -/** - * @summary FileList scroll wrapper element - * @class - * @type {StyledComponent} - */ -const FileListWrap = styled(UnstyledFileListWrap)` - overflow-x: hidden; - overflow-y: auto; - padding: 0 20px; -` - -/** - * @summary File element - * @class - * @type {ReactElement} - */ -class UnstyledFile extends React.PureComponent { - - static getFileIconClass (file) { - return file.isDirectory - ? 'fas fa-folder' - : 'fas fa-file-alt' - } - - onHighlight (event) { - event.preventDefault() - this.props.onHighlight(this.props.file) - } - - onSelect (event) { - event.preventDefault() - this.props.onSelect(this.props.file) - } - - render () { - const file = this.props.file - return ( - - - { middleEllipsis(file.basename, FILENAME_CHAR_LIMIT) } -
{ file.isDirectory ? '' : prettyBytes(file.size || 0) }
-
- ) - } -} - -/** - * @summary File element - * @class - * @type {StyledComponent} - */ -const File = styled(UnstyledFile)` - width: 100px; - min-height: 100px; - max-height: 128px; - margin: 5px 10px; - padding: 5px; - background-color: none; - transition: 0.05s background-color ease-out; - color: ${ colors.primary.color }; - cursor: pointer; - border-radius: 5px; - word-break: break-word; - - > span:first-of-type { - align-self: center; - line-height: 1; - margin-bottom: 6px; - font-size: 48px; - color: ${ props => props.disabled ? colors.primary.faded : colors.soft.color }; - } - - > span:last-of-type { - display: flex; - justify-content: center; - text-align: center; - font-size: 16px; - } - - > div:last-child { - background-color: none; - color: ${ colors.primary.subColor }; - text-align: center; - font-size: 12px; - } - - :hover, :visited { - color: ${ colors.primary.color }; - } - - :focus, - :active { - color: ${ colors.highlight.color }; - background-color: ${ colors.highlight.background }; - } - - :focus > span:first-of-type, - :active > span:first-of-type { - color: ${ colors.highlight.color }; - } - - :focus > div:last-child, - :active > div:last-child { - color: ${ colors.highlight.color }; - } -` - -/** - * @summary FileList element - * @class - * @type {ReactElement} - */ -class FileList extends React.Component { - constructor (props) { - super(props) - - this.state = { - path: props.path, - highlighted: null, - files: [], - } - - debug('FileList', props) - } - - readdir (dirname) { - debug('FileList:readdir', dirname) - - if (this.props.constraintPath && dirname === '/') { - if (this.props.constraint) { - const mountpoints = this.props.constraint.mountpoints.map(( mount ) => { - const entry = new files.FileEntry(mount.path, { - size: 0, - isFile: () => false, - isDirectory: () => true - }) - entry.name = mount.label - return entry - }) - debug('FileList:readdir', mountpoints) - window.requestAnimationFrame(() => { - this.setState({ files: mountpoints }) - }) - } - return - } - - files.readdirAsync(dirname).then((files) => { - window.requestAnimationFrame(() => { - this.setState({ files: files }) - }) - }) - } - - componentDidMount () { - process.nextTick(() => { - this.readdir(this.state.path) - }) - } - - onHighlight (file) { - debug('FileList:onHighlight', file) - this.props.onHighlight(file) - } - - onSelect (file) { - debug('FileList:onSelect', file.path, file.isDirectory) - this.props.onSelect(file) - } - - shouldComponentUpdate (nextProps, nextState) { - const shouldUpdate = (this.state.files !== nextState.files) - debug('FileList:shouldComponentUpdate', shouldUpdate) - if (this.props.path !== nextProps.path || this.props.constraint !== nextProps.constraint) { - process.nextTick(() => { - this.readdir(nextProps.path) - }) - } - return shouldUpdate - } - - static isSelectable (file) { - return file.isDirectory || !file.ext || - SUPPORTED_FORMATS_PATTERN.test(file.ext) - } - - render () { - return ( - - { - this.state.files.map((file) => { - return ( - - ) - }) - } - - ) - } -} - -module.exports = FileList diff --git a/lib/gui/app/components/file-selector/file-selector/file-selector.jsx b/lib/gui/app/components/file-selector/file-selector/file-selector.jsx deleted file mode 100644 index ba262d74..00000000 --- a/lib/gui/app/components/file-selector/file-selector/file-selector.jsx +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright 2018 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 path = require('path') -const sdk = require('etcher-sdk') - -const Bluebird = require('bluebird') -const React = require('react') -const propTypes = require('prop-types') -const styled = require('styled-components').default -const rendition = require('rendition') -const colors = require('./colors') - -const Breadcrumbs = require('./path-breadcrumbs') -const FileList = require('./file-list') -const RecentFiles = require('./recent-files') -const files = require('../../../models/files') - -const selectionState = require('../../../models/selection-state') -const store = require('../../../models/store') -const osDialog = require('../../../os/dialog') -const exceptionReporter = require('../../../modules/exception-reporter') -const messages = require('../../../../../shared/messages') -const errors = require('../../../../../shared/errors') -const supportedFormats = require('../../../../../shared/supported-formats') -const analytics = require('../../../modules/analytics') - -const debug = require('debug')('etcher:gui:file-selector') - -/** - * @summary Flex styled component - * @function - * @type {ReactElement} - */ -const Flex = styled.div` - display: flex; - flex: ${ props => props.flex }; - flex-direction: ${ props => props.direction }; - justify-content: ${ props => props.justifyContent }; - align-items: ${ props => props.alignItems }; - flex-wrap: ${ props => props.wrap }; - flex-grow: ${ props => props.grow }; - overflow: ${ props => props.overflow }; -` - -const Header = styled(Flex) ` - padding: 10px 15px 0; - border-bottom: 1px solid ${ colors.primary.faded }; - - > * { - margin: 5px; - } -` - -const Main = styled(Flex) `` - -const Footer = styled(Flex) ` - padding: 10px; - flex: 0 0 auto; - border-top: 1px solid ${ colors.primary.faded }; - - > * { - margin: 0 10px; - } - - > button { - flex-grow: 0; - flex-shrink: 0; - } -` - -class UnstyledFilePath extends React.PureComponent { - render () { - return ( -
- { - this.props.file && !this.props.file.isDirectory - ? this.props.file.basename - : '' - } -
- ) - } -} - -const FilePath = styled(UnstyledFilePath)` - display: flex; - flex-grow: 1; - align-items: center; - overflow: hidden; - - > span { - font-size: 16px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -` - -class FileSelector extends React.PureComponent { - constructor (props) { - super(props) - - this.state = { - path: props.path, - highlighted: null, - constraint: null, - files: [], - } - - } - - componentDidMount() { - if (this.props.constraintpath) { - const device = files.getConstraintDevice(this.props.constraintpath) - debug('FileSelector:getConstraintDevice', device) - if (device !== undefined) { - this.setState({ constraint: device.drive }) - } - } - } - - confirmSelection () { - if (this.state.highlighted) { - this.selectFile(this.state.highlighted) - } - } - - close () { - this.props.close() - } - - componentDidUpdate () { - debug('FileSelector:componentDidUpdate') - } - - containPath (newPath) { - if (this.state.constraint) { - const isContained = this.state.constraint.mountpoints.some((mount) => { - return !path.relative(mount.path, newPath).startsWith('..') - }) - if (!isContained) { - return '/' - } - } - return newPath - } - - navigate (newPath) { - debug('FileSelector:navigate', newPath) - this.setState({ path: this.containPath(newPath) }) - } - - navigateUp () { - let newPath = this.containPath(path.join(this.state.path, '..')) - debug('FileSelector:navigateUp', this.state.path, '->', newPath) - this.setState({ path: newPath }) - } - - selectImage (image) { - debug('FileSelector:selectImage', image) - - if (!supportedFormats.isSupportedImage(image.path)) { - const invalidImageError = errors.createUserError({ - title: 'Invalid image', - description: messages.error.invalidImage(image.path) - }) - - osDialog.showError(invalidImageError) - analytics.logEvent('Invalid image', { - image, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - return Bluebird.resolve() - } - - return Bluebird.try(() => { - let message = null - - if (supportedFormats.looksLikeWindowsImage(image.path)) { - analytics.logEvent('Possibly Windows image', { - image, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - message = messages.warning.looksLikeWindowsImage() - } else if (!image.hasMBR) { - analytics.logEvent('Missing partition table', { - image, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - message = messages.warning.missingPartitionTable() - } - - if (message) { - // TODO: `Continue` should be on a red background (dangerous action) instead of `Change`. - // We want `X` to act as `Continue`, that's why `Continue` is the `rejectionLabel` - return osDialog.showWarning({ - confirmationLabel: 'Change', - rejectionLabel: 'Continue', - title: 'Warning', - description: message - }) - } - - return false - }).then((shouldChange) => { - if (shouldChange) { - return - } - - selectionState.selectImage(image) - - this.close() - - // An easy way so we can quickly identify if we're making use of - // certain features without printing pages of text to DevTools. - image.logo = Boolean(image.logo) - image.blockMap = Boolean(image.blockMap) - - analytics.logEvent('Select image', { - image, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - }).catch(exceptionReporter.report) - } - - selectFile (file) { - debug('FileSelector:selectFile', file) - - if (file.isDirectory) { - this.navigate(file.path) - return - } - - if (!supportedFormats.isSupportedImage(file.path)) { - const invalidImageError = errors.createUserError({ - title: 'Invalid image', - description: messages.error.invalidImage(file.path) - }) - - osDialog.showError(invalidImageError) - analytics.logEvent('Invalid image', { path: file.path }) - return - } - - debug('FileSelector:getImageMetadata', file) - - const source = new sdk.sourceDestination.File(file.path, sdk.sourceDestination.File.OpenFlags.Read) - source.getInnerSource() - .then((innerSource) => { - return innerSource.getMetadata() - .then((imageMetadata) => { - debug('FileSelector:getImageMetadata', imageMetadata) - imageMetadata.path = file.path - imageMetadata.extension = path.extname(file.path).slice(1) - return innerSource.getPartitionTable() - .then((partitionTable) => { - if (partitionTable !== undefined) { - imageMetadata.hasMBR = true - imageMetadata.partitions = partitionTable.partitions - } - return this.selectImage(imageMetadata) - }) - }) - }) - .catch((error) => { - debug('FileSelector:getImageMetadata', error) - const imageError = errors.createUserError({ - title: 'Error opening image', - description: messages.error.openImage(path.basename(file.path), error.message) - }) - - osDialog.showError(imageError) - analytics.logException(error) - }) - } - - onHighlight (file) { - this.setState({ highlighted: file }) - } - - render () { - const styles = { - display: 'flex', - height: 'calc(100vh - 20px)', - } - return ( - - {/**/} - -
- - -  Back - - - -
-
- - - -
-
- - Cancel - - Select file - -
-
-
- ) - } -} - -FileSelector.propTypes = { - path: propTypes.string, - close: propTypes.func, - constraintpath: propTypes.string, -} - -module.exports = FileSelector diff --git a/lib/gui/app/components/file-selector/file-selector/path-breadcrumbs.jsx b/lib/gui/app/components/file-selector/file-selector/path-breadcrumbs.jsx deleted file mode 100644 index 23eaa5ee..00000000 --- a/lib/gui/app/components/file-selector/file-selector/path-breadcrumbs.jsx +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2018 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 path = require('path') - -const React = require('react') -const propTypes = require('prop-types') -const styled = require('styled-components').default -const rendition = require('rendition') - -const middleEllipsis = require('../../../utils/middle-ellipsis') - -/** - * @summary How many directories to show with the breadcrumbs - * @type {Number} - * @constant - * @private - */ -const MAX_DIR_CRUMBS = 3 - -/** - * @summary Character limit of a filename before a middle-ellipsis is added - * @constant - * @private - */ -const FILENAME_CHAR_LIMIT_SHORT = 15 - -function splitComponents(dirname, root) { - const components = [] - let basename = null - root = root || path.parse(dirname).root - while( dirname !== root ) { - basename = path.basename(dirname) - components.unshift({ - path: dirname, - basename: basename, - name: basename - }) - dirname = path.join( dirname, '..' ) - } - if (components.length < MAX_DIR_CRUMBS) { - components.unshift({ - path: root, - basename: root, - name: 'Root' - }) - } - return components -} - -class Crumb extends React.PureComponent { - constructor (props) { - super(props) - } - - render () { - return ( - - - { middleEllipsis(this.props.dir.name, FILENAME_CHAR_LIMIT_SHORT) } - - - ) - } - - navigate () { - this.props.navigate(this.props.dir.path) - } -} - -class UnstyledBreadcrumbs extends React.PureComponent { - render () { - const components = splitComponents(this.props.path).slice(-MAX_DIR_CRUMBS) - return ( -
- { - components.map((dir, index) => { - return ( - - ) - }) - } -
- ) - } -} - -const Breadcrumbs = styled(UnstyledBreadcrumbs)` - font-size: 18px; - - & > button:not(:last-child)::after { - content: '/'; - margin: 9px; - } -` - -module.exports = Breadcrumbs diff --git a/lib/gui/app/components/file-selector/file-selector/recent-files.jsx b/lib/gui/app/components/file-selector/file-selector/recent-files.jsx deleted file mode 100644 index ea655b74..00000000 --- a/lib/gui/app/components/file-selector/file-selector/recent-files.jsx +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2018 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 React = require('react') -const propTypes = require('prop-types') -const styled = require('styled-components').default -const rendition = require('rendition') -const colors = require('./colors') - -const middleEllipsis = require('../../../utils/middle-ellipsis') - -/** - * @summary Flex styled component - * @function - * @type {ReactElement} - */ -const Flex = styled.div` - display: flex; - flex: ${ props => props.flex }; - flex-direction: ${ props => props.direction }; - justify-content: ${ props => props.justifyContent }; - align-items: ${ props => props.alignItems }; - flex-wrap: ${ props => props.wrap }; - flex-grow: ${ props => props.grow }; -` - -class RecentFileLink extends React.PureComponent { - constructor (props) { - super(props) - } - - render () { - const file = this.props.file - return ( - - { middleEllipsis(file.name, FILENAME_CHAR_LIMIT_SHORT) } - - ) - } - - select () { - this.props.onSelect(this.props.file) - } -} - -class UnstyledRecentFiles extends React.PureComponent { - constructor(props) { - super(props) - this.state = { - recent: [], - favorites: [] - } - } - - render () { - return ( - -
Recent
- { - this.state.recent.map((file) => { - - }) - } -
Favorite
- { - this.state.favorites.map((file) => { - - }) - } -
- ) - } -} - -const RecentFiles = styled(UnstyledRecentFiles)` - display: flex; - flex: 0 0 auto; - flex-direction: column; - align-items: flex-start; - width: 130px; - background-color: ${ colors.secondary.background }; - padding: 20px; - color: ${ colors.secondary.color }; - - > h5 { - color: ${ colors.secondary.title }; - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - margin-bottom: 15px; - } - - > h5:last-of-type { - margin-top: 20px; - } - - > button { - margin-bottom: 10px; - text-align: start; - font-size: 16px; - } -` - -module.exports = RecentFiles diff --git a/lib/gui/app/components/file-selector/index.js b/lib/gui/app/components/file-selector/index.js deleted file mode 100644 index 36f57f0b..00000000 --- a/lib/gui/app/components/file-selector/index.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2018 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' - -/* eslint-disable jsdoc/require-example */ - -/** - * @module Etcher.Components.SVGIcon - */ - -const angular = require('angular') -const react2angular = require('react2angular').react2angular - -const MODULE_NAME = 'Etcher.Components.FileSelector' -const angularFileSelector = angular.module(MODULE_NAME, [ - require('../modal/modal') -]) - -angularFileSelector.component('fileSelector', react2angular(require('./file-selector/file-selector.jsx'))) -angularFileSelector.controller('FileSelectorController', require('./controllers/file-selector')) -angularFileSelector.service('FileSelectorService', require('./services/file-selector')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/file-selector/services/file-selector.js b/lib/gui/app/components/file-selector/services/file-selector.js deleted file mode 100644 index 0f27fbe6..00000000 --- a/lib/gui/app/components/file-selector/services/file-selector.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2018 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 (ModalService, $q) { - let modal = null - - /** - * @summary Open the file selector widget - * @function - * @public - * - * @example - * DriveSelectorService.open() - */ - this.open = () => { - modal = ModalService.open({ - name: 'file-selector', - template: require('../templates/file-selector-modal.tpl.html'), - controller: 'FileSelectorController as selector', - size: 'file-selector-modal' - }) - } - - /** - * @summary Close the file selector widget - * @function - * @public - * - * @example - * DriveSelectorService.close() - */ - this.close = () => { - if (modal) { - modal.close() - } - modal = null - } -} diff --git a/lib/gui/app/components/file-selector/styles/_file-selector.scss b/lib/gui/app/components/file-selector/styles/_file-selector.scss deleted file mode 100644 index 189a3cc3..00000000 --- a/lib/gui/app/components/file-selector/styles/_file-selector.scss +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2018 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-file-selector-modal { - width: calc(100vw - 10px); - - > .modal-content { - height: calc(100vh - 20px); - } -} diff --git a/lib/gui/app/components/file-selector/templates/file-selector-modal.tpl.html b/lib/gui/app/components/file-selector/templates/file-selector-modal.tpl.html deleted file mode 100644 index 89eb1771..00000000 --- a/lib/gui/app/components/file-selector/templates/file-selector-modal.tpl.html +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/lib/gui/app/components/finish/finish.tsx b/lib/gui/app/components/finish/finish.tsx new file mode 100644 index 00000000..e3d2a7e4 --- /dev/null +++ b/lib/gui/app/components/finish/finish.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2019 balena.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. + */ + +import * as _ from 'lodash'; +import * as React from 'react'; +import * as uuidV4 from 'uuid/v4'; + +import * as messages from '../../../../shared/messages'; +import * as flashState from '../../models/flash-state'; +import * as selectionState from '../../models/selection-state'; +import * as store from '../../models/store'; +import * as analytics from '../../modules/analytics'; +import * as updateLock from '../../modules/update-lock'; +import { open as openExternal } from '../../os/open-external/services/open-external'; +import { FlashAnother } from '../flash-another/flash-another'; +import { FlashResults } from '../flash-results/flash-results'; +import * as SVGIcon from '../svg-icon/svg-icon'; + +const restart = (options: any, $state: any) => { + const { + applicationSessionUuid, + flashingWorkflowUuid, + // @ts-ignore + } = store.getState().toJS(); + if (!options.preserveImage) { + selectionState.deselectImage(); + } + selectionState.deselectAllDrives(); + analytics.logEvent('Restart', { + ...options, + applicationSessionUuid, + flashingWorkflowUuid, + }); + + // Re-enable lock release on inactivity + updateLock.resume(); + + // Reset the flashing workflow uuid + store.dispatch({ + type: 'SET_FLASHING_WORKFLOW_UUID', + data: uuidV4(), + }); + + $state.go('main'); +}; + +const formattedErrors = () => { + const errors = _.map( + _.get(flashState.getFlashResults(), ['results', 'errors']), + error => { + return `${error.device}: ${error.message || error.code}`; + }, + ); + return errors.join('\n'); +}; + +function FinishPage({ $state }: any) { + // @ts-ignore + const results = flashState.getFlashResults().results || {}; + const progressMessage = messages.progress; + return ( +
+
+
+ + + restart(options, $state)} + > +
+ +
+
+
+ Thanks for using + + openExternal( + 'https://balena.io/etcher?ref=etcher_offline_banner', + ) + } + > + + +
+
+ made with + + by + + openExternal('https://balena.io?ref=etcher_success') + } + > + + +
+
+
+
+
+ ); +} + +export default FinishPage; diff --git a/lib/gui/app/components/drive-selector/index.js b/lib/gui/app/components/finish/index.ts similarity index 53% rename from lib/gui/app/components/drive-selector/index.js rename to lib/gui/app/components/finish/index.ts index e40e100f..25c580cb 100644 --- a/lib/gui/app/components/drive-selector/index.js +++ b/lib/gui/app/components/finish/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2019 resin.io + * Copyright 2019 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,22 @@ * limitations under the License. */ -'use strict' - /** - * @module Etcher.Components.TargetSelector + * @module Etcher.Pages.Finish */ -const angular = require('angular') -const { react2angular } = require('react2angular') +import * as angular from 'angular'; +import { react2angular } from 'react2angular'; +import FinishPage from './finish'; -const MODULE_NAME = 'Etcher.Components.TargetSelector' -const SelectTargetButton = angular.module(MODULE_NAME, []) +export const MODULE_NAME = 'Etcher.Pages.Finish'; +const Finish = angular.module(MODULE_NAME, []); -SelectTargetButton.component( - 'targetSelector', - react2angular(require('./target-selector.jsx')) -) +Finish.component('finish', react2angular(FinishPage, [], ['$state'])); -module.exports = MODULE_NAME +Finish.config(($stateProvider: any) => { + $stateProvider.state('success', { + url: '/success', + template: '', + }); +}); diff --git a/lib/gui/app/components/flash-another/flash-another.jsx b/lib/gui/app/components/flash-another/flash-another.jsx deleted file mode 100644 index 9f3fb294..00000000 --- a/lib/gui/app/components/flash-another/flash-another.jsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 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' - -// eslint-disable-next-line no-unused-vars -const React = require('react') -const PropTypes = require('prop-types') -const styled = require('styled-components').default -const { position, right } = require('styled-system') -const { BaseButton, ThemedProvider } = require('../../styled-components') - -const Div = styled.div ` - ${position} - ${right} -` - -const FlashAnother = (props) => { - return ( - -
- - Flash Another - -
-
- ) -} - -FlashAnother.propTypes = { - onClick: PropTypes.func -} - -module.exports = FlashAnother diff --git a/lib/gui/app/components/flash-another/flash-another.tsx b/lib/gui/app/components/flash-another/flash-another.tsx new file mode 100644 index 00000000..c993b559 --- /dev/null +++ b/lib/gui/app/components/flash-another/flash-another.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2019 balena.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. + */ + +import * as React from 'react'; +import styled from 'styled-components'; +import { position, right } from 'styled-system'; +import { BaseButton, ThemedProvider } from '../../styled-components'; + +const Div = styled.div` + ${position} + ${right} +`; + +export interface FlashAnotherProps { + onClick: (options: { preserveImage: boolean }) => void; +} + +export const FlashAnother = (props: FlashAnotherProps) => { + return ( + +
+ + Flash Another + +
+
+ ); +}; diff --git a/lib/gui/app/components/flash-another/index.js b/lib/gui/app/components/flash-another/index.js deleted file mode 100644 index d0545718..00000000 --- a/lib/gui/app/components/flash-another/index.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018 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.FlashAnother - */ - -const angular = require('angular') -const { react2angular } = require('react2angular') - -const MODULE_NAME = 'Etcher.Components.FlashAnother' -const FlashAnother = angular.module(MODULE_NAME, []) - -FlashAnother.component( - 'flashAnother', - react2angular(require('./flash-another.jsx')) -) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/settings/index.ts b/lib/gui/app/components/flash-another/index.ts similarity index 71% rename from lib/gui/app/components/settings/index.ts rename to lib/gui/app/components/flash-another/index.ts index ba82c00b..06626392 100644 --- a/lib/gui/app/components/settings/index.ts +++ b/lib/gui/app/components/flash-another/index.ts @@ -14,15 +14,11 @@ * limitations under the License. */ -/** - * @module Etcher.Components.FeaturedProject - */ - import * as angular from 'angular'; import { react2angular } from 'react2angular'; -import { SettingsButton } from './settings'; +import { FlashAnother } from './flash-another'; -export const MODULE_NAME = 'Etcher.Components.Settings'; -const Settings = angular.module(MODULE_NAME, []); +export const MODULE_NAME = 'Etcher.Components.FlashAnother'; +const FlashAnotherModule = angular.module(MODULE_NAME, []); -Settings.component('settings', react2angular(SettingsButton)); +FlashAnotherModule.component('flashAnother', react2angular(FlashAnother)); diff --git a/lib/gui/app/components/flash-error-modal/flash-error-modal.js b/lib/gui/app/components/flash-error-modal/flash-error-modal.js deleted file mode 100644 index b0931e99..00000000 --- a/lib/gui/app/components/flash-error-modal/flash-error-modal.js +++ /dev/null @@ -1,31 +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.FlashErrorModal - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Components.FlashErrorModal' -const FlashErrorModal = angular.module(MODULE_NAME, [ - require('../warning-modal/warning-modal') -]) - -FlashErrorModal.service('FlashErrorModalService', require('./services/flash-error-modal')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/flash-error-modal/services/flash-error-modal.js b/lib/gui/app/components/flash-error-modal/services/flash-error-modal.js deleted file mode 100644 index 5110ddff..00000000 --- a/lib/gui/app/components/flash-error-modal/services/flash-error-modal.js +++ /dev/null @@ -1,53 +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 flashState = require('../../../models/flash-state') -const selectionState = require('../../../models/selection-state') -const store = require('../../../models/store') -const analytics = require('../../../modules/analytics') - -module.exports = function (WarningModalService) { - /** - * @summary Open the flash error modal - * @function - * @public - * - * @param {String} message - flash error message - * @returns {Promise} - * - * @example - * FlashErrorModalService.show('The drive is not large enough!'); - */ - this.show = (message) => { - return WarningModalService.display({ - confirmationLabel: 'Retry', - description: message - }).then((confirmed) => { - flashState.resetState() - - if (confirmed) { - analytics.logEvent('Restart after failure', { - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - } else { - selectionState.clear() - } - }) - } -} diff --git a/lib/gui/app/components/flash-results/flash-results.jsx b/lib/gui/app/components/flash-results/flash-results.jsx deleted file mode 100644 index 29f6cbbb..00000000 --- a/lib/gui/app/components/flash-results/flash-results.jsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2018 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 React = require('react') -const PropTypes = require('prop-types') -const _ = require('lodash') -const styled = require('styled-components').default -const { position, left, top, space } = require('styled-system') -const { Underline } = require('./../../styled-components') - -const Div = styled.div ` - ${position} - ${top} - ${left} - ${space} -` - -/* eslint-disable no-inline-comments */ - -const FlashResults = (props) => { - return ( -
-
- -

Flash Complete!

-
-
- - {_.map(props.results.devices, (quantity, type) => { - return (quantity) ? ( -
- - { quantity } - { props.message[type](quantity) } -
- ) : null - })} -
-
-
- ) -} - -FlashResults.propTypes = { - results: PropTypes.object, - message: PropTypes.object, - errors: PropTypes.func -} - -module.exports = FlashResults diff --git a/lib/gui/app/components/flash-results/flash-results.tsx b/lib/gui/app/components/flash-results/flash-results.tsx new file mode 100644 index 00000000..90b27e20 --- /dev/null +++ b/lib/gui/app/components/flash-results/flash-results.tsx @@ -0,0 +1,65 @@ +/* + * Copyright 2019 balena.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. + */ + +import * as _ from 'lodash'; +import * as React from 'react'; +import styled from 'styled-components'; +import { left, position, space, top } from 'styled-system'; +import { Underline } from '../../styled-components'; + +const Div: any = styled.div` + ${position} + ${top} + ${left} + ${space} +`; + +export const FlashResults: any = ({ + errors, + results, + message, +}: { + errors: () => string; + results: any; + message: any; +}) => { + return ( +
+
+ +

Flash Complete!

+
+
+ + {_.map(results.devices, (quantity, type) => { + return quantity ? ( +
+ + {quantity} + + {message[type](quantity)} + +
+ ) : null; + })} +
+
+
+ ); +}; diff --git a/lib/gui/app/components/flash-results/index.js b/lib/gui/app/components/flash-results/index.ts similarity index 63% rename from lib/gui/app/components/flash-results/index.js rename to lib/gui/app/components/flash-results/index.ts index 325f3eea..d5ecb0b4 100644 --- a/lib/gui/app/components/flash-results/index.js +++ b/lib/gui/app/components/flash-results/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2019 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,15 @@ * limitations under the License. */ -'use strict' - /** * @module Etcher.Components.FlashResults */ -const angular = require('angular') -const { react2angular } = require('react2angular') +import * as angular from 'angular'; +import { react2angular } from 'react2angular'; +import { FlashResults } from './flash-results'; -const MODULE_NAME = 'Etcher.Components.FlashResults' -const FlashResults = angular.module(MODULE_NAME, []) +export const MODULE_NAME = 'Etcher.Components.FlashResults'; +const FlashResultsModule = angular.module(MODULE_NAME, []); -FlashResults.component( - 'flashResults', - react2angular(require('./flash-results.jsx')) -) - -module.exports = MODULE_NAME +FlashResultsModule.component('flashResults', react2angular(FlashResults)); diff --git a/lib/gui/app/components/image-selector/image-selector.jsx b/lib/gui/app/components/image-selector/image-selector.jsx index d19692cf..a190eb80 100644 --- a/lib/gui/app/components/image-selector/image-selector.jsx +++ b/lib/gui/app/components/image-selector/image-selector.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,23 @@ 'use strict' -/* eslint-disable no-unused-vars */ -const React = require('react') +const Bluebird = require('bluebird') +const sdk = require('etcher-sdk') +const _ = require('lodash') +const path = require('path') const propTypes = require('prop-types') - -const middleEllipsis = require('./../../utils/middle-ellipsis') - -const shared = require('./../../../../shared/units') +const React = require('react') +const Dropzone = require('react-dropzone').default +const errors = require('../../../../shared/errors') +const messages = require('../../../../shared/messages') +const supportedFormats = require('../../../../shared/supported-formats') +const shared = require('../../../../shared/units') +const selectionState = require('../../models/selection-state') +const store = require('../../models/store') +const analytics = require('../../modules/analytics') +const exceptionReporter = require('../../modules/exception-reporter') +const osDialog = require('../../os/dialog') +const { replaceWindowsNetworkDriveLetter } = require('../../os/windows-network-drives') const { StepButton, StepNameButton, @@ -30,69 +40,365 @@ const { Footer, Underline, DetailsText, - ChangeButton, - ThemedProvider -} = require('./../../styled-components') + ChangeButton +} = require('../../styled-components') +const { + Modal +} = require('rendition') +const middleEllipsis = require('../../utils/middle-ellipsis') +const SVGIcon = require('../svg-icon/svg-icon.jsx') +const { default: styled } = require('styled-components') + +// TODO move these styles to rendition +const ModalText = styled.p ` + a { + color: rgb(0, 174, 239); + + &:hover { + color: rgb(0, 139, 191); + } + } +` + +/** + * @summary Main supported extensions + * @constant + * @type {String[]} + * @public + */ +const mainSupportedExtensions = _.intersection([ + 'img', + 'iso', + 'zip' +], supportedFormats.getAllExtensions()) + +/** + * @summary Extra supported extensions + * @constant + * @type {String[]} + * @public + */ +const extraSupportedExtensions = _.difference( + supportedFormats.getAllExtensions(), + mainSupportedExtensions +).sort() + +const getState = () => { + return { + hasImage: selectionState.hasImage(), + imageName: selectionState.getImageName(), + imageSize: selectionState.getImageSize() + } +} + +class ImageSelector extends React.Component { + constructor (props) { + super(props) + + this.state = { + ...getState(), + warning: null, + showImageDetails: false + } + + this.openImageSelector = this.openImageSelector.bind(this) + this.reselectImage = this.reselectImage.bind(this) + this.handleOnDrop = this.handleOnDrop.bind(this) + this.showSelectedImageDetails = this.showSelectedImageDetails.bind(this) + } + + componentDidMount () { + this.unsubscribe = store.observe(() => { + this.setState(getState()) + }) + } + + componentWillUnmount () { + this.unsubscribe() + } + + reselectImage () { + analytics.logEvent('Reselect image', { + previousImage: selectionState.getImage(), + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + + this.openImageSelector() + } + + selectImage (image) { + if (!supportedFormats.isSupportedImage(image.path)) { + const invalidImageError = errors.createUserError({ + title: 'Invalid image', + description: messages.error.invalidImage(image) + }) + + osDialog.showError(invalidImageError) + analytics.logEvent('Invalid image', _.merge({ + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }, image)) + return + } + + Bluebird.try(() => { + let message = null + let title = null + + if (supportedFormats.looksLikeWindowsImage(image.path)) { + analytics.logEvent('Possibly Windows image', { + image, + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + message = messages.warning.looksLikeWindowsImage() + title = 'Possible Windows image detected' + } else if (!image.hasMBR) { + analytics.logEvent('Missing partition table', { + image, + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + title = 'Missing partition table' + message = messages.warning.missingPartitionTable() + } + + if (message) { + this.setState({ + warning: { + message, + title + } + }) + + return + } + + return false + }).then(() => { + selectionState.selectImage(image) + + // An easy way so we can quickly identify if we're making use of + // certain features without printing pages of text to DevTools. + image.logo = Boolean(image.logo) + image.blockMap = Boolean(image.blockMap) + + return analytics.logEvent('Select image', { + image, + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + }).catch(exceptionReporter.report) + } + + async selectImageByPath (imagePath) { + try { + // eslint-disable-next-line no-param-reassign + imagePath = await replaceWindowsNetworkDriveLetter(imagePath) + } catch (error) { + analytics.logException(error) + } + if (!supportedFormats.isSupportedImage(imagePath)) { + const invalidImageError = errors.createUserError({ + title: 'Invalid image', + description: messages.error.invalidImage(imagePath) + }) + + osDialog.showError(invalidImageError) + analytics.logEvent('Invalid image', { path: imagePath }) + return + } + + const source = new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read) + try { + const innerSource = await source.getInnerSource() + const metadata = await innerSource.getMetadata() + const partitionTable = await innerSource.getPartitionTable() + if (partitionTable) { + metadata.hasMBR = true + metadata.partitions = partitionTable.partitions + } + metadata.path = imagePath + // eslint-disable-next-line no-magic-numbers + metadata.extension = path.extname(imagePath).slice(1) + this.selectImage(metadata) + } catch (error) { + const imageError = errors.createUserError({ + title: 'Error opening image', + description: messages.error.openImage(path.basename(imagePath), error.message) + }) + osDialog.showError(imageError) + analytics.logException(error) + } finally { + try { + await source.close() + } catch (error) { + // Noop + } + } + } + + /** + * @summary Open image selector + * @function + * @public + * + * @example + * ImageSelectionController.openImageSelector(); + */ + openImageSelector () { + analytics.logEvent('Open image selector', { + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + + osDialog.selectImage().then((imagePath) => { + // Avoid analytics and selection state changes + // if no file was resolved from the dialog. + if (!imagePath) { + analytics.logEvent('Image selector closed', { + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) + return + } + + this.selectImageByPath(imagePath) + }).catch(exceptionReporter.report) + } + + handleOnDrop (acceptedFiles) { + const [ file ] = acceptedFiles + + if (file) { + this.selectImageByPath(file.path) + } + } + + showSelectedImageDetails () { + analytics.logEvent('Show selected image tooltip', { + imagePath: selectionState.getImagePath(), + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid, + applicationSessionUuid: store.getState().toJS().applicationSessionUuid + }) + + this.setState({ + showImageDetails: true + }) + } + + // TODO add a visual change when dragging a file over the selector + render () { + const { + flashing + } = this.props + const { + showImageDetails + } = this.state + + const hasImage = selectionState.hasImage() + + const imageBasename = hasImage ? path.basename(selectionState.getImagePath()) : '' + const imageName = selectionState.getImageName() + const imageSize = selectionState.getImageSize() -const SelectImageButton = (props) => { - if (props.hasImage) { return ( - - - {/* eslint-disable no-magic-numbers */} - { middleEllipsis(props.imageName || props.imageBasename, 20) } - - { !props.flashing && - +
+ + {({ getRootProps, getInputProps }) => ( +
+ + +
+ )} +
+ +
+ {hasImage ? ( + + + {/* eslint-disable no-magic-numbers */} + { middleEllipsis(imageName || imageBasename, 20) } + + { !flashing && + + Change + + } + + {shared.bytesToClosestUnit(imageSize)} + + + ) : ( + + + Select image + +
+ { mainSupportedExtensions.join(', ') }, and{' '} + + many more + +
+
+ )} +
+
+ + {Boolean(this.state.warning) && ( + + + {' '} + {this.state.warning.title} + + )} + action='Continue' + cancel={() => { + this.setState({ warning: null }) + this.reselectImage() + }} + done={() => { + this.setState({ warning: null }) + }} + primaryButtonProps={{ warning: true, primary: false }} > - Change -
- } - - {shared.bytesToClosestUnit(props.imageSize)} - -
+ + + )} + + {showImageDetails && ( + { + this.setState({ showImageDetails: false }) + }} + > + {selectionState.getImagePath()} + + )} + ) } - return ( - - - - Select image - -
- { props.mainSupportedExtensions.join(', ') }, and{' '} - - many more - -
-
-
- ) } -SelectImageButton.propTypes = { - openImageSelector: propTypes.func, - mainSupportedExtensions: propTypes.array, - extraSupportedExtensions: propTypes.array, - hasImage: propTypes.bool, - showSelectedImageDetails: propTypes.func, - imageName: propTypes.string, - imageBasename: propTypes.string, - reselectImage: propTypes.func, - flashing: propTypes.bool, - imageSize: propTypes.number +ImageSelector.propTypes = { + flashing: propTypes.bool } -module.exports = SelectImageButton +module.exports = ImageSelector diff --git a/lib/gui/app/components/image-selector/index.js b/lib/gui/app/components/image-selector/index.js deleted file mode 100644 index e8e3f25b..00000000 --- a/lib/gui/app/components/image-selector/index.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018 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.ImageSelector - */ - -const angular = require('angular') -const { react2angular } = require('react2angular') - -const MODULE_NAME = 'Etcher.Components.ImageSelector' -const SelectImageButton = angular.module(MODULE_NAME, []) - -SelectImageButton.component( - 'imageSelector', - react2angular(require('./image-selector.jsx')) -) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/modal/modal.js b/lib/gui/app/components/modal/modal.js index 1df5c45d..3d905935 100644 --- a/lib/gui/app/components/modal/modal.js +++ b/lib/gui/app/components/modal/modal.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/modal/services/modal.js b/lib/gui/app/components/modal/services/modal.js index 192f2556..4060d9dd 100644 --- a/lib/gui/app/components/modal/services/modal.js +++ b/lib/gui/app/components/modal/services/modal.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/modal/styles/_modal.scss b/lib/gui/app/components/modal/styles/_modal.scss index 5d4c687f..cd8b5c73 100644 --- a/lib/gui/app/components/modal/styles/_modal.scss +++ b/lib/gui/app/components/modal/styles/_modal.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/progress-button/index.js b/lib/gui/app/components/progress-button/index.js deleted file mode 100644 index 7d47eb53..00000000 --- a/lib/gui/app/components/progress-button/index.js +++ /dev/null @@ -1,34 +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.ProgressButton - */ - -const angular = require('angular') -const { react2angular } = require('react2angular') - -const MODULE_NAME = 'Etcher.Components.ProgressButton' -const ProgressButton = angular.module(MODULE_NAME, []) - -ProgressButton.component( - 'progressButton', - react2angular(require('./progress-button.jsx')) -) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/progress-button/progress-button.jsx b/lib/gui/app/components/progress-button/progress-button.jsx index 2eab3e3b..27481ef9 100644 --- a/lib/gui/app/components/progress-button/progress-button.jsx +++ b/lib/gui/app/components/progress-button/progress-button.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ const { keyframes } = require('styled-components') -const { ProgressBar, Provider } = require('rendition') +const { ProgressBar } = require('rendition') const { colors } = require('./../../theme') const { StepButton, StepSelection } = require('./../../styled-components') @@ -78,7 +78,7 @@ const FlashProgressBarValidating = styled(FlashProgressBar) ` // Notice that we add 0.01 to certain gradient stop positions. // That workarounds a Chrome rendering issue where diagonal // lines look spiky. - // See https://github.com/resin-io/etcher/issues/472 + // See https://github.com/balena-io/etcher/issues/472 background-image: -webkit-gradient(linear, 0 0, 100% 100%, color-stop(0.25, ${progressButtonStripesForegroundColor}), @@ -105,46 +105,40 @@ class ProgressButton extends React.Component { if (this.props.active) { if (this.props.striped) { return ( - - - - { this.props.label } - - - - ) - } - - return ( - - { this.props.label } - + - + ) + } + + return ( + + + { this.props.label } + + ) } return ( - - - - {this.props.label} - - - + + + {this.props.label} + + ) } } diff --git a/lib/gui/app/components/reduced-flashing-infos/index.js b/lib/gui/app/components/reduced-flashing-infos/index.js deleted file mode 100644 index 4b1446e3..00000000 --- a/lib/gui/app/components/reduced-flashing-infos/index.js +++ /dev/null @@ -1,34 +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.ReducedFlashingInfos - */ - -const angular = require('angular') -const { react2angular } = require('react2angular') - -const MODULE_NAME = 'Etcher.Components.ReducedFlashingInfos' -const ReducedFlashingInfos = angular.module(MODULE_NAME, []) - -ReducedFlashingInfos.component( - 'reducedFlashingInfos', - react2angular(require('./reduced-flashing-infos.jsx')) -) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx index bbcab727..0fdd161c 100644 --- a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx +++ b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/safe-webview/index.js b/lib/gui/app/components/safe-webview/index.js index b929587e..fc7531f4 100644 --- a/lib/gui/app/components/safe-webview/index.js +++ b/lib/gui/app/components/safe-webview/index.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/safe-webview/safe-webview.jsx b/lib/gui/app/components/safe-webview/safe-webview.jsx index 68029356..39ea9c6c 100644 --- a/lib/gui/app/components/safe-webview/safe-webview.jsx +++ b/lib/gui/app/components/safe-webview/safe-webview.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,8 +108,8 @@ class SafeWebview extends react.PureComponent { this.didGetResponseDetails = _.bind(this.didGetResponseDetails, this) const logWebViewMessage = (event) => { - console.log('Message from SafeWebview:', event.message); - }; + console.log('Message from SafeWebview:', event.message) + } this.eventTuples = [ [ 'did-fail-load', this.didFailLoad ], @@ -174,6 +174,9 @@ class SafeWebview extends react.PureComponent { this.setState({ shouldShow: false }) + if (this.props.onWebviewShow) { + this.props.onWebviewShow(false) + } } /** diff --git a/lib/gui/app/components/settings/settings.tsx b/lib/gui/app/components/settings/settings.tsx index 2aac757b..d2f2110c 100644 --- a/lib/gui/app/components/settings/settings.tsx +++ b/lib/gui/app/components/settings/settings.tsx @@ -15,46 +15,22 @@ */ import { faGithub } from '@fortawesome/free-brands-svg-icons'; -import { faCog } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import * as _ from 'lodash'; import * as os from 'os'; -import * as propTypes from 'prop-types'; import * as React from 'react'; -import { Badge, Button, Checkbox, Modal, Provider } from 'rendition'; +import { Badge, Checkbox, Modal } from 'rendition'; import styled from 'styled-components'; -import packageJSON = require('../../../../../package.json'); + +import { version } from '../../../../../package.json'; import * as settings from '../../models/settings'; import * as store from '../../models/store'; import * as analytics from '../../modules/analytics'; import { open as openExternal } from '../../os/open-external/services/open-external'; -import { colors } from '../../theme'; const { useState } = React; const platform = os.platform(); -export const SettingsButton = () => { - const [hideModal, setHideModal] = useState(true); - - return ( - - - {hideModal ? null : ( - setHideModal(!value)} /> - )} - - ); -}; - -SettingsButton.propTypes = {}; - interface WarningModalProps { message: string; confirmLabel: string; @@ -219,7 +195,7 @@ export const SettingsModal: any = styled( ) } > - {packageJSON.version} + {version} @@ -249,7 +225,3 @@ export const SettingsModal: any = styled( justify-content: center; } `; - -SettingsModal.propTypes = { - toggleModal: propTypes.func, -}; diff --git a/lib/gui/app/components/svg-icon/index.js b/lib/gui/app/components/svg-icon/index.js index 5ba29955..deeac4f0 100644 --- a/lib/gui/app/components/svg-icon/index.js +++ b/lib/gui/app/components/svg-icon/index.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/svg-icon/svg-icon.jsx b/lib/gui/app/components/svg-icon/svg-icon.jsx index 5846787a..50c99bd9 100644 --- a/lib/gui/app/components/svg-icon/svg-icon.jsx +++ b/lib/gui/app/components/svg-icon/svg-icon.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/components/tooltip-modal/controllers/tooltip-modal.js b/lib/gui/app/components/tooltip-modal/controllers/tooltip-modal.js deleted file mode 100644 index fe3e929d..00000000 --- a/lib/gui/app/components/tooltip-modal/controllers/tooltip-modal.js +++ /dev/null @@ -1,38 +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.exports = function ($uibModalInstance, tooltipData) { - /** - * @summary Tooltip data - * @type {Object} - * @public - */ - this.data = tooltipData - - /** - * @summary Close the modal - * @function - * @public - * - * @example - * TooltipModalController.closeModal(); - */ - this.closeModal = () => { - $uibModalInstance.dismiss() - } -} diff --git a/lib/gui/app/components/tooltip-modal/services/tooltip-modal.js b/lib/gui/app/components/tooltip-modal/services/tooltip-modal.js deleted file mode 100644 index dcc32b27..00000000 --- a/lib/gui/app/components/tooltip-modal/services/tooltip-modal.js +++ /dev/null @@ -1,49 +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') - -module.exports = function (ModalService) { - /** - * @summary Open the tooltip modal - * @function - * @public - * - * @param {Object} options - tooltip options - * @param {String} options.title - tooltip title - * @param {String} options.message - tooltip message - * @returns {Promise} - * - * @example - * TooltipModalService.show({ - * title: 'Important tooltip', - * message: 'Tooltip contents' - * }); - */ - this.show = (options) => { - return ModalService.open({ - name: 'tooltip', - template: require('../templates/tooltip-modal.tpl.html'), - controller: 'TooltipModalController as modal', - size: 'tooltip-modal', - resolve: { - tooltipData: _.constant(options) - } - }).result - } -} diff --git a/lib/gui/app/components/tooltip-modal/styles/_tooltip-modal.scss b/lib/gui/app/components/tooltip-modal/styles/_tooltip-modal.scss deleted file mode 100644 index 532491e1..00000000 --- a/lib/gui/app/components/tooltip-modal/styles/_tooltip-modal.scss +++ /dev/null @@ -1,23 +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-tooltip-modal .modal-body { - text-align: center; - margin: 15px; - color: $palette-theme-light-foreground; - background-color: darken($palette-theme-light-background, 5%); - word-wrap: break-word; -} diff --git a/lib/gui/app/components/tooltip-modal/templates/tooltip-modal.tpl.html b/lib/gui/app/components/tooltip-modal/templates/tooltip-modal.tpl.html deleted file mode 100644 index 226a3dd7..00000000 --- a/lib/gui/app/components/tooltip-modal/templates/tooltip-modal.tpl.html +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/lib/gui/app/components/tooltip-modal/tooltip-modal.js b/lib/gui/app/components/tooltip-modal/tooltip-modal.js deleted file mode 100644 index b99f60f2..00000000 --- a/lib/gui/app/components/tooltip-modal/tooltip-modal.js +++ /dev/null @@ -1,32 +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.TooltipModal - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Components.TooltipModal' -const TooltipModal = angular.module(MODULE_NAME, [ - require('../modal/modal') -]) - -TooltipModal.controller('TooltipModalController', require('./controllers/tooltip-modal')) -TooltipModal.service('TooltipModalService', require('./services/tooltip-modal')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/components/warning-modal/controllers/warning-modal.js b/lib/gui/app/components/warning-modal/controllers/warning-modal.js deleted file mode 100644 index 286d4d89..00000000 --- a/lib/gui/app/components/warning-modal/controllers/warning-modal.js +++ /dev/null @@ -1,50 +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.exports = function ($uibModalInstance, options) { - /** - * @summary Modal options - * @type {Object} - * @public - */ - this.options = options - - /** - * @summary Reject the warning prompt - * @function - * @public - * - * @example - * WarningModalController.reject(); - */ - this.reject = () => { - $uibModalInstance.close(false) - } - - /** - * @summary Accept the warning prompt - * @function - * @public - * - * @example - * WarningModalController.accept(); - */ - this.accept = () => { - $uibModalInstance.close(true) - } -} diff --git a/lib/gui/app/components/warning-modal/services/warning-modal.js b/lib/gui/app/components/warning-modal/services/warning-modal.js deleted file mode 100644 index dac49af9..00000000 --- a/lib/gui/app/components/warning-modal/services/warning-modal.js +++ /dev/null @@ -1,52 +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') - -module.exports = function ($sce, ModalService) { - /** - * @summary Display the warning modal - * @function - * @public - * - * @param {Object} options - options - * @param {String} options.description - danger message - * @param {String} options.confirmationLabel - confirmation button text - * @param {String} options.rejectionLabel - rejection button text - * @fulfil {Boolean} - whether the user accepted or rejected the warning - * @returns {Promise} - * - * @example - * WarningModalService.display({ - * description: 'Don\'t do this!', - * confirmationLabel: 'Yes, continue!' - * }); - */ - this.display = (options = {}) => { - options.description = $sce.trustAsHtml(options.description) - return ModalService.open({ - name: 'warning', - template: require('../templates/warning-modal.tpl.html'), - controller: 'WarningModalController as modal', - size: 'warning-modal', - resolve: { - options: _.constant(options) - } - }).result - } -} diff --git a/lib/gui/app/components/warning-modal/styles/_warning-modal.scss b/lib/gui/app/components/warning-modal/styles/_warning-modal.scss deleted file mode 100644 index 41d2f874..00000000 --- a/lib/gui/app/components/warning-modal/styles/_warning-modal.scss +++ /dev/null @@ -1,28 +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-warning-modal .modal-content { - width: 350px; -} - -.modal-warning-modal .modal-title .glyphicon { - color: $palette-theme-danger-background; -} - -.modal-warning-modal .modal-body { - max-height: 200px; - overflow-y: auto; -} diff --git a/lib/gui/app/components/warning-modal/templates/warning-modal.tpl.html b/lib/gui/app/components/warning-modal/templates/warning-modal.tpl.html deleted file mode 100644 index fdbdfef5..00000000 --- a/lib/gui/app/components/warning-modal/templates/warning-modal.tpl.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/lib/gui/app/components/warning-modal/warning-modal.js b/lib/gui/app/components/warning-modal/warning-modal.js deleted file mode 100644 index 6ad3edff..00000000 --- a/lib/gui/app/components/warning-modal/warning-modal.js +++ /dev/null @@ -1,32 +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.WarningModal - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Components.WarningModal' -const WarningModal = angular.module(MODULE_NAME, [ - require('../modal/modal') -]) - -WarningModal.controller('WarningModalController', require('./controllers/warning-modal')) -WarningModal.service('WarningModalService', require('./services/warning-modal')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/index.html b/lib/gui/app/index.html index 89b4f880..6706b418 100644 --- a/lib/gui/app/index.html +++ b/lib/gui/app/index.html @@ -10,28 +10,8 @@ -
- - - - - -
- -
+
+ diff --git a/lib/gui/app/models/available-drives.js b/lib/gui/app/models/available-drives.js index 78e4b525..85549b45 100644 --- a/lib/gui/app/models/available-drives.js +++ b/lib/gui/app/models/available-drives.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/models/files.js b/lib/gui/app/models/files.js index ceb0a343..6eae6ddb 100644 --- a/lib/gui/app/models/files.js +++ b/lib/gui/app/models/files.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/models/flash-state.js b/lib/gui/app/models/flash-state.js index 493d95dc..9bd4ee16 100644 --- a/lib/gui/app/models/flash-state.js +++ b/lib/gui/app/models/flash-state.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/models/local-settings.js b/lib/gui/app/models/local-settings.js index 94c0387b..7ad08e53 100644 --- a/lib/gui/app/models/local-settings.js +++ b/lib/gui/app/models/local-settings.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/models/selection-state.js b/lib/gui/app/models/selection-state.js index 180e209f..0e32f288 100644 --- a/lib/gui/app/models/selection-state.js +++ b/lib/gui/app/models/selection-state.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/models/settings.js b/lib/gui/app/models/settings.js index 1002a407..486dc694 100644 --- a/lib/gui/app/models/settings.js +++ b/lib/gui/app/models/settings.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/models/storage.js b/lib/gui/app/models/storage.js index 05a7b6d7..655fd048 100644 --- a/lib/gui/app/models/storage.js +++ b/lib/gui/app/models/storage.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/models/store.js b/lib/gui/app/models/store.js index 4a9f2247..ab03f273 100644 --- a/lib/gui/app/models/store.js +++ b/lib/gui/app/models/store.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/modules/analytics.js b/lib/gui/app/modules/analytics.js index 40a648f2..bc6e5737 100644 --- a/lib/gui/app/modules/analytics.js +++ b/lib/gui/app/modules/analytics.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/modules/drive-scanner.js b/lib/gui/app/modules/drive-scanner.js index 614349e6..b2e577f5 100644 --- a/lib/gui/app/modules/drive-scanner.js +++ b/lib/gui/app/modules/drive-scanner.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/modules/exception-reporter.js b/lib/gui/app/modules/exception-reporter.js index eb95801c..b2a61dd0 100644 --- a/lib/gui/app/modules/exception-reporter.js +++ b/lib/gui/app/modules/exception-reporter.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/modules/image-writer.js b/lib/gui/app/modules/image-writer.js index 644a6b88..cc7535e7 100644 --- a/lib/gui/app/modules/image-writer.js +++ b/lib/gui/app/modules/image-writer.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ const THREADS_PER_CPU = 16 * @param {Object} analyticsData - analytics object * * @example - * handleErrorLogging({ code: 'EUNPLUGGED' }, { image: 'resin.img' }) + * handleErrorLogging({ code: 'EUNPLUGGED' }, { image: 'balena.img' }) */ const handleErrorLogging = (error, analyticsData) => { const eventData = _.assign({ diff --git a/lib/gui/app/modules/progress-status.js b/lib/gui/app/modules/progress-status.js index de0875fa..9aab2fec 100644 --- a/lib/gui/app/modules/progress-status.js +++ b/lib/gui/app/modules/progress-status.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/modules/update-lock.js b/lib/gui/app/modules/update-lock.js index 43fb1fb6..01c4b41b 100644 --- a/lib/gui/app/modules/update-lock.js +++ b/lib/gui/app/modules/update-lock.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,12 +35,12 @@ const INTERACTION_TIMEOUT_MS = settings.has('interactionTimeout') : 5 * 60 * 1000 /** - * Resin Update Lock + * Balena Update Lock * @class */ class UpdateLock extends EventEmitter { /** - * @summary Resin Update Lock + * @summary Balena Update Lock * @example * new UpdateLock() */ @@ -55,7 +55,7 @@ class UpdateLock extends EventEmitter { } /** - * @summary Inactivity event handler, releases the resin update lock on inactivity + * @summary Inactivity event handler, releases the balena update lock on inactivity * @private * @example * this.on('inactive', onInactive) diff --git a/lib/gui/app/os/dialog.js b/lib/gui/app/os/dialog.js index 97ca1e27..1a81855b 100644 --- a/lib/gui/app/os/dialog.js +++ b/lib/gui/app/os/dialog.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/os/dropzone/directives/dropzone.js b/lib/gui/app/os/dropzone/directives/dropzone.js deleted file mode 100644 index 4799089b..00000000 --- a/lib/gui/app/os/dropzone/directives/dropzone.js +++ /dev/null @@ -1,74 +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') - -/** - * @summary Dropzone directive - * @function - * @public - * - * @description - * This directive provides an attribute to detect a file - * being dropped into the element. - * - * @param {Object} $timeout - Angular's timeout wrapper - * @returns {Object} directive - * - * @example - *
Drag a file here
- */ -module.exports = ($timeout) => { - return { - restrict: 'A', - scope: { - osDropzone: '&' - }, - link: (scope, $element) => { - const domElement = _.first($element) - - // See https://github.com/electron/electron/blob/master/docs/api/file-object.md - - // We're not interested in these events - domElement.ondragover = _.constant(false) - domElement.ondragleave = _.constant(false) - domElement.ondragend = _.constant(false) - - domElement.ondrop = (event) => { - event.preventDefault() - - if (event.dataTransfer.files.length) { - const filename = _.first(event.dataTransfer.files).path - - // Safely bring this to the world of Angular - $timeout(() => { - scope.osDropzone({ - - // Pass the filename as a named - // parameter called `$file` - $file: filename - - }) - }) - } - - return false - } - } - } -} diff --git a/lib/gui/app/os/dropzone/dropzone.js b/lib/gui/app/os/dropzone/dropzone.js deleted file mode 100644 index 17c7e98f..00000000 --- a/lib/gui/app/os/dropzone/dropzone.js +++ /dev/null @@ -1,28 +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.OS.Dropzone - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.OS.Dropzone' -const OSDropzone = angular.module(MODULE_NAME, []) -OSDropzone.directive('osDropzone', require('./directives/dropzone')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/os/notification.js b/lib/gui/app/os/notification.js index c85c589d..757866dd 100644 --- a/lib/gui/app/os/notification.js +++ b/lib/gui/app/os/notification.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/os/open-external/directives/open-external.js b/lib/gui/app/os/open-external/directives/open-external.js deleted file mode 100644 index 7149be98..00000000 --- a/lib/gui/app/os/open-external/directives/open-external.js +++ /dev/null @@ -1,61 +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') - -/** - * @summary OsOpenExternal directive - * @function - * @public - * - * @description - * This directive provides an attribute to open an external - * resource with the default operating system action. - * - * @param {Object} OSOpenExternalService - OSOpenExternalService - * @returns {Object} directive - * - * @example - * - */ -module.exports = (OSOpenExternalService) => { - return { - restrict: 'A', - scope: false, - link: (scope, element, attributes) => { - // This directive might be added to elements - // other than buttons. - element.css('cursor', 'pointer') - - element.on('click', () => { - OSOpenExternalService.open(attributes.osOpenExternal) - }) - - const ENTER_KEY = 13 - const SPACE_KEY = 32 - element.on('keypress', (event) => { - if (_.includes([ ENTER_KEY, SPACE_KEY ], event.keyCode)) { - // Don't spam the user with several tabs if the key is being held - if (!event.repeat) { - OSOpenExternalService.open(attributes.osOpenExternal) - } - } - }) - } - } -} diff --git a/lib/gui/app/os/open-external/open-external.js b/lib/gui/app/os/open-external/open-external.js deleted file mode 100644 index 2ad4b214..00000000 --- a/lib/gui/app/os/open-external/open-external.js +++ /dev/null @@ -1,46 +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.OS.OpenExternal - */ - -const angular = require('angular') -const url = require('url') - -const MODULE_NAME = 'Etcher.OS.OpenExternal' -const OSOpenExternal = angular.module(MODULE_NAME, []) -OSOpenExternal.service('OSOpenExternalService', require('./services/open-external')) -OSOpenExternal.directive('osOpenExternal', require('./directives/open-external')) - -OSOpenExternal.run((OSOpenExternalService) => { - document.addEventListener('click', (event) => { - const target = event.target - if (target.tagName === 'A' && angular.isDefined(target.href)) { - // Electron interprets relative URLs as being relative to the - // current loaded URL (with `webContents.loadURL`) and expands - // them to the corresponding absolute URL. If it's a `file://` - // URL, we don't want it opened in an external browser. - if (url.parse(target.href).protocol !== 'file:') { - OSOpenExternalService.open(target.href) - } - } - }) -}) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/os/open-external/services/open-external.js b/lib/gui/app/os/open-external/services/open-external.js index ec2fcaab..b4b55cac 100644 --- a/lib/gui/app/os/open-external/services/open-external.js +++ b/lib/gui/app/os/open-external/services/open-external.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/os/window-progress.js b/lib/gui/app/os/window-progress.js index c92d8137..915cc9fe 100644 --- a/lib/gui/app/os/window-progress.js +++ b/lib/gui/app/os/window-progress.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/os/windows-network-drives.js b/lib/gui/app/os/windows-network-drives.js index b7dfe1da..700fbf8b 100755 --- a/lib/gui/app/os/windows-network-drives.js +++ b/lib/gui/app/os/windows-network-drives.js @@ -1,5 +1,5 @@ /* - * Copyright 2019 resin.io + * Copyright 2019 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/pages/finish/controllers/finish.js b/lib/gui/app/pages/finish/controllers/finish.js deleted file mode 100644 index d072d8a9..00000000 --- a/lib/gui/app/pages/finish/controllers/finish.js +++ /dev/null @@ -1,98 +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 uuidV4 = require('uuid/v4') -const store = require('../../../models/store') -const settings = require('../../../models/settings') -const flashState = require('../../../models/flash-state') -const selectionState = require('../../../models/selection-state') -const analytics = require('../../../modules/analytics') -const updateLock = require('../../../modules/update-lock') -const messages = require('../../../../../shared/messages') - -module.exports = function ($state) { - /** - * @summary Settings model - * @type {Object} - * @public - */ - this.settings = settings - - /** - * @summary Flash state - * @type {Object} - * @public - */ - this.flash = flashState - - this.progressMessage = messages.progress - - this.results = this.flash.getFlashResults().results || {} - - /** - * @summary Restart the flashing process - * @function - * @public - * - * @param {Object} [options] - options - * @param {Boolean} [options.preserveImage=false] - preserve image - * - * @example - * FinishController.restart({ preserveImage: true }); - */ - this.restart = (options) => { - if (!options.preserveImage) { - selectionState.deselectImage() - } - selectionState.deselectAllDrives() - analytics.logEvent('Restart', _.assign({ - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }, options)) - - // Re-enable lock release on inactivity - updateLock.resume() - - // Reset the flashing workflow uuid - store.dispatch({ - type: 'SET_FLASHING_WORKFLOW_UUID', - data: uuidV4() - }) - - $state.go('main') - } - - /** - * @summary Format the result errors with newlines - * @function - * @public - * - * @returns {String} formatted errors - * - * @example - * const errors = FinishController.formattedErrors() - * console.log(errors) - */ - this.formattedErrors = () => { - const errors = _.map(_.get(flashState.getFlashResults(), [ 'results', 'errors' ]), (error) => { - return `${error.device}: ${error.message || error.code}` - }) - return errors.join('\n') - } -} diff --git a/lib/gui/app/pages/finish/finish.js b/lib/gui/app/pages/finish/finish.js deleted file mode 100644 index abb160af..00000000 --- a/lib/gui/app/pages/finish/finish.js +++ /dev/null @@ -1,46 +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' - -/** - * The finish page represents the application state where - * the the flash/validation has completed. - * - * Its purpose is to display success or failure information, - * as well as the "next steps". - * - * @module Etcher.Pages.Finish - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Pages.Finish' -const FinishPage = angular.module(MODULE_NAME, [ - require('angular-ui-router') -]) - -FinishPage.controller('FinishController', require('./controllers/finish')) - -FinishPage.config(($stateProvider) => { - $stateProvider - .state('success', { - url: '/success', - controller: 'FinishController as finish', - template: require('./templates/success.tpl.html') - }) -}) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/pages/finish/styles/_finish.scss b/lib/gui/app/pages/finish/styles/_finish.scss index f6748964..0b7e84d2 100644 --- a/lib/gui/app/pages/finish/styles/_finish.scss +++ b/lib/gui/app/pages/finish/styles/_finish.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,11 @@ */ .page-finish { - margin-top: -15px; - flex: 1; + margin-top: 60px; +} - .col-xs-5.inline-flex.items-baseline > span, .col-xs-5.inline-flex.items-baseline > div { - margin-bottom: -10px; - } +.col-xs-5.inline-flex.items-baseline > span, .col-xs-5.inline-flex.items-baseline > div { + margin-bottom: -10px; } .page-finish .button-label { diff --git a/lib/gui/app/pages/finish/templates/success.tpl.html b/lib/gui/app/pages/finish/templates/success.tpl.html deleted file mode 100644 index 152aa15d..00000000 --- a/lib/gui/app/pages/finish/templates/success.tpl.html +++ /dev/null @@ -1,45 +0,0 @@ -
-
-
- - - - - -
- -
-
-
Thanks for using - - - - -
- -
-
-
-
diff --git a/lib/gui/app/pages/main/DriveSelector.tsx b/lib/gui/app/pages/main/DriveSelector.tsx new file mode 100644 index 00000000..76cfdfbd --- /dev/null +++ b/lib/gui/app/pages/main/DriveSelector.tsx @@ -0,0 +1,149 @@ +/* + * Copyright 2016 balena.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. + */ + +import * as _ from 'lodash'; +import * as propTypes from 'prop-types'; +import * as React from 'react'; +import styled from 'styled-components'; +import * as driveConstraints from '../../../../shared/drive-constraints'; +import * as utils from '../../../../shared/utils'; +import * as DriveSelectorModal from '../../components/drive-selector/DriveSelectorModal.jsx'; +import * as TargetSelector from '../../components/drive-selector/target-selector.jsx'; +import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx'; +import * as selectionState from '../../models/selection-state'; +import * as settings from '../../models/settings'; +import * as store from '../../models/store'; +import * as analytics from '../../modules/analytics'; + +const StepBorder = styled.div<{ + disabled: boolean; + left?: boolean; + right?: boolean; +}>` + height: 2px; + background-color: ${props => + props.disabled + ? props.theme.customColors.dark.disabled.foreground + : props.theme.customColors.dark.foreground}; + position: absolute; + width: 124px; + top: 19px; + + left: ${props => (props.left ? '-67px' : undefined)}; + right: ${props => (props.right ? '-67px' : undefined)}; +`; + +const getDriveListLabel = () => { + return _.join( + _.map(selectionState.getSelectedDrives(), (drive: any) => { + return `${drive.description} (${drive.displayName})`; + }), + '\n', + ); +}; + +const getMemoizedSelectedDrives = utils.memoize( + selectionState.getSelectedDrives, + _.isEqual, +); + +const shouldShowDrivesButton = () => { + return !settings.get('disableExplicitDriveSelection'); +}; + +const getDriveSelectionStateSlice = () => ({ + showDrivesButton: shouldShowDrivesButton(), + driveListLabel: getDriveListLabel(), + targets: getMemoizedSelectedDrives(), +}); + +export const DriveSelector = ({ + webviewShowing, + disabled, + nextStepDisabled, + hasDrive, + flashing, +}: any) => { + // TODO: inject these from redux-connector + const [ + { showDrivesButton, driveListLabel, targets }, + setStateSlice, + ] = React.useState(getDriveSelectionStateSlice()); + const [showDriveSelectorModal, setShowDriveSelectorModal] = React.useState( + false, + ); + + React.useEffect(() => { + return (store as any).observe(() => { + setStateSlice(getDriveSelectionStateSlice()); + }); + }, []); + + const showStepConnectingLines = !webviewShowing || !flashing; + + return ( +
+ {showStepConnectingLines && ( + + + + + )} + +
+ +
+ +
+ { + setShowDriveSelectorModal(true); + }} + reselectDrive={() => { + analytics.logEvent('Reselect drive', { + applicationSessionUuid: (store as any).getState().toJS() + .applicationSessionUuid, + flashingWorkflowUuid: (store as any).getState().toJS() + .flashingWorkflowUuid, + }); + setShowDriveSelectorModal(true); + }} + flashing={flashing} + constraints={driveConstraints} + targets={targets} + /> +
+ + {showDriveSelectorModal && ( + setShowDriveSelectorModal(false)} + > + )} +
+ ); +}; + +DriveSelector.propTypes = { + webviewShowing: propTypes.bool, + disabled: propTypes.bool, + nextStepDisabled: propTypes.bool, + hasDrive: propTypes.bool, + flashing: propTypes.bool, +}; diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx new file mode 100644 index 00000000..311732ca --- /dev/null +++ b/lib/gui/app/pages/main/Flash.tsx @@ -0,0 +1,323 @@ +/* + * Copyright 2016 balena.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. + */ + +import * as _ from 'lodash'; +import * as path from 'path'; +import * as React from 'react'; +import { Modal, Txt } from 'rendition'; +import * as constraints from '../../../../shared/drive-constraints'; +import * as messages from '../../../../shared/messages'; +import * as DriveSelectorModal from '../../components/drive-selector/DriveSelectorModal.jsx'; +import * as ProgressButton from '../../components/progress-button/progress-button.jsx'; +import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx'; +import * as availableDrives from '../../models/available-drives'; +import * as flashState from '../../models/flash-state'; +import * as selection from '../../models/selection-state'; +import * as store from '../../models/store'; +import * as analytics from '../../modules/analytics'; +import * as driveScanner from '../../modules/drive-scanner'; +import * as imageWriter from '../../modules/image-writer'; +import * as progressStatus from '../../modules/progress-status'; +import * as notification from '../../os/notification'; + +const COMPLETED_PERCENTAGE = 100; +const SPEED_PRECISION = 2; + +const getWarningMessages = (drives: any, image: any) => { + const warningMessages = []; + for (const drive of drives) { + if (constraints.isDriveSizeLarge(drive)) { + warningMessages.push(messages.warning.largeDriveSize(drive)); + } else if (!constraints.isDriveSizeRecommended(drive, image)) { + warningMessages.push( + messages.warning.unrecommendedDriveSize(image, drive), + ); + } + + // TODO(Shou): we should consider adding the same warning dialog for system drives and remove unsafe mode + } + + return warningMessages; +}; + +const getErrorMessageFromCode = (errorCode: string) => { + // TODO: All these error codes to messages translations + // should go away if the writer emitted user friendly + // messages on the first place. + if (errorCode === 'EVALIDATION') { + return messages.error.validation(); + } else if (errorCode === 'EUNPLUGGED') { + return messages.error.driveUnplugged(); + } else if (errorCode === 'EIO') { + return messages.error.inputOutput(); + } else if (errorCode === 'ENOSPC') { + return messages.error.notEnoughSpaceInDrive(); + } else if (errorCode === 'ECHILDDIED') { + return messages.error.childWriterDied(); + } + return ''; +}; + +const flashImageToDrive = async (goToSuccess: () => void) => { + const devices = selection.getSelectedDevices(); + const image: any = selection.getImage(); + const drives = _.filter(availableDrives.getDrives(), (drive: any) => { + return _.includes(devices, drive.device); + }); + + // eslint-disable-next-line no-magic-numbers + if (drives.length === 0 || flashState.isFlashing()) { + return ''; + } + + // Stop scanning drives when flashing + // otherwise Windows throws EPERM + driveScanner.stop(); + + const iconPath = '../../assets/icon.png'; + const basename = path.basename(image.path); + try { + await imageWriter.flash(image.path, drives); + if (!flashState.wasLastFlashCancelled()) { + const flashResults: any = flashState.getFlashResults(); + notification.send('Flash complete!', { + body: messages.info.flashComplete( + basename, + drives as any, + flashResults.results.devices, + ), + icon: iconPath, + }); + goToSuccess(); + } + } catch (error) { + // When flashing is cancelled before starting above there is no error + if (!error) { + return ''; + } + + notification.send('Oops! Looks like the flash failed.', { + body: messages.error.flashFailure(path.basename(image.path), drives), + icon: iconPath, + }); + + let errorMessage = getErrorMessageFromCode(error.code); + if (!errorMessage) { + error.image = basename; + analytics.logException(error); + errorMessage = messages.error.genericFlashError(); + } + + return errorMessage; + } finally { + availableDrives.setDrives([]); + driveScanner.start(); + } + + return ''; +}; + +/** + * @summary Get progress button label + * @function + * @public + * + * @returns {String} progress button label + * + * @example + * const label = FlashController.getProgressButtonLabel() + */ +const getProgressButtonLabel = () => { + if (!flashState.isFlashing()) { + return 'Flash!'; + } + + return progressStatus.fromFlashState(flashState.getFlashState()); +}; + +const formatSeconds = (totalSeconds: number) => { + if (!totalSeconds && !_.isNumber(totalSeconds)) { + return ''; + } + // eslint-disable-next-line no-magic-numbers + const minutes = Math.floor(totalSeconds / 60); + // eslint-disable-next-line no-magic-numbers + const seconds = Math.floor(totalSeconds - minutes * 60); + + return `${minutes}m${seconds}s`; +}; + +export const Flash = ({ + shouldFlashStepBeDisabled, + lastFlashErrorCode, + progressMessage, + goToSuccess, +}: any) => { + const state: any = flashState.getFlashState(); + const isFlashing = flashState.isFlashing(); + const flashErrorCode = lastFlashErrorCode(); + + const [warningMessages, setWarningMessages] = React.useState([]); + const [errorMessage, setErrorMessage] = React.useState(''); + const [showDriveSelectorModal, setShowDriveSelectorModal] = React.useState( + false, + ); + + const handleWarningResponse = async (shouldContinue: boolean) => { + setWarningMessages([]); + + if (!shouldContinue) { + setShowDriveSelectorModal(true); + return; + } + + setErrorMessage(await flashImageToDrive(goToSuccess)); + }; + + const handleFlashErrorResponse = (shouldRetry: boolean) => { + setErrorMessage(''); + flashState.resetState(); + if (shouldRetry) { + analytics.logEvent('Restart after failure', { + applicationSessionUuid: (store as any).getState().toJS() + .applicationSessionUuid, + flashingWorkflowUuid: (store as any).getState().toJS() + .flashingWorkflowUuid, + }); + } else { + selection.clear(); + } + }; + + const tryFlash = async () => { + const devices = selection.getSelectedDevices(); + const image = selection.getImage(); + const drives = _.filter(availableDrives.getDrives(), (drive: any) => { + return _.includes(devices, drive.device); + }); + + // eslint-disable-next-line no-magic-numbers + if (drives.length === 0 || flashState.isFlashing()) { + return; + } + + const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus( + drives, + image, + ); + if (hasDangerStatus) { + setWarningMessages(getWarningMessages(drives, image)); + return; + } + + setErrorMessage(await flashImageToDrive(goToSuccess)); + }; + + return ( + +
+
+ +
+ +
+ + + {isFlashing && ( + + )} + {!_.isNil(state.speed) && state.percentage !== COMPLETED_PERCENTAGE && ( +

+ {Boolean(state.speed) && ( + {`${state.speed.toFixed(SPEED_PRECISION)} MB/s`} + )} + {!_.isNil(state.eta) && ( + {`ETA: ${formatSeconds(state.eta)}`} + )} +

+ )} + + {Boolean(state.failed) && ( +
+
+ + {state.failed} + + {progressMessage.failed(state.failed)}{' '} + +
+
+ )} +
+
+ + {/* eslint-disable-next-line no-magic-numbers */} + {warningMessages && warningMessages.length > 0 && ( + handleWarningResponse(false)} + done={() => handleWarningResponse(true)} + cancelButtonProps={{ + children: 'Change', + }} + action={'Continue'} + primaryButtonProps={{ primary: false, warning: true }} + > + {_.map(warningMessages, (message, key) => ( + + {message} + + ))} + + )} + + {errorMessage && ( + handleFlashErrorResponse(false)} + done={() => handleFlashErrorResponse(true)} + action={'Retry'} + > + {errorMessage} + + )} + + {showDriveSelectorModal && ( + setShowDriveSelectorModal(false)} + > + )} +
+ ); +}; diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx new file mode 100644 index 00000000..4d062ba7 --- /dev/null +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -0,0 +1,216 @@ +/* + * Copyright 2019 balena.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. + */ + +import { faCog, faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import * as path from 'path'; +import * as React from 'react'; +import { Button } from 'rendition'; + +import * as FeaturedProject from '../../components/featured-project/featured-project'; +import * as ImageSelector from '../../components/image-selector/image-selector'; +import * as ReducedFlashingInfos from '../../components/reduced-flashing-infos/reduced-flashing-infos'; +import { SettingsModal } from '../../components/settings/settings'; +import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx'; +import * as flashState from '../../models/flash-state'; +import * as selectionState from '../../models/selection-state'; +import * as settings from '../../models/settings'; +import * as store from '../../models/store'; +import { open as openExternal } from '../../os/open-external/services/open-external'; +import { ThemedProvider } from '../../styled-components'; +import { colors } from '../../theme'; +import * as middleEllipsis from '../../utils/middle-ellipsis'; + +import * as messages from '../../../../shared/messages'; +import { bytesToClosestUnit } from '../../../../shared/units'; + +import { DriveSelector } from './DriveSelector'; +import { Flash } from './Flash'; + +const DEFAULT_SUPPORT_URL = + 'https://github.com/balena-io/etcher/blob/master/SUPPORT.md'; + +const getDrivesTitle = (selection: any) => { + const drives = selection.getSelectedDrives(); + + if (drives.length === 1) { + return drives[0].description || 'Untitled Device'; + } + + if (drives.length === 0) { + return 'No targets found'; + } + + return `${drives.length} Targets`; +}; + +const getImageBasename = (selection: any) => { + if (!selection.hasImage()) { + return ''; + } + + const selectionImageName = selection.getImageName(); + const imageBasename = path.basename(selection.getImagePath()); + return selectionImageName || imageBasename; +}; + +const MainPage = ({ $state }: any) => { + const setRefresh = React.useState(false)[1]; + const [isWebviewShowing, setIsWebviewShowing] = React.useState(false); + const [hideSettings, setHideSettings] = React.useState(true); + React.useEffect(() => { + return (store as any).observe(() => { + setRefresh(ref => !ref); + }); + }, []); + + const setWebviewShowing = (isShowing: boolean) => { + setIsWebviewShowing(isShowing); + store.dispatch({ + type: 'SET_WEBVIEW_SHOWING_STATUS', + data: Boolean(isShowing), + }); + }; + + const isFlashing = flashState.isFlashing(); + const shouldDriveStepBeDisabled = !selectionState.hasImage(); + const shouldFlashStepBeDisabled = + !selectionState.hasDrive() || shouldDriveStepBeDisabled; + const hasDrive = selectionState.hasDrive(); + const imageLogo = selectionState.getImageLogo(); + const imageSize = bytesToClosestUnit(selectionState.getImageSize()); + const imageName = middleEllipsis(getImageBasename(selectionState), 16); + const driveTitle = middleEllipsis(getDrivesTitle(selectionState), 16); + const shouldShowFlashingInfos = isFlashing && isWebviewShowing; + const lastFlashErrorCode = flashState.getLastFlashErrorCode; + const progressMessage = messages.progress; + + return ( + +
+ + openExternal('https://www.balena.io/etcher?ref=etcher_footer') + } + tabIndex={100} + > + + + + +
+ {hideSettings ? null : ( + { + setHideSettings(!value); + }} + /> + )} + +
+
+ +
+ +
+ +
+ + {isFlashing && ( +
+ +
+ )} + +
+ +
+ +
+ $state.go('success')} + shouldFlashStepBeDisabled={shouldFlashStepBeDisabled} + lastFlashErrorCode={lastFlashErrorCode} + progressMessage={progressMessage} + /> +
+
+
+ ); +}; + +export default MainPage; diff --git a/lib/gui/app/pages/main/controllers/drive-selection.js b/lib/gui/app/pages/main/controllers/drive-selection.js deleted file mode 100644 index 8abdf0c8..00000000 --- a/lib/gui/app/pages/main/controllers/drive-selection.js +++ /dev/null @@ -1,159 +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 angular = require('angular') -const prettyBytes = require('pretty-bytes') -const store = require('../../../models/store') -const settings = require('../../../models/settings') -const selectionState = require('../../../models/selection-state') -const analytics = require('../../../modules/analytics') -const exceptionReporter = require('../../../modules/exception-reporter') -const utils = require('../../../../../shared/utils') - -module.exports = function (DriveSelectorService) { - /** - * @summary Get drive title based on device quantity - * @function - * @public - * - * @returns {String} - drives title - * - * @example - * console.log(DriveSelectionController.getDrivesTitle()) - * > 'Multiple Drives (4)' - */ - this.getDrivesTitle = () => { - const drives = selectionState.getSelectedDrives() - - // eslint-disable-next-line no-magic-numbers - if (drives.length === 1) { - return _.head(drives).description || 'Untitled Device' - } - - // eslint-disable-next-line no-magic-numbers - if (drives.length === 0) { - return 'No targets found' - } - - return `${drives.length} Devices` - } - - /** - * @summary Get drive subtitle - * @function - * @public - * - * @returns {String} - drives subtitle - * - * @example - * console.log(DriveSelectionController.getDrivesSubtitle()) - * > '32 GB' - */ - this.getDrivesSubtitle = () => { - const drive = selectionState.getCurrentDrive() - - if (drive) { - return prettyBytes(drive.size) - } - - return 'Please insert at least one target device' - } - - /** - * @summary Get drive list label - * @function - * @public - * - * @returns {String} - 'list' of drives separated by newlines - * - * @example - * console.log(DriveSelectionController.getDriveListLabel()) - * > 'My Drive (/dev/disk1)\nMy Other Drive (/dev/disk2)' - */ - this.getDriveListLabel = () => { - return _.join(_.map(selectionState.getSelectedDrives(), (drive) => { - return `${drive.description} (${drive.displayName})` - }), '\n') - } - - /** - * @summary Open drive selector - * @function - * @public - * - * @example - * DriveSelectionController.openDriveSelector(); - */ - this.openDriveSelector = () => { - DriveSelectorService.open().then((drive) => { - if (!drive) { - return - } - - selectionState.selectDrive(drive.device) - - analytics.logEvent('Select drive', { - device: drive.device, - unsafeMode: settings.get('unsafeMode') && !settings.get('disableUnsafeMode'), - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - }).catch(exceptionReporter.report) - } - - /** - * @summary Reselect a drive - * @function - * @public - * - * @example - * DriveSelectionController.reselectDrive(); - */ - this.reselectDrive = () => { - this.openDriveSelector() - analytics.logEvent('Reselect drive', { - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - } - - /** - * @summary Get memoized selected drives - * @function - * @public - * - * @example - * DriveSelectionController.getMemoizedSelectedDrives() - */ - this.getMemoizedSelectedDrives = utils.memoize(selectionState.getSelectedDrives, angular.equals) - - /** - * @summary Should the drive selection button be shown - * @function - * @public - * - * @returns {Boolean} - * - * @example - * DriveSelectionController.shouldShowDrivesButton() - */ - this.shouldShowDrivesButton = () => { - return !settings.get('disableExplicitDriveSelection') - } -} diff --git a/lib/gui/app/pages/main/controllers/flash.js b/lib/gui/app/pages/main/controllers/flash.js deleted file mode 100644 index d10bb065..00000000 --- a/lib/gui/app/pages/main/controllers/flash.js +++ /dev/null @@ -1,222 +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 messages = require('../../../../../shared/messages') -const flashState = require('../../../models/flash-state') -const driveScanner = require('../../../modules/drive-scanner') -const progressStatus = require('../../../modules/progress-status') -const notification = require('../../../os/notification') -const analytics = require('../../../modules/analytics') -const imageWriter = require('../../../modules/image-writer') -const path = require('path') -const store = require('../../../models/store') -const constraints = require('../../../../../shared/drive-constraints') -const availableDrives = require('../../../models/available-drives') -const selection = require('../../../models/selection-state') - -module.exports = function ( - $state, - $timeout, - FlashErrorModalService, - WarningModalService, - DriveSelectorService -) { - /** - * @summary Spawn a confirmation warning modal - * @function - * @public - * - * @param {Array} warningMessages - warning messages - * @returns {Promise} warning modal promise - * - * @example - * confirmationWarningModal([ 'Hello, World!' ]) - */ - const confirmationWarningModal = (warningMessages) => { - return WarningModalService.display({ - confirmationLabel: 'Continue', - rejectionLabel: 'Change', - description: [ - warningMessages.join('\n\n'), - 'Are you sure you want to continue?' - ].join(' ') - }) - } - - /** - * @summary Display warning tailored to the warning of the current drives-image pair - * @function - * @public - * - * @param {Array} drives - list of drive objects - * @param {Object} image - image object - * @returns {Promise} - * - * @example - * displayTailoredWarning(drives, image).then((ok) => { - * if (ok) { - * console.log('No warning was shown or continue was pressed') - * } else { - * console.log('Change was pressed') - * } - * }) - */ - const displayTailoredWarning = async (drives, image) => { - const warningMessages = [] - for (const drive of drives) { - if (constraints.isDriveSizeLarge(drive)) { - warningMessages.push(messages.warning.largeDriveSize(drive)) - } else if (!constraints.isDriveSizeRecommended(drive, image)) { - warningMessages.push(messages.warning.unrecommendedDriveSize(image, drive)) - } - - // TODO(Shou): we should consider adding the same warning dialog for system drives and remove unsafe mode - } - - if (!warningMessages.length) { - return true - } - - return confirmationWarningModal(warningMessages) - } - - /** - * @summary Flash image to drives - * @function - * @public - * - * @example - * FlashController.flashImageToDrive({ - * path: 'rpi.img', - * size: 1000000000, - * compressedSize: 1000000000, - * isSizeEstimated: false, - * }, [ - * '/dev/disk2', - * '/dev/disk5' - * ]) - */ - this.flashImageToDrive = async () => { - const devices = selection.getSelectedDevices() - const image = selection.getImage() - const drives = _.filter(availableDrives.getDrives(), (drive) => { - return _.includes(devices, drive.device) - }) - - // eslint-disable-next-line no-magic-numbers - if (drives.length === 0) { - return - } - - const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus(drives, image) - if (hasDangerStatus) { - if (!(await displayTailoredWarning(drives, image))) { - DriveSelectorService.open() - return - } - } - - if (flashState.isFlashing()) { - return - } - - // Trigger Angular digests along with store updates, as the flash state - // updates. Without this there is essentially no progress to watch. - const unsubscribe = store.observe($timeout) - - // Stop scanning drives when flashing - // otherwise Windows throws EPERM - driveScanner.stop() - - const iconPath = '../../../assets/icon.png' - const basename = path.basename(image.path) - try { - await imageWriter.flash(image.path, drives) - if (!flashState.wasLastFlashCancelled()) { - const flashResults = flashState.getFlashResults() - notification.send('Flash complete!', { - body: messages.info.flashComplete(basename, drives, flashResults.results.devices), - icon: iconPath - }) - $state.go('success') - } - } catch (error) { - // When flashing is cancelled before starting above there is no error - if (!error) { - return - } - - notification.send('Oops! Looks like the flash failed.', { - body: messages.error.flashFailure(path.basename(image.path), drives), - icon: iconPath - }) - - // TODO: All these error codes to messages translations - // should go away if the writer emitted user friendly - // messages on the first place. - if (error.code === 'EVALIDATION') { - FlashErrorModalService.show(messages.error.validation()) - } else if (error.code === 'EUNPLUGGED') { - FlashErrorModalService.show(messages.error.driveUnplugged()) - } else if (error.code === 'EIO') { - FlashErrorModalService.show(messages.error.inputOutput()) - } else if (error.code === 'ENOSPC') { - FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive()) - } else if (error.code === 'ECHILDDIED') { - FlashErrorModalService.show(messages.error.childWriterDied()) - } else { - FlashErrorModalService.show(messages.error.genericFlashError()) - error.image = basename - analytics.logException(error) - } - } finally { - availableDrives.setDrives([]) - driveScanner.start() - unsubscribe() - } - } - - /** - * @summary Get progress button label - * @function - * @public - * - * @returns {String} progress button label - * - * @example - * const label = FlashController.getProgressButtonLabel() - */ - this.getProgressButtonLabel = () => { - if (!flashState.isFlashing()) { - return 'Flash!' - } - - return progressStatus.fromFlashState(flashState.getFlashState()) - } - - /** - * @summary Abort write process - * @function - * @public - * - * @example - * FlashController.cancelFlash() - */ - this.cancelFlash = imageWriter.cancel -} diff --git a/lib/gui/app/pages/main/controllers/image-selection.js b/lib/gui/app/pages/main/controllers/image-selection.js deleted file mode 100644 index 3060c713..00000000 --- a/lib/gui/app/pages/main/controllers/image-selection.js +++ /dev/null @@ -1,265 +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 Bluebird = require('bluebird') -const path = require('path') -const sdk = require('etcher-sdk') - -const store = require('../../../models/store') -const messages = require('../../../../../shared/messages') -const errors = require('../../../../../shared/errors') -const supportedFormats = require('../../../../../shared/supported-formats') -const analytics = require('../../../modules/analytics') -const settings = require('../../../models/settings') -const selectionState = require('../../../models/selection-state') -const osDialog = require('../../../os/dialog') -const { replaceWindowsNetworkDriveLetter } = require('../../../os/windows-network-drives') -const exceptionReporter = require('../../../modules/exception-reporter') - -module.exports = function ( - $timeout, - FileSelectorService, - WarningModalService -) { - /** - * @summary Main supported extensions - * @constant - * @type {String[]} - * @public - */ - this.mainSupportedExtensions = _.intersection([ - 'img', - 'iso', - 'zip' - ], supportedFormats.getAllExtensions()) - - /** - * @summary Extra supported extensions - * @constant - * @type {String[]} - * @public - */ - this.extraSupportedExtensions = _.difference( - supportedFormats.getAllExtensions(), - this.mainSupportedExtensions - ).sort() - - /** - * @summary Select image - * @function - * @public - * - * @param {Object} image - image - * - * @example - * osDialogService.selectImage() - * .then(ImageSelectionController.selectImage); - */ - this.selectImage = (image) => { - if (!supportedFormats.isSupportedImage(image.path)) { - const invalidImageError = errors.createUserError({ - title: 'Invalid image', - description: messages.error.invalidImage(image) - }) - - osDialog.showError(invalidImageError) - analytics.logEvent('Invalid image', _.merge({ - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }, image)) - return - } - - Bluebird.try(() => { - let message = null - - if (supportedFormats.looksLikeWindowsImage(image.path)) { - analytics.logEvent('Possibly Windows image', { - image, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - message = messages.warning.looksLikeWindowsImage() - } else if (!image.hasMBR) { - analytics.logEvent('Missing partition table', { - image, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - message = messages.warning.missingPartitionTable() - } - - if (message) { - // TODO: `Continue` should be on a red background (dangerous action) instead of `Change`. - // We want `X` to act as `Continue`, that's why `Continue` is the `rejectionLabel` - return WarningModalService.display({ - confirmationLabel: 'Change', - rejectionLabel: 'Continue', - description: message - }) - } - - return false - }).then((shouldChange) => { - if (shouldChange) { - return this.reselectImage() - } - - selectionState.selectImage(image) - - // An easy way so we can quickly identify if we're making use of - // certain features without printing pages of text to DevTools. - image.logo = Boolean(image.logo) - image.blockMap = Boolean(image.blockMap) - - return analytics.logEvent('Select image', { - image, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - }).catch(exceptionReporter.report) - } - - /** - * @summary Select an image by path - * @function - * @public - * - * @param {String} imagePath - image path - * - * @example - * ImageSelectionController.selectImageByPath('path/to/image.img'); - */ - this.selectImageByPath = async (imagePath) => { - try { - // eslint-disable-next-line no-param-reassign - imagePath = await replaceWindowsNetworkDriveLetter(imagePath) - } catch (error) { - analytics.logException(error) - } - if (!supportedFormats.isSupportedImage(imagePath)) { - const invalidImageError = errors.createUserError({ - title: 'Invalid image', - description: messages.error.invalidImage(imagePath) - }) - - osDialog.showError(invalidImageError) - analytics.logEvent('Invalid image', { path: imagePath }) - return - } - - const source = new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read) - try { - const innerSource = await source.getInnerSource() - const metadata = await innerSource.getMetadata() - const partitionTable = await innerSource.getPartitionTable() - if (partitionTable) { - metadata.hasMBR = true - metadata.partitions = partitionTable.partitions - } - metadata.path = imagePath - // eslint-disable-next-line no-magic-numbers - metadata.extension = path.extname(imagePath).slice(1) - this.selectImage(metadata) - $timeout() - } catch (error) { - const imageError = errors.createUserError({ - title: 'Error opening image', - description: messages.error.openImage(path.basename(imagePath), error.message) - }) - osDialog.showError(imageError) - analytics.logException(error) - } finally { - try { - await source.close() - } catch (error) { - // Noop - } - } - } - - /** - * @summary Open image selector - * @function - * @public - * - * @example - * ImageSelectionController.openImageSelector(); - */ - this.openImageSelector = () => { - analytics.logEvent('Open image selector', { - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - - if (settings.get('experimentalFilePicker')) { - FileSelectorService.open() - } else { - osDialog.selectImage().then((imagePath) => { - // Avoid analytics and selection state changes - // if no file was resolved from the dialog. - if (!imagePath) { - analytics.logEvent('Image selector closed', { - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - return - } - - this.selectImageByPath(imagePath) - }).catch(exceptionReporter.report) - } - } - - /** - * @summary Reselect image - * @function - * @public - * - * @example - * ImageSelectionController.reselectImage(); - */ - this.reselectImage = () => { - analytics.logEvent('Reselect image', { - previousImage: selectionState.getImage(), - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid - }) - - this.openImageSelector() - } - - /** - * @summary Get the basename of the selected image - * @function - * @public - * - * @returns {String} basename of the selected image - * - * @example - * const imageBasename = ImageSelectionController.getImageBasename(); - */ - this.getImageBasename = () => { - if (!selectionState.hasImage()) { - return '' - } - - return path.basename(selectionState.getImagePath()) - } -} diff --git a/lib/gui/app/pages/main/controllers/main.js b/lib/gui/app/pages/main/controllers/main.js deleted file mode 100644 index 60d52d0a..00000000 --- a/lib/gui/app/pages/main/controllers/main.js +++ /dev/null @@ -1,183 +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 path = require('path') -const store = require('../../../models/store') -const settings = require('../../../models/settings') -const flashState = require('../../../models/flash-state') -const analytics = require('../../../modules/analytics') -const exceptionReporter = require('../../../modules/exception-reporter') -const availableDrives = require('../../../models/available-drives') -const selectionState = require('../../../models/selection-state') -const driveConstraints = require('../../../../../shared/drive-constraints') -const messages = require('../../../../../shared/messages') -const prettyBytes = require('pretty-bytes') - -module.exports = function ( - TooltipModalService, - OSOpenExternalService, - $filter -) { - // Expose several modules to the template for convenience - this.selection = selectionState - this.drives = availableDrives - this.state = flashState - this.settings = settings - this.external = OSOpenExternalService - this.constraints = driveConstraints - this.progressMessage = messages.progress - this.isWebviewShowing = Boolean(store.getState().toJS().isWebviewShowing) - - /** - * @summary Determine if the drive step should be disabled - * @function - * @public - * - * @returns {Boolean} whether the drive step should be disabled - * - * @example - * if (MainController.shouldDriveStepBeDisabled()) { - * console.log('The drive step should be disabled'); - * } - */ - this.shouldDriveStepBeDisabled = () => { - return !selectionState.hasImage() - } - - /** - * @summary Determine if the flash step should be disabled - * @function - * @public - * - * @returns {Boolean} whether the flash step should be disabled - * - * @example - * if (MainController.shouldFlashStepBeDisabled()) { - * console.log('The flash step should be disabled'); - * } - */ - this.shouldFlashStepBeDisabled = () => { - return !selectionState.hasDrive() || this.shouldDriveStepBeDisabled() - } - - /** - * @summary Display a tooltip with the selected image details - * @function - * @public - * - * @returns {Promise} - * - * @example - * MainController.showSelectedImageDetails() - */ - this.showSelectedImageDetails = () => { - analytics.logEvent('Show selected image tooltip', { - imagePath: selectionState.getImagePath(), - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid - }) - - return TooltipModalService.show({ - title: 'Image File Name', - message: selectionState.getImagePath() - }).catch(exceptionReporter.report) - } - - /** - * @summary Get drive title based on device quantity - * @function - * @public - * - * @returns {String} - drives title - * - * @example - * console.log(DriveSelectionController.getDrivesTitle()) - * > 'Multiple Drives (4)' - */ - this.getDrivesTitle = () => { - const drives = this.selection.getSelectedDrives() - - /* eslint-disable no-magic-numbers */ - if (drives.length === 1) { - return drives[0].description || 'Untitled Device' - } - /* eslint-enable no-magic-numbers */ - - // eslint-disable-next-line no-magic-numbers - if (drives.length === 0) { - return 'No targets found' - } - - return `${drives.length} Targets` - } - - /** - * @summary Get drive subtitle - * @function - * @public - * - * @returns {String} - drives subtitle - * - * @example - * console.log(MainController.getDrivesSubtitle()) - * > '32 GB' - */ - this.getDrivesSubtitle = () => { - const drive = this.selection.getCurrentDrive() - - if (drive) { - return prettyBytes(drive.size) - } - - return 'Please insert at least one target device' - } - - /** - * @summary Get the basename of the selected image - * @function - * @public - * - * @returns {String} basename of the selected image - * - * @example - * const imageBasename = ImageSelectionController.getImageBasename(); - */ - this.getImageBasename = () => { - if (!this.selection.hasImage()) { - return '' - } - - return path.basename(this.selection.getImagePath()) - } - - this.setWebviewShowing = (data) => { - this.isWebviewShowing = data - store.dispatch({ - type: 'SET_WEBVIEW_SHOWING_STATUS', - data: Boolean(data) - }) - } - - this.getDriveTitle = () => { - /* eslint-disable no-magic-numbers */ - const driveTitleRaw = (this.selection.getSelectedDevices().length === 1) - ? this.getDrivesSubtitle() - : `${this.selection.getSelectedDevices().length} Targets` - return $filter('middleEllipsis:20')(driveTitleRaw) - } -} diff --git a/lib/gui/app/pages/main/main.js b/lib/gui/app/pages/main/main.js deleted file mode 100644 index 7838ba04..00000000 --- a/lib/gui/app/pages/main/main.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' - -/** - * This page represents the application main page. - * - * @module Etcher.Pages.Main - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Pages.Main' - -require('angular-moment') - -const MainPage = angular.module(MODULE_NAME, [ - 'angularMoment', - - require('angular-ui-router'), - require('angular-seconds-to-date'), - - require('../../components/drive-selector/drive-selector'), - require('../../components/tooltip-modal/tooltip-modal'), - require('../../components/flash-error-modal/flash-error-modal'), - require('../../components/progress-button'), - require('../../components/image-selector'), - require('../../components/warning-modal/warning-modal'), - require('../../components/file-selector'), - require('../../components/featured-project'), - require('../../components/reduced-flashing-infos'), - require('../../components/flash-another'), - require('../../components/flash-results'), - require('../../components/drive-selector'), - - require('../../os/open-external/open-external'), - require('../../os/dropzone/dropzone'), - - require('../../utils/byte-size/byte-size'), - require('../../utils/middle-ellipsis/filter') -]) - -MainPage.controller('MainController', require('./controllers/main')) -MainPage.controller('ImageSelectionController', require('./controllers/image-selection')) -MainPage.controller('DriveSelectionController', require('./controllers/drive-selection')) -MainPage.controller('FlashController', require('./controllers/flash')) - -MainPage.config(($stateProvider) => { - $stateProvider - .state('main', { - url: '/main', - controller: 'MainController as main', - template: require('./templates/main.tpl.html') - }) -}) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/pages/main/main.ts b/lib/gui/app/pages/main/main.ts new file mode 100644 index 00000000..30edc184 --- /dev/null +++ b/lib/gui/app/pages/main/main.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2019 balena.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'; + +/** + * This page represents the application main page. + * + * @module Etcher.Pages.Main + */ + +import * as angular from 'angular'; +// @ts-ignore +import * as angularRouter from 'angular-ui-router'; +import { react2angular } from 'react2angular'; +import MainPage from './MainPage'; + +import { MODULE_NAME as flashAnother } from '../../components/flash-another'; +import { MODULE_NAME as flashResults } from '../../components/flash-results'; +import * as byteSize from '../../utils/byte-size/byte-size'; + +export const MODULE_NAME = 'Etcher.Pages.Main'; + +const Main = angular.module(MODULE_NAME, [ + angularRouter, + flashAnother, + flashResults, + byteSize, +]); + +Main.component('mainPage', react2angular(MainPage, [], ['$state'])); + +Main.config(($stateProvider: any) => { + $stateProvider.state('main', { + url: '/main', + template: '', + }); +}); diff --git a/lib/gui/app/pages/main/styles/_main.scss b/lib/gui/app/pages/main/styles/_main.scss index 24e75dec..3da1468b 100644 --- a/lib/gui/app/pages/main/styles/_main.scss +++ b/lib/gui/app/pages/main/styles/_main.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,14 @@ * limitations under the License. */ -svg-icon > img[disabled] { +img[disabled] { opacity: $disabled-opacity; } .page-main { flex: 1; align-self: center; + margin: 20px; } .page-main > .col-xs { @@ -64,28 +65,6 @@ svg-icon > img[disabled] { font-weight: 300; } -%step-border { - height: 2px; - background-color: $palette-theme-dark-foreground; - position: absolute; - width: 124px; - top: 19px; - - &[disabled] { - background-color: $palette-theme-dark-disabled-foreground; - } -} - -.page-main .step-border-left { - @extend %step-border; - left: -67px; -} - -.page-main .step-border-right { - @extend %step-border; - right: -67px; -} - .page-main .step-tooltip { display: block; margin: -5px auto -20px; @@ -114,11 +93,6 @@ svg-icon > img[disabled] { width: $btn-min-width; } -.page-main .step-footer-underline { - border-bottom: 1px dotted; - padding-bottom: 2px; -} - .page-main .button.step-footer { font-size: 16px; color: $palette-theme-primary-background; @@ -191,8 +165,7 @@ svg-icon > img[disabled] { .target-status-line { display: flex; align-items: baseline; - font-size: 16px; - font-family: inherit; + margin-bottom: 9px; > .target-status-dot { width: 12px; @@ -226,17 +199,3 @@ svg-icon > img[disabled] { .space-vertical-large { position: relative; } - -body.rendition-modal-open > div:last-child > div > div > div:last-child { - top: unset; - bottom: -200px; -} - -#app-logo { - position: fixed; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - width: 123px; -} diff --git a/lib/gui/app/pages/main/templates/main.tpl.html b/lib/gui/app/pages/main/templates/main.tpl.html deleted file mode 100644 index fdf1b71d..00000000 --- a/lib/gui/app/pages/main/templates/main.tpl.html +++ /dev/null @@ -1,118 +0,0 @@ -
-
-
- -
- -
- -
- - -
- -
-
- -
-
- -
-
- -
- -
- -
- - -
- -
-
- -
- -
- -
- -
- -
-
-
- -
- -
- - - - - - - -
-
- - {{ main.state.getFlashState().failed }} - {{ - main.progressMessage.failed(main.state.getFlashState().failed) - }} -
-
-
-
-
-
diff --git a/lib/gui/app/scss/components/_badge.scss b/lib/gui/app/scss/components/_badge.scss index 058e50ab..46831ee3 100644 --- a/lib/gui/app/scss/components/_badge.scss +++ b/lib/gui/app/scss/components/_badge.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/scss/components/_button.scss b/lib/gui/app/scss/components/_button.scss index 55d5adba..4251c5f8 100644 --- a/lib/gui/app/scss/components/_button.scss +++ b/lib/gui/app/scss/components/_button.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/scss/components/_caption.scss b/lib/gui/app/scss/components/_caption.scss index 0a3c9003..50f8f4ee 100644 --- a/lib/gui/app/scss/components/_caption.scss +++ b/lib/gui/app/scss/components/_caption.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/scss/components/_label.scss b/lib/gui/app/scss/components/_label.scss index 567481b4..19f76a83 100644 --- a/lib/gui/app/scss/components/_label.scss +++ b/lib/gui/app/scss/components/_label.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/scss/components/_tick.scss b/lib/gui/app/scss/components/_tick.scss index 67e6d75c..9a9519ec 100644 --- a/lib/gui/app/scss/components/_tick.scss +++ b/lib/gui/app/scss/components/_tick.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/scss/main.scss b/lib/gui/app/scss/main.scss index f337c8e7..ebc3ebec 100644 --- a/lib/gui/app/scss/main.scss +++ b/lib/gui/app/scss/main.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,9 +34,6 @@ $disabled-opacity: 0.2; @import "../components/modal/styles/modal"; @import "../components/drive-selector/styles/drive-selector"; @import "../components/svg-icon/styles/svg-icon"; -@import "../components/tooltip-modal/styles/tooltip-modal"; -@import "../components/warning-modal/styles/warning-modal"; -@import "../components/file-selector/styles/file-selector"; @import "../pages/main/styles/main"; @import "../pages/finish/styles/finish"; @@ -167,24 +164,11 @@ body { margin: 0 13px; } - .caption[os-open-external] { - border-bottom: 1px dashed; - padding-bottom: 2px; - - &:hover { - color: lighten($palette-theme-dark-disabled-foreground, 5%); - } - } - .footer-right { font-size: 10px; position: absolute; right: 0; } - - > span[os-open-external] { - display: flex; - } } .section-loader { @@ -209,27 +193,7 @@ body { margin: 20px 50px; } -.section-header { - text-align: right; - padding: 13px 14px; - - > .button { - padding: 0; - - > .glyphicon { - font-size: 24px; - } - } - - > * { - display: inline-block; - vertical-align: middle; - height: 24px; - margin: 0 10px; - } -} - -featured-project { +.featured-project { webview { flex: 0 1; height: 0; diff --git a/lib/gui/app/scss/modules/_bootstrap.scss b/lib/gui/app/scss/modules/_bootstrap.scss index dfa138c4..c49095f4 100644 --- a/lib/gui/app/scss/modules/_bootstrap.scss +++ b/lib/gui/app/scss/modules/_bootstrap.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/scss/modules/_space.scss b/lib/gui/app/scss/modules/_space.scss index 9f6a456d..c4151951 100644 --- a/lib/gui/app/scss/modules/_space.scss +++ b/lib/gui/app/scss/modules/_space.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/scss/modules/_theme.scss b/lib/gui/app/scss/modules/_theme.scss index 98091c08..1ce922b3 100644 --- a/lib/gui/app/scss/modules/_theme.scss +++ b/lib/gui/app/scss/modules/_theme.scss @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/styled-components.js b/lib/gui/app/styled-components.js index 3024d54f..508584aa 100644 --- a/lib/gui/app/styled-components.js +++ b/lib/gui/app/styled-components.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ const { const { colors } = require('./theme') const theme = { + // TODO: Standardize how the colors are specified to match with rendition's format. + customColors: colors, button: { border: { width: '0', @@ -79,11 +81,14 @@ exports.ChangeButton = styled(BaseButton) ` padding: 0; width: 100%; height: auto; - ${space} - &:hover, &:focus, &:active { - color: ${colors.primary.background}; + &:enabled { + &:hover, &:focus, &:active { + color: #8f9297; + } } + + ${space} ` exports.StepNameButton = styled(BaseButton) ` display: flex; @@ -93,8 +98,10 @@ exports.StepNameButton = styled(BaseButton) ` font-weight: bold; color: ${colors.dark.foreground}; - &:hover, &:focus, &:active{ - color: ${colors.primary.foreground}; + &:enabled { + &:hover, &:focus, &:active{ + color: #8f9297; + } } ` exports.StepSelection = styled(Flex) ` diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx new file mode 100644 index 00000000..3faa7e4f --- /dev/null +++ b/lib/gui/app/styled-components.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2018 balena.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. + */ + +import * as React from 'react'; +import { Button, Flex, Provider, Txt } from 'rendition'; +import styled from 'styled-components'; +import { space } from 'styled-system'; + +import { colors } from './theme'; + +const theme = { + // TODO: Standardize how the colors are specified to match with rendition's format. + customColors: colors, + button: { + border: { + width: '0', + radius: '24px', + }, + disabled: { + opacity: 1, + }, + extend: () => ` + width: 200px; + height: 48px; + font-size: 16px; + + &:disabled { + background-color: ${colors.dark.disabled.background}; + color: ${colors.dark.disabled.foreground}; + opacity: 1; + + &:hover { + background-color: ${colors.dark.disabled.background}; + color: ${colors.dark.disabled.foreground}; + } + } + `, + }, +}; + +export const ThemedProvider = (props: any) => ( + +); + +export const BaseButton = styled(Button)` + height: 48px; +`; + +export const StepButton = (props: any) => ( + +); + +export const ChangeButton = styled(BaseButton)` + color: ${colors.primary.background}; + padding: 0; + width: 100%; + height: auto; + + &:enabled { + &:hover, + &:focus, + &:active { + color: #8f9297; + } + } + ${space} +`; +export const StepNameButton = styled(BaseButton)` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + font-weight: bold; + color: ${colors.dark.foreground}; + + &:enabled { + &:hover, + &:focus, + &:active { + color: #8f9297; + } + } +`; +export const StepSelection = styled(Flex)` + flex-wrap: wrap; + justify-content: center; +`; +export const Footer = styled(Txt)` + margin-top: 10px; + color: ${colors.dark.disabled.foreground}; + font-size: 10px; +`; +export const Underline = styled(Txt.span)` + border-bottom: 1px dotted; + padding-bottom: 2px; +`; +export const DetailsText = styled(Txt.p)` + color: ${colors.dark.disabled.foreground}; + margin-bottom: 0; +`; diff --git a/lib/gui/app/theme.js b/lib/gui/app/theme.js index 9e566a51..2cdf0453 100644 --- a/lib/gui/app/theme.js +++ b/lib/gui/app/theme.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"), * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/utils/byte-size/byte-size.js b/lib/gui/app/utils/byte-size/byte-size.js index 8a4b2827..8101cd50 100644 --- a/lib/gui/app/utils/byte-size/byte-size.js +++ b/lib/gui/app/utils/byte-size/byte-size.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/utils/byte-size/filter.js b/lib/gui/app/utils/byte-size/filter.js index 74f4eb7c..f677aeed 100644 --- a/lib/gui/app/utils/byte-size/filter.js +++ b/lib/gui/app/utils/byte-size/filter.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/utils/manifest-bind/directives/manifest-bind.js b/lib/gui/app/utils/manifest-bind/directives/manifest-bind.js deleted file mode 100644 index 65818a06..00000000 --- a/lib/gui/app/utils/manifest-bind/directives/manifest-bind.js +++ /dev/null @@ -1,52 +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 errors = require('../../../../../shared/errors') - -/** - * @summary ManifestBind directive - * @function - * @public - * - * @description - * This directive provides an attribute to bind the current - * element value to a property in `package.json`. - * - * @param {Object} ManifestBindService - ManifestBindService - * @returns {Object} directive - * - * @example - * - */ -module.exports = (ManifestBindService) => { - return { - restrict: 'A', - scope: false, - link: (scope, element, attributes) => { - const value = ManifestBindService.get(attributes.manifestBind) - - if (!value) { - throw errors.createError({ - title: `ManifestBind: Unknown property \`${attributes.manifestBind}\`` - }) - } - - element.html(value) - } - } -} diff --git a/lib/gui/app/utils/manifest-bind/manifest-bind.js b/lib/gui/app/utils/manifest-bind/manifest-bind.js deleted file mode 100644 index f21be472..00000000 --- a/lib/gui/app/utils/manifest-bind/manifest-bind.js +++ /dev/null @@ -1,33 +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' - -/** - * The purpose of this module is to provide an attribute - * directive to bind the current element to a property - * in the application's `package.json` manifest. - * - * @module Etcher.Utils.ManifestBind - */ - -const angular = require('angular') -const MODULE_NAME = 'Etcher.Utils.ManifestBind' -const ManifestBind = angular.module(MODULE_NAME, []) -ManifestBind.service('ManifestBindService', require('./services/manifest-bind')) -ManifestBind.directive('manifestBind', require('./directives/manifest-bind')) - -module.exports = MODULE_NAME diff --git a/lib/gui/app/utils/manifest-bind/services/manifest-bind.js b/lib/gui/app/utils/manifest-bind/services/manifest-bind.js deleted file mode 100644 index 39c399c2..00000000 --- a/lib/gui/app/utils/manifest-bind/services/manifest-bind.js +++ /dev/null @@ -1,37 +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 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 = (attribute) => { - return _.get(packageJSON, attribute) - } -} diff --git a/lib/gui/app/utils/middle-ellipsis.js b/lib/gui/app/utils/middle-ellipsis.js index 4d94950f..84aa053f 100644 --- a/lib/gui/app/utils/middle-ellipsis.js +++ b/lib/gui/app/utils/middle-ellipsis.js @@ -1,6 +1,6 @@ /* * Copyright 2016 Juan Cruz Viotti. https://github.com/jviotti - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/app/utils/middle-ellipsis/filter.js b/lib/gui/app/utils/middle-ellipsis/filter.js deleted file mode 100644 index 0e72044e..00000000 --- a/lib/gui/app/utils/middle-ellipsis/filter.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2016 Juan Cruz Viotti. https://github.com/jviotti - * Copyright 2018 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' - -/** - * The purpose of this module is to provide utilities - * to work with sizes in bytes. - * - * @module Etcher.Utils.MiddleEllipsis - */ - -const _ = require('lodash') -const angular = require('angular') -const middleEllipsis = require('../middle-ellipsis') - -const MODULE_NAME = 'Etcher.Utils.MiddleEllipsis' -const MiddleEllipsis = angular.module(MODULE_NAME, []) - -/* eslint-disable lodash/prefer-lodash-method */ - -MiddleEllipsis.filter('middleEllipsis', _.constant(middleEllipsis)) - -/* eslint-enable lodash/prefer-lodash-method */ - -module.exports = MODULE_NAME diff --git a/lib/gui/assets/resin.svg b/lib/gui/assets/resin.svg deleted file mode 100644 index 43c447c0..00000000 --- a/lib/gui/assets/resin.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - Group + resin_text - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/gui/css/angular.css b/lib/gui/css/angular.css index 08ddec69..5c9daea5 100644 --- a/lib/gui/css/angular.css +++ b/lib/gui/css/angular.css @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/css/desktop.css b/lib/gui/css/desktop.css index 8c3e2100..e830430f 100644 --- a/lib/gui/css/desktop.css +++ b/lib/gui/css/desktop.css @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ body { /* Allow window to be dragged from anywhere */ -body > header { +#app-header { -webkit-app-region: drag; } diff --git a/lib/gui/css/main.css b/lib/gui/css/main.css index 940cb0bf..757f86bb 100644 --- a/lib/gui/css/main.css +++ b/lib/gui/css/main.css @@ -1,6 +1,6 @@ @charset "UTF-8"; /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -5850,7 +5850,7 @@ button.close { display: none !important; } } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -5865,7 +5865,7 @@ button.close { * limitations under the License. */ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -5896,7 +5896,7 @@ body { word-wrap: break-word; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -5938,7 +5938,7 @@ body { margin-right: 5px; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -5969,7 +5969,7 @@ body { color: #fff; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -5992,7 +5992,7 @@ body { letter-spacing: 0; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -6012,7 +6012,7 @@ body { margin-bottom: 0; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -6090,7 +6090,7 @@ body { color: #fff; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -6126,7 +6126,7 @@ body { border-color: #d9534f; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -6202,7 +6202,7 @@ body { position: initial; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -6246,26 +6246,29 @@ body { border-color: #ededed; padding: 12px 0; } .modal-drive-selector-modal .list-group-item .list-group-item-section-expanded { - flex-grow: 1; } + flex-grow: 1; + margin-left: 15px; } .modal-drive-selector-modal .list-group-item .list-group-item-section + .list-group-item-section { - margin-left: 10px; } + margin-left: 10px; + display: inline-block; + vertical-align: middle; } .modal-drive-selector-modal .list-group-item > .tick { font-size: 11px; } .modal-drive-selector-modal .list-group-item:first-child { border-top: 0; } .modal-drive-selector-modal .list-group-item[disabled] .list-group-item-heading { color: #b3b3b3; } - .modal-drive-selector-modal .list-group-item progress { + .modal-drive-selector-modal .list-group-item .drive-init-progress { appearance: none; width: 100%; height: 2.5px; border: none; border-radius: 50% 50%; } - .modal-drive-selector-modal .list-group-item progress::-webkit-progress-bar { + .modal-drive-selector-modal .list-group-item .drive-init-progress::-webkit-progress-bar { background-color: #ececec; border: none; outline: none; } - .modal-drive-selector-modal .list-group-item progress::-webkit-progress-value { + .modal-drive-selector-modal .list-group-item .drive-init-progress::-webkit-progress-value { border-bottom: 1px solid #176a9c; background-color: #2297de; } @@ -6287,7 +6290,7 @@ svg-icon { height: 100%; } /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -6301,79 +6304,13 @@ svg-icon { * See the License for the specific language governing permissions and * limitations under the License. */ -.modal-tooltip-modal .modal-body { - text-align: center; - margin: 15px; - color: #666; - background-color: #f2f2f2; - word-wrap: break-word; } - -/* - * 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-warning-modal .modal-content { - width: 350px; } - -.modal-warning-modal .modal-title .glyphicon, .modal-warning-modal .modal-title .tick { - color: #d9534f; } - -.modal-warning-modal .modal-body { - max-height: 200px; - overflow-y: auto; } - -/* - * Copyright 2018 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-file-selector-modal { - width: calc(100vw - 10px); } - .modal-file-selector-modal > .modal-content { - height: calc(100vh - 20px); } - -/* - * 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. - */ -svg-icon > img[disabled] { +img[disabled] { opacity: 0.2; } .page-main { flex: 1; - align-self: center; } + align-self: center; + margin: 20px; } .page-main > .col-xs { height: 165px; } @@ -6409,21 +6346,6 @@ svg-icon > img[disabled] { font-size: 16px; font-weight: 300; } -.page-main .step-border-left, .page-main .step-border-right { - height: 2px; - background-color: #fff; - position: absolute; - width: 124px; - top: 19px; } - .page-main .step-border-left[disabled], .page-main .step-border-right[disabled] { - background-color: #787c7f; } - -.page-main .step-border-left { - left: -67px; } - -.page-main .step-border-right { - right: -67px; } - .page-main .step-tooltip { display: block; margin: -5px auto -20px; @@ -6448,10 +6370,6 @@ svg-icon > img[disabled] { justify-content: space-between; width: 170px; } -.page-main .step-footer-underline { - border-bottom: 1px dotted; - padding-bottom: 2px; } - .page-main .button.step-footer { font-size: 16px; color: #2297de; @@ -6512,8 +6430,7 @@ svg-icon > img[disabled] { .target-status-line { display: flex; align-items: baseline; - font-size: 16px; - font-family: inherit; } + margin-bottom: 9px; } .target-status-line > .target-status-dot { width: 12px; height: 12px; @@ -6536,20 +6453,8 @@ svg-icon > img[disabled] { .space-vertical-large { position: relative; } -body.rendition-modal-open > div:last-child > div > div > div:last-child { - top: unset; - bottom: -200px; } - -#app-logo { - position: fixed; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - width: 123px; } - /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -6564,10 +6469,10 @@ body.rendition-modal-open > div:last-child > div > div > div:last-child { * limitations under the License. */ .page-finish { - margin-top: -15px; - flex: 1; } - .page-finish .col-xs-5.inline-flex.items-baseline > span, .page-finish .col-xs-5.inline-flex.items-baseline > div { - margin-bottom: -10px; } + margin-top: 60px; } + +.col-xs-5.inline-flex.items-baseline > span, .col-xs-5.inline-flex.items-baseline > div { + margin-bottom: -10px; } .page-finish .button-label { margin: 0 auto 15px; @@ -9931,17 +9836,10 @@ body { padding: 0; } .section-footer-main .svg-icon { margin: 0 13px; } - .section-footer-main .caption[os-open-external] { - border-bottom: 1px dashed; - padding-bottom: 2px; } - .section-footer-main .caption[os-open-external]:hover { - color: #85898c; } .section-footer-main .footer-right { font-size: 10px; position: absolute; right: 0; } - .section-footer-main > span[os-open-external] { - display: flex; } .section-loader webview { flex: 0 1; @@ -9960,25 +9858,12 @@ body { height: 100%; margin: 20px 50px; } -.section-header { - text-align: right; - padding: 13px 14px; } - .section-header > .button { - padding: 0; } - .section-header > .button > .glyphicon, .section-header > .button > .tick { - font-size: 24px; } - .section-header > * { - display: inline-block; - vertical-align: middle; - height: 24px; - margin: 0 10px; } - -featured-project webview { +.featured-project webview { flex: 0 1; height: 0; width: 0; } -featured-project.fp-visible webview { +.featured-project.fp-visible webview { width: 480px; height: 360px; position: absolute; diff --git a/lib/gui/etcher.js b/lib/gui/etcher.js index c5da7b39..8f054ce9 100644 --- a/lib/gui/etcher.js +++ b/lib/gui/etcher.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/gui/menu.js b/lib/gui/menu.js index e3209e2d..44a4f9ad 100644 --- a/lib/gui/menu.js +++ b/lib/gui/menu.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,17 +49,6 @@ const buildWindowMenu = (window) => { } } - /** - * @summary Open the main window's settings page - * @example - * showSettings() - */ - const showSettings = () => { - if (window) { - window.webContents.send('menu:preferences') - } - } - const menuTemplate = [ { role: 'editMenu' @@ -96,7 +85,7 @@ const buildWindowMenu = (window) => { { label: 'Report an issue', click () { - electron.shell.openExternal('https://github.com/resin-io/etcher/issues') + electron.shell.openExternal('https://github.com/balena-io/etcher/issues') } } ] @@ -111,12 +100,6 @@ const buildWindowMenu = (window) => { label: 'About Etcher' }, { type: 'separator' - }, { - label: 'Preferences', - accelerator: 'Command+,', - click: showSettings - }, { - type: 'separator' }, { role: 'hide' }, { @@ -133,9 +116,6 @@ const buildWindowMenu = (window) => { menuTemplate.unshift({ label: packageJson.displayName, submenu: [ { - label: 'Settings', - click: showSettings - }, { role: 'quit' } ] }) diff --git a/lib/gui/modules/child-writer.js b/lib/gui/modules/child-writer.js index ba613e7d..a60e0358 100644 --- a/lib/gui/modules/child-writer.js +++ b/lib/gui/modules/child-writer.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/drive-constraints.js b/lib/shared/drive-constraints.js index f4924628..15c4f2f8 100644 --- a/lib/shared/drive-constraints.js +++ b/lib/shared/drive-constraints.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/errors.js b/lib/shared/errors.js index 7756cb6c..f69b31ad 100644 --- a/lib/shared/errors.js +++ b/lib/shared/errors.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/exit-codes.js b/lib/shared/exit-codes.js index e412aef0..8637c869 100644 --- a/lib/shared/exit-codes.js +++ b/lib/shared/exit-codes.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/file-extensions.js b/lib/shared/file-extensions.js index 05c27b92..b56f748e 100644 --- a/lib/shared/file-extensions.js +++ b/lib/shared/file-extensions.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/messages.js b/lib/shared/messages.js index 6f0733b3..f26b6275 100644 --- a/lib/shared/messages.js +++ b/lib/shared/messages.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/permissions.js b/lib/shared/permissions.js index c0bbd052..a8ee05fd 100755 --- a/lib/shared/permissions.js +++ b/lib/shared/permissions.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/supported-formats.js b/lib/shared/supported-formats.js index 3d26dfa8..09592b51 100644 --- a/lib/shared/supported-formats.js +++ b/lib/shared/supported-formats.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/units.js b/lib/shared/units.js index 6e4f5741..4c028278 100644 --- a/lib/shared/units.js +++ b/lib/shared/units.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/shared/utils.js b/lib/shared/utils.js index 88189a79..c86e6356 100755 --- a/lib/shared/utils.js +++ b/lib/shared/utils.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/start.js b/lib/start.js index 7dfe96d9..28c90d5d 100644 --- a/lib/start.js +++ b/lib/start.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0572e1ae..8a5fc39c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1042,6 +1042,41 @@ "prop-types": "^15.5.10" } }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -1175,6 +1210,12 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.9.tgz", "integrity": "sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w==" }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, "@types/optimist": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/optimist/-/optimist-0.0.29.tgz", @@ -1510,6 +1551,24 @@ "es6-promisify": "^5.0.0" } }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } + } + }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -1568,22 +1627,6 @@ "integrity": "sha512-t3eQmuAZczdOVdOQj7muCBwH+MBNwd+/FaAsV1SNp+597EQVWABQwxI6KXE0k0ZlyJ5JbtkNIKU8kGAj1znxhw==", "dev": true }, - "angular-moment": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz", - "integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==", - "requires": { - "moment": ">=2.8.0 <3.0.0" - } - }, - "angular-seconds-to-date": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/angular-seconds-to-date/-/angular-seconds-to-date-1.0.1.tgz", - "integrity": "sha1-mTi6xPKkeyvJVc0h0TwU8s3odj0=", - "requires": { - "angular": "^1.5.6" - } - }, "angular-ui-bootstrap": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/angular-ui-bootstrap/-/angular-ui-bootstrap-2.5.6.tgz", @@ -1665,6 +1708,12 @@ "color-convert": "^1.9.0" } }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true + }, "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2014,6 +2063,12 @@ "es-abstract": "^1.7.0" } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", @@ -2135,6 +2190,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "attr-accept": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.0.0.tgz", + "integrity": "sha512-I9SDP4Wvh2ItYYoafEg8hFpsBe96pfQ+eabceShXt3sw2fbIP96+Aoj9zZE0vkZNAkXXzHJATVRuWz+h9FxJxQ==" + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -2911,6 +2971,23 @@ } } }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -3253,6 +3330,12 @@ "source-map": "~0.6.0" } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-boxes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", @@ -3268,6 +3351,24 @@ "restore-cursor": "^2.0.0" } }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true, + "requires": { + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + }, + "dependencies": { + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + } + } + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -3646,6 +3747,30 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, "crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -4189,6 +4314,12 @@ "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", "dev": true }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -4215,6 +4346,12 @@ "mimic-response": "^2.0.0" } }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -4325,6 +4462,39 @@ } } }, + "del": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", + "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", + "dev": true, + "requires": { + "globby": "^10.0.1", + "graceful-fs": "^4.2.2", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.1", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true + }, + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4664,6 +4834,23 @@ "randombytes": "^2.0.0" } }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, "dmg-builder": { "version": "21.2.0", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-21.2.0.tgz", @@ -5221,6 +5408,12 @@ "is-electron-renderer": "^2.0.0" } }, + "elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", + "dev": true + }, "elliptic": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", @@ -6313,6 +6506,30 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, + "fast-glob": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz", + "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -6330,6 +6547,15 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, "fatfs": { "version": "0.10.7", "resolved": "https://registry.npmjs.org/fatfs/-/fatfs-0.10.7.tgz", @@ -6400,6 +6626,14 @@ "object-assign": "^4.0.1" } }, + "file-selector": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.12.tgz", + "integrity": "sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==", + "requires": { + "tslib": "^1.9.0" + } + }, "file-type": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", @@ -6867,6 +7101,12 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-own-enumerable-property-symbols": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz", + "integrity": "sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA==", + "dev": true + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -7010,6 +7250,41 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "dependencies": { + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + } + } + }, "globule": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", @@ -7521,6 +7796,154 @@ "debug": "^3.1.0" } }, + "husky": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-3.1.0.tgz", + "integrity": "sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "ci-info": "^2.0.0", + "cosmiconfig": "^5.2.1", + "execa": "^1.0.0", + "get-stdin": "^7.0.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "read-pkg": "^5.2.0", + "run-node": "^1.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + } + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7559,6 +7982,33 @@ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=" }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -7837,6 +8287,12 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, "is-electron": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.0.tgz", @@ -7932,6 +8388,21 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, + "is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "requires": { + "symbol-observable": "^1.1.0" + } + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", @@ -8289,6 +8760,264 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "lint-staged": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.5.0.tgz", + "integrity": "sha512-nawMob9cb/G1J98nb8v3VC/E8rcX1rryUYXVZ69aT9kde6YWX+uvNOEHY5yf2gcWcTJGiD0kqXmCnS3oD75GIA==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "commander": "^2.20.0", + "cosmiconfig": "^5.2.1", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "del": "^5.0.0", + "execa": "^2.0.3", + "listr": "^0.14.3", + "log-symbols": "^3.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.1.1", + "string-argv": "^0.3.0", + "stringify-object": "^3.3.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "execa": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", + "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^3.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", + "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "listr": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "dev": true, + "requires": { + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } + } + }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true + }, + "listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } + } + } + }, + "listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -8713,6 +9442,18 @@ "is-what": "^3.3.1" } }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true + }, "mermaid": { "version": "8.4.3", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.3.tgz", @@ -9174,11 +9915,6 @@ "sinon-chai": "^2.8.0" } }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, "moment-mini": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.22.1.tgz", @@ -9952,6 +10688,12 @@ "mimic-fn": "^1.0.0" } }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -10071,6 +10813,15 @@ "p-limit": "^2.0.0" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -10348,6 +11099,15 @@ } } }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "plist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", @@ -10762,6 +11522,28 @@ "scheduler": "^0.17.0" } }, + "react-dropzone": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.2.1.tgz", + "integrity": "sha512-Me5nOu8hK9/Xyg5easpdfJ6SajwUquqYR/2YTdMotsCUgJ1pHIIwNsv0n+qcIno0tWR2V2rVQtj2r/hXYs2TnQ==", + "requires": { + "attr-accept": "^2.0.0", + "file-selector": "^0.1.12", + "prop-types": "^15.7.2" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, "react-google-recaptcha": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.0.1.tgz", @@ -11512,6 +12294,12 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rgb2hex": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.9.tgz", @@ -11550,6 +12338,18 @@ "is-promise": "^2.1.0" } }, + "run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -11579,6 +12379,15 @@ "rx-lite": "*" } }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -12115,6 +12924,12 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", @@ -12295,6 +13110,12 @@ "integrity": "sha1-FeSd/U8EQmDib8c8AUJlEYUhN7Y=", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", @@ -12703,6 +13524,12 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -12747,6 +13574,17 @@ "safe-buffer": "~5.1.0" } }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -12776,6 +13614,12 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", @@ -14834,4 +15678,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 9d1e9d49..afc8f5eb 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,18 @@ "concourse-test": "npx npm@6.7.0 test", "concourse-test-electron": "npx npm@6.7.0 test" }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "./**/*.{ts,tsx}": [ + "npm run prettier", + "make lint-ts", + "git add" + ] + }, "author": "Balena Inc. ", "license": "Apache-2.0", "platformSpecificDependencies": [ @@ -46,8 +58,6 @@ "@fortawesome/react-fontawesome": "^0.1.7", "angular": "1.7.6", "angular-if-state": "^1.0.0", - "angular-moment": "^1.0.1", - "angular-seconds-to-date": "^1.0.0", "angular-ui-bootstrap": "^2.5.0", "angular-ui-router": "^0.4.2", "bindings": "^1.3.0", @@ -70,6 +80,7 @@ "prop-types": "^15.5.9", "react": "^16.8.5", "react-dom": "^16.8.5", + "react-dropzone": "^10.2.1", "react2angular": "^4.0.2", "redux": "^3.5.2", "rendition": "^11.24.0", @@ -107,6 +118,8 @@ "eslint-plugin-standard": "^3.0.1", "html-angular-validate": "^0.2.3", "html-loader": "^0.5.1", + "husky": "^3.1.0", + "lint-staged": "^9.5.0", "mocha": "^6.2.1", "mochainon": "^2.0.0", "node-gyp": "^3.8.0", diff --git a/scripts/ci/ensure-all-file-extensions-in-gitattributes.sh b/scripts/ci/ensure-all-file-extensions-in-gitattributes.sh index 7f53b1b4..8972c16e 100755 --- a/scripts/ci/ensure-all-file-extensions-in-gitattributes.sh +++ b/scripts/ci/ensure-all-file-extensions-in-gitattributes.sh @@ -1,7 +1,7 @@ #!/bin/bash ### -# Copyright 2017 resin.io +# Copyright 2017 balena.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/scripts/ci/ensure-npm-dependencies-compatibility.sh b/scripts/ci/ensure-npm-dependencies-compatibility.sh index 93030212..eb0edc6c 100755 --- a/scripts/ci/ensure-npm-dependencies-compatibility.sh +++ b/scripts/ci/ensure-npm-dependencies-compatibility.sh @@ -1,7 +1,7 @@ #!/bin/bash ### -# Copyright 2017 resin.io +# Copyright 2017 balena.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/scripts/ci/ensure-staged-sass.sh b/scripts/ci/ensure-staged-sass.sh index f6c81690..c0b6cbc2 100755 --- a/scripts/ci/ensure-staged-sass.sh +++ b/scripts/ci/ensure-staged-sass.sh @@ -1,7 +1,7 @@ #!/bin/bash ### -# Copyright 2017 resin.io +# Copyright 2017 balena.io # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/scripts/html-lint.js b/scripts/html-lint.js index dc19a45c..946ff5de 100644 --- a/scripts/html-lint.js +++ b/scripts/html-lint.js @@ -26,15 +26,10 @@ angularValidate.validate( ], { customtags: [ - 'settings' + 'flash' ], customattrs: [ - // Internal - 'os-open-external', - 'os-dropzone', - 'manifest-bind', - // External 'hide-if-state', 'show-if-state', diff --git a/src/elevator_init.cpp b/src/elevator_init.cpp index 2bbf23c5..0f264e1a 100644 --- a/src/elevator_init.cpp +++ b/src/elevator_init.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/os/elevate.h b/src/os/elevate.h index 6ab9d06e..834805f0 100644 --- a/src/os/elevate.h +++ b/src/os/elevate.h @@ -2,7 +2,7 @@ #define SRC_OS_ELEVATE_H_ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/os/win32/elevate.cpp b/src/os/win32/elevate.cpp index f06c47b1..93b9e27a 100644 --- a/src/os/win32/elevate.cpp +++ b/src/os/win32/elevate.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/utils/v8utils.cpp b/src/utils/v8utils.cpp index 6bca61b7..6f2060e1 100644 --- a/src/utils/v8utils.cpp +++ b/src/utils/v8utils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/utils/v8utils.h b/src/utils/v8utils.h index 6807a1c7..cdaa1a3f 100644 --- a/src/utils/v8utils.h +++ b/src/utils/v8utils.h @@ -2,7 +2,7 @@ #define SRC_UTILS_V8UTILS_H_ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/components/drive-selector.spec.js b/tests/gui/components/drive-selector.spec.js deleted file mode 100644 index 0931ef07..00000000 --- a/tests/gui/components/drive-selector.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2017 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 m = require('mochainon') -const angular = require('angular') -const utils = require('../../../lib/shared/utils') - -describe('Browser: DriveSelector', function () { - describe('DriveSelectorController', function () { - describe('.memoize()', function () { - it('should handle equal angular objects with different hashes', function () { - const memoizedParameter = utils.memoize(_.identity, angular.equals) - const angularObjectA = { - $$hashKey: 1, - keyA: true - } - const angularObjectB = { - $$hashKey: 2, - keyA: true - } - - m.chai.expect(memoizedParameter(angularObjectA)).to.equal(angularObjectA) - m.chai.expect(memoizedParameter(angularObjectB)).to.equal(angularObjectA) - }) - }) - }) -}) diff --git a/tests/gui/components/modal.spec.js b/tests/gui/components/modal.spec.js index f385b8cb..c02064d3 100644 --- a/tests/gui/components/modal.spec.js +++ b/tests/gui/components/modal.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/components/svg-icon.spec.js b/tests/gui/components/svg-icon.spec.js index b0022ab4..b838e403 100644 --- a/tests/gui/components/svg-icon.spec.js +++ b/tests/gui/components/svg-icon.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ describe('Browser: SVGIcon', function () { iconContents[0] = `` iconContents = iconContents.join('\n') - const element = $compile(`Resin.io`)($rootScope) + const element = $compile(`Balena.io`)($rootScope) $rootScope.$digest() // We parse the SVGs to get rid of discrepancies caused by string differences @@ -72,7 +72,7 @@ describe('Browser: SVGIcon', function () { iconContents[0] = `` iconContents = iconContents.join('\n') - const element = $compile(`Resin.io`)($rootScope) + const element = $compile(`Balena.io`)($rootScope) $rootScope.$digest() // We parse the SVGs to get rid of discrepancies caused by string differences @@ -91,7 +91,7 @@ describe('Browser: SVGIcon', function () { const imgData = `data:image/svg+xml,${encodeURIComponent(iconContents)}` $rootScope.iconContents = iconContents - const element = $compile('Resin.io')($rootScope) + const element = $compile('Balena.io')($rootScope) $rootScope.$digest() m.chai.expect(element.children().attr('src')).to.equal(imgData) }) @@ -101,7 +101,7 @@ describe('Browser: SVGIcon', function () { const imgData = `data:image/svg+xml,${encodeURIComponent(iconContents)}` $rootScope.iconContents = iconContents - const svg = `Resin.io` + const svg = `Balena.io` const element = $compile(svg)($rootScope) $rootScope.$digest() m.chai.expect(element.children().attr('src')).to.equal(imgData) @@ -112,27 +112,27 @@ describe('Browser: SVGIcon', function () { const iconContents = '' $rootScope.iconContents = iconContents - const element = $compile('Resin.io')($rootScope) + const element = $compile('Balena.io')($rootScope) $rootScope.$digest() m.chai.expect(element.children().attr('src')).to.be.empty }) it('should default the size to 40x40 pixels', function () { - const element = $compile(`Resin.io`)($rootScope) + const element = $compile(`Balena.io`)($rootScope) $rootScope.$digest() m.chai.expect(element.children().css('width')).to.equal('40px') m.chai.expect(element.children().css('height')).to.equal('40px') }) it('should be able to set a custom width', function () { - const element = $compile(`Resin.io`)($rootScope) + const element = $compile(`Balena.io`)($rootScope) $rootScope.$digest() m.chai.expect(element.children().css('width')).to.equal('20px') m.chai.expect(element.children().css('height')).to.equal('40px') }) it('should be able to set a custom height', function () { - const element = $compile(`Resin.io`)($rootScope) + const element = $compile(`Balena.io`)($rootScope) $rootScope.$digest() m.chai.expect(element.children().css('width')).to.equal('40px') m.chai.expect(element.children().css('height')).to.equal('20px') diff --git a/tests/gui/models/available-drives.spec.js b/tests/gui/models/available-drives.spec.js index 5b6af4fc..eacc5c69 100644 --- a/tests/gui/models/available-drives.spec.js +++ b/tests/gui/models/available-drives.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/models/files.spec.js b/tests/gui/models/files.spec.js index 8431252b..b2dd4735 100644 --- a/tests/gui/models/files.spec.js +++ b/tests/gui/models/files.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/models/flash-state.spec.js b/tests/gui/models/flash-state.spec.js index d8229d6e..e9814a71 100644 --- a/tests/gui/models/flash-state.spec.js +++ b/tests/gui/models/flash-state.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/models/selection-state.spec.js b/tests/gui/models/selection-state.spec.js index d42c8f00..9e4d1d79 100644 --- a/tests/gui/models/selection-state.spec.js +++ b/tests/gui/models/selection-state.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/models/settings.spec.js b/tests/gui/models/settings.spec.js index bbc672bd..1052562e 100644 --- a/tests/gui/models/settings.spec.js +++ b/tests/gui/models/settings.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/models/storage.spec.js b/tests/gui/models/storage.spec.js index 26b598ff..43b17bbf 100644 --- a/tests/gui/models/storage.spec.js +++ b/tests/gui/models/storage.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/modules/child-writer.spec.js b/tests/gui/modules/child-writer.spec.js index 351a5596..3b5929ec 100644 --- a/tests/gui/modules/child-writer.spec.js +++ b/tests/gui/modules/child-writer.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/os/dropzone.spec.js b/tests/gui/os/dropzone.spec.js deleted file mode 100644 index 06871ef3..00000000 --- a/tests/gui/os/dropzone.spec.js +++ /dev/null @@ -1,86 +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 m = require('mochainon') -const angular = require('angular') - -describe('Browser: OSDropzone', function () { - beforeEach(angular.mock.module( - require('../../../lib/gui/app/os/dropzone/dropzone') - )) - - describe('osDropzone', function () { - let $compile - let $rootScope - let $timeout - - beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_, _$timeout_) { - $compile = _$compile_ - $rootScope = _$rootScope_ - $timeout = _$timeout_ - })) - - it('should pass the file back to the callback as $file', function (done) { - $rootScope.onDropZone = function (file) { - m.chai.expect(file).to.deep.equal('/foo/bar') - done() - } - - const element = $compile('
Drop a file here
')($rootScope) - $rootScope.$digest() - - element[0].ondrop({ - preventDefault: angular.noop, - dataTransfer: { - files: [ - { - path: '/foo/bar' - } - ] - } - }) - - $rootScope.$digest() - $timeout.flush() - }) - - it('should pass undefined to the callback if not passing $file', function (done) { - $rootScope.onDropZone = function (file) { - m.chai.expect(file).to.be.undefined - done() - } - - const element = $compile('
Drop a file here
')($rootScope) - $rootScope.$digest() - - element[0].ondrop({ - preventDefault: angular.noop, - dataTransfer: { - files: [ - { - path: '/foo/bar' - } - ] - } - }) - - $rootScope.$digest() - $timeout.flush() - }) - }) -}) diff --git a/tests/gui/os/open-external.spec.js b/tests/gui/os/open-external.spec.js deleted file mode 100644 index 65d33a94..00000000 --- a/tests/gui/os/open-external.spec.js +++ /dev/null @@ -1,61 +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 m = require('mochainon') -const angular = require('angular') -const electron = require('electron') - -describe('Browser: OSOpenExternal', function () { - beforeEach(angular.mock.module( - require('../../../lib/gui/app/os/open-external/open-external') - )) - - describe('osOpenExternal', function () { - let $compile - let $rootScope - - beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_) { - $compile = _$compile_ - $rootScope = _$rootScope_ - })) - - it('should set the element cursor to pointer', function () { - const element = $compile('Resin.io')($rootScope) - $rootScope.$digest() - m.chai.expect(element.css('cursor')).to.equal('pointer') - }) - - it('should call Electron shell.openExternal with the attribute value', function () { - const shellExternalStub = m.sinon.stub(electron.shell, 'openExternal') - const element = $compile('Resin.io')($rootScope) - element.triggerHandler('click') - $rootScope.$digest() - m.chai.expect(shellExternalStub).to.have.been.calledWith('https://resin.io') - shellExternalStub.restore() - }) - - it('should not call Electron shell.openExternal if the attribute value is not defined', function () { - const shellExternalStub = m.sinon.stub(electron.shell, 'openExternal') - const element = $compile('Resin.io')($rootScope) - element.triggerHandler('click') - $rootScope.$digest() - m.chai.expect(shellExternalStub).to.not.have.been.called - shellExternalStub.restore() - }) - }) -}) diff --git a/tests/gui/os/window-progress.spec.js b/tests/gui/os/window-progress.spec.js index 57aa217f..84321953 100644 --- a/tests/gui/os/window-progress.spec.js +++ b/tests/gui/os/window-progress.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/os/windows-network-drives.spec.js b/tests/gui/os/windows-network-drives.spec.js index 65f1869d..628e1a8f 100644 --- a/tests/gui/os/windows-network-drives.spec.js +++ b/tests/gui/os/windows-network-drives.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/pages/finish.spec.js b/tests/gui/pages/finish.spec.js deleted file mode 100644 index b3632946..00000000 --- a/tests/gui/pages/finish.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2018 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 m = require('mochainon') -const fs = require('fs') -const angular = require('angular') - -describe('Browser: FinishPage', function () { - beforeEach(angular.mock.module( - require('../../../lib/gui/app/pages/finish/finish') - )) - - describe('page template', function () { - let $state - - beforeEach(angular.mock.inject(function (_$state_) { - $state = _$state_ - })) - - it('should match the file contents', function () { - const { - template - } = $state.get('success') - const contents = fs.readFileSync('lib/gui/app/pages/finish/templates/success.tpl.html', { - encoding: 'utf-8' - }) - m.chai.expect(template).to.equal(contents) - }) - }) -}) diff --git a/tests/gui/pages/main.spec.js b/tests/gui/pages/main.spec.js deleted file mode 100644 index 3e51f06d..00000000 --- a/tests/gui/pages/main.spec.js +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright 2017 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 m = require('mochainon') -const _ = require('lodash') -const fs = require('fs') -const path = require('path') -const supportedFormats = require('../../../lib/shared/supported-formats') -const angular = require('angular') -const flashState = require('../../../lib/gui/app/models/flash-state') -const availableDrives = require('../../../lib/gui/app/models/available-drives') -const selectionState = require('../../../lib/gui/app/models/selection-state') - -// Mock HTML requires by reading from the file-system -// eslint-disable-next-line node/no-deprecated-api -require.extensions['.html'] = (module, filename) => { - module.exports = fs.readFileSync(filename, { - encoding: 'utf8' - }) -} - -// NOTE(Shou): since we don't test React yet we just ignore JSX files -// eslint-disable-next-line node/no-deprecated-api -require.extensions['.jsx'] = _.constant(null) - -describe('Browser: MainPage', function () { - beforeEach(angular.mock.module( - require('../../../lib/gui/app/pages/main/main') - )) - - describe('MainController', function () { - let $controller - - beforeEach(angular.mock.inject(function (_$controller_) { - $controller = _$controller_ - })) - - describe('.shouldDriveStepBeDisabled()', function () { - it('should return true if there is no drive', function () { - const controller = $controller('MainController', { - $scope: {} - }) - - selectionState.clear() - - m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.true - }) - - it('should return false if there is a drive', function () { - const controller = $controller('MainController', { - $scope: {} - }) - - selectionState.selectImage({ - path: 'rpi.img', - extension: 'img', - size: 99999, - isSizeEstimated: false - }) - - m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.false - }) - }) - - describe('.shouldFlashStepBeDisabled()', function () { - it('should return true if there is no selected drive nor image', function () { - const controller = $controller('MainController', { - $scope: {} - }) - - selectionState.clear() - - m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true - }) - - it('should return true if there is a selected image but no drive', function () { - const controller = $controller('MainController', { - $scope: {} - }) - - selectionState.clear() - selectionState.selectImage({ - path: 'rpi.img', - extension: 'img', - size: 99999, - isSizeEstimated: false - }) - - m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true - }) - - it('should return true if there is a selected drive but no image', function () { - const controller = $controller('MainController', { - $scope: {} - }) - - availableDrives.setDrives([ - { - device: '/dev/disk2', - description: 'Foo', - size: 99999, - mountpoint: '/mnt/foo', - system: false - } - ]) - - selectionState.clear() - selectionState.selectDrive('/dev/disk2') - - m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true - }) - - it('should return false if there is a selected drive and a selected image', function () { - const controller = $controller('MainController', { - $scope: {} - }) - - availableDrives.setDrives([ - { - device: '/dev/disk2', - description: 'Foo', - size: 99999, - mountpoint: '/mnt/foo', - system: false - } - ]) - - selectionState.clear() - selectionState.selectDrive('/dev/disk2') - - selectionState.selectImage({ - path: 'rpi.img', - extension: 'img', - size: 99999, - isSizeEstimated: false - }) - - m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.false - }) - }) - }) - - describe('ImageSelectionController', function () { - let $controller - - beforeEach(angular.mock.inject(function (_$controller_) { - $controller = _$controller_ - })) - - it('should contain all available extensions in mainSupportedExtensions and extraSupportedExtensions', function () { - const $scope = {} - const controller = $controller('ImageSelectionController', { - $scope - }) - - const extensions = controller.mainSupportedExtensions.concat(controller.extraSupportedExtensions) - m.chai.expect(_.sortBy(extensions)).to.deep.equal(_.sortBy(supportedFormats.getAllExtensions())) - }) - - describe('.getImageBasename()', function () { - it('should return the basename of the selected image', function () { - const controller = $controller('ImageSelectionController', { - $scope: {} - }) - - selectionState.selectImage({ - path: path.join(__dirname, 'foo', 'bar.img'), - extension: 'img', - size: 999999999, - isSizeEstimated: false - }) - - m.chai.expect(controller.getImageBasename()).to.equal('bar.img') - selectionState.deselectImage() - }) - - it('should return an empty string if no selected image', function () { - const controller = $controller('ImageSelectionController', { - $scope: {} - }) - - selectionState.deselectImage() - m.chai.expect(controller.getImageBasename()).to.equal('') - }) - }) - }) - - describe('FlashController', function () { - let $controller - - beforeEach(angular.mock.inject(function (_$controller_) { - $controller = _$controller_ - })) - - describe('.getProgressButtonLabel()', function () { - it('should return "Flash!" given a clean state', function () { - const controller = $controller('FlashController', { - $scope: {} - }) - - flashState.resetState() - m.chai.expect(controller.getProgressButtonLabel()).to.equal('Flash!') - }) - - it('should display the flashing progress', function () { - const controller = $controller('FlashController', { - $scope: {} - }) - - flashState.setFlashingFlag() - flashState.setProgressState({ - flashing: 1, - verifying: 0, - successful: 0, - failed: 0, - percentage: 85, - eta: 15, - speed: 1000, - totalSpeed: 2000 - }) - m.chai.expect(controller.getProgressButtonLabel()).to.equal('85% Flashing') - }) - }) - }) - - describe('DriveSelectionController', function () { - let $controller - let DriveSelectionController - - const drivePaths = process.platform === 'win32' - ? [ '\\\\.\\PhysicalDrive1', '\\\\.\\PhysicalDrive2', '\\\\.\\PhysicalDrive3' ] - : [ '/dev/disk1', '/dev/disk2', '/dev/disk3' ] - const drives = [ - { - device: drivePaths[0], - description: 'My Drive', - size: 123456789, - displayName: drivePaths[0], - mountpoints: [ drivePaths[0] ], - isSystem: false, - isReadOnly: false - }, - { - device: drivePaths[1], - description: 'My Other Drive', - size: 987654321, - displayName: drivePaths[1], - mountpoints: [ drivePaths[1] ], - isSystem: false, - isReadOnly: false - }, - { - device: drivePaths[2], - size: 987654321, - displayName: drivePaths[2], - mountpoints: [], - isSystem: false, - isReadOnly: false - } - ] - - beforeEach(angular.mock.inject(function (_$controller_) { - $controller = _$controller_ - DriveSelectionController = $controller('DriveSelectionController', { - $scope: {} - }) - - availableDrives.setDrives(drives) - })) - - afterEach(() => { - selectionState.clear() - }) - - describe('.getDrivesTitle()', function () { - it('should return the drive description when there is one drive', function () { - selectionState.selectDrive(drives[0].device) - m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal(drives[0].description) - }) - - it('should return untitled when there is no description', function () { - selectionState.selectDrive(drives[2].device) - m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal('Untitled Device') - }) - - it('should return a consolidated title with quantity when there are multiple drives', function () { - selectionState.selectDrive(drives[0].device) - selectionState.selectDrive(drives[1].device) - m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal('2 Devices') - }) - }) - - describe('.getDriveListLabel()', function () { - it('should return the drive description and display name when there is one drive', function () { - const label = `${drives[0].description} (${drives[0].displayName})` - selectionState.selectDrive(drives[0].device) - m.chai.expect(DriveSelectionController.getDriveListLabel()).to.equal(label) - }) - - it('should return drive descriptions and display names of all drives separated by newlines', function () { - const label = `${drives[0].description} (${drives[0].displayName})\n${drives[1].description} (${drives[1].displayName})` - selectionState.selectDrive(drives[0].device) - selectionState.selectDrive(drives[1].device) - m.chai.expect(DriveSelectionController.getDriveListLabel()).to.equal(label) - }) - }) - }) - - describe('page template', function () { - let $state - - beforeEach(angular.mock.inject(function (_$state_) { - $state = _$state_ - })) - - it('should match the file contents', function () { - const { - template - } = $state.get('main') - const contents = fs.readFileSync('lib/gui/app/pages/main/templates/main.tpl.html', { - encoding: 'utf-8' - }) - m.chai.expect(template).to.equal(contents) - }) - }) -}) diff --git a/tests/gui/utils/byte-size.spec.js b/tests/gui/utils/byte-size.spec.js index 5dd62a39..d5104b5b 100644 --- a/tests/gui/utils/byte-size.spec.js +++ b/tests/gui/utils/byte-size.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/gui/utils/manifest-bind.spec.js b/tests/gui/utils/manifest-bind.spec.js deleted file mode 100644 index f90c9e74..00000000 --- a/tests/gui/utils/manifest-bind.spec.js +++ /dev/null @@ -1,78 +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 m = require('mochainon') -const angular = require('angular') -const packageJSON = require('../../../package.json') - -describe('Browser: ManifestBind', function () { - beforeEach(angular.mock.module( - require('../../../lib/gui/app/utils/manifest-bind/manifest-bind') - )) - - let $compile - let $rootScope - - beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_) { - $compile = _$compile_ - $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 () { - it('should bind to top level properties', function () { - const element = $compile('')($rootScope) - $rootScope.$digest() - m.chai.expect(element.html()).to.equal(packageJSON.version) - }) - - it('should bind to nested properties', function () { - const element = $compile('')($rootScope) - $rootScope.$digest() - m.chai.expect(element.html()).to.equal(packageJSON.repository.type) - }) - - it('should throw if the property does not exist', function () { - m.chai.expect(function () { - $compile('')($rootScope) - }).to.throw('ManifestBind: Unknown property `foo.bar`') - }) - }) -}) diff --git a/tests/gui/utils/middle-ellipsis.spec.js b/tests/gui/utils/middle-ellipsis.spec.js index 5df0af41..cec6f7d2 100644 --- a/tests/gui/utils/middle-ellipsis.spec.js +++ b/tests/gui/utils/middle-ellipsis.spec.js @@ -1,6 +1,6 @@ /* - * Copyright 2018 resin.io + * Copyright 2018 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/drive-constraints.spec.js b/tests/shared/drive-constraints.spec.js index edd501dd..36d6cd0e 100644 --- a/tests/shared/drive-constraints.spec.js +++ b/tests/shared/drive-constraints.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/errors.spec.js b/tests/shared/errors.spec.js index adb185c8..758c3436 100644 --- a/tests/shared/errors.spec.js +++ b/tests/shared/errors.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/file-extensions.spec.js b/tests/shared/file-extensions.spec.js index 7acc037f..e5f241b6 100644 --- a/tests/shared/file-extensions.spec.js +++ b/tests/shared/file-extensions.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/messages.spec.js b/tests/shared/messages.spec.js index b1ed5193..39a6ea8e 100644 --- a/tests/shared/messages.spec.js +++ b/tests/shared/messages.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/permissions.spec.js b/tests/shared/permissions.spec.js index 9a48c44a..c80f5459 100644 --- a/tests/shared/permissions.spec.js +++ b/tests/shared/permissions.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/supported-formats.spec.js b/tests/shared/supported-formats.spec.js index b06081e4..540d1f6b 100644 --- a/tests/shared/supported-formats.spec.js +++ b/tests/shared/supported-formats.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/units.spec.js b/tests/shared/units.spec.js index 77cb8186..c486dcc2 100644 --- a/tests/shared/units.spec.js +++ b/tests/shared/units.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2016 resin.io + * Copyright 2016 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/shared/utils.spec.js b/tests/shared/utils.spec.js index 88625c99..c61094dd 100644 --- a/tests/shared/utils.spec.js +++ b/tests/shared/utils.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/spectron/runner.spec.js b/tests/spectron/runner.spec.js index fc8e01f1..a657fd9a 100644 --- a/tests/spectron/runner.spec.js +++ b/tests/spectron/runner.spec.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/webpack.config.js b/webpack.config.js index b0bc3bd0..331ab0bd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,5 @@ /* - * Copyright 2017 resin.io + * Copyright 2017 balena.io * * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. @@ -79,7 +79,8 @@ const guiConfig = { }, entry: { gui: path.join(__dirname, 'lib', 'gui', 'app', 'app.js') - } + }, + devtool: 'source-map' } const etcherConfig = {