From abfc6be84d971670fd2914432caaa42263cfe260 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Tue, 3 Dec 2019 19:37:18 +0200 Subject: [PATCH] Convert the drive selection step to React Change-type: patch Changelog-entry: Convert the drive selection step to React Signed-off-by: Thodoris Greasidis --- lib/gui/app/pages/main/DriveSelector.jsx | 243 ++++++++++++++++++ .../pages/main/controllers/drive-selection.js | 159 ------------ lib/gui/app/pages/main/main.js | 11 +- .../app/pages/main/templates/main.tpl.html | 35 +-- tests/gui/pages/main.spec.js | 83 ------ 5 files changed, 261 insertions(+), 270 deletions(-) create mode 100644 lib/gui/app/pages/main/DriveSelector.jsx delete mode 100644 lib/gui/app/pages/main/controllers/drive-selection.js diff --git a/lib/gui/app/pages/main/DriveSelector.jsx b/lib/gui/app/pages/main/DriveSelector.jsx new file mode 100644 index 00000000..3456d1a2 --- /dev/null +++ b/lib/gui/app/pages/main/DriveSelector.jsx @@ -0,0 +1,243 @@ +/* + * 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 prettyBytes = require('pretty-bytes') +const propTypes = require('prop-types') +const React = require('react') +const driveConstraints = require('../../../../shared/drive-constraints') +const utils = require('../../../../shared/utils') +const TargetSelector = require('../../components/drive-selector/target-selector') +const SvgIcon = require('../../components/svg-icon/svg-icon.jsx') +const selectionState = require('../../models/selection-state') +const settings = require('../../models/settings') +const store = require('../../models/store') +const analytics = require('../../modules/analytics') +const exceptionReporter = require('../../modules/exception-reporter') + +/** + * @summary Get drive title based on device quantity + * @function + * @public + * + * @returns {String} - drives title + * + * @example + * console.log(getDrivesTitle()) + * > 'Multiple Drives (4)' + */ +const 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(getDrivesSubtitle()) + * > '32 GB' + */ +const 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(getDriveListLabel()) + * > 'My Drive (/dev/disk1)\nMy Other Drive (/dev/disk2)' + */ +const getDriveListLabel = () => { + return _.join(_.map(selectionState.getSelectedDrives(), (drive) => { + return `${drive.description} (${drive.displayName})` + }), '\n') +} + +/** + * @summary Open drive selector + * @function + * @public + * @param {Object} DriveSelectorService - drive selector service + * + * @example + * openDriveSelector(DriveSelectorService); + */ +const openDriveSelector = (DriveSelectorService) => { + 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 + * @param {Object} DriveSelectorService - drive selector service + * + * @example + * reselectDrive(DriveSelectorService); + */ +const reselectDrive = (DriveSelectorService) => { + openDriveSelector(DriveSelectorService) + analytics.logEvent('Reselect drive', { + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid + }) +} + +/** + * @summary Get memoized selected drives + * @function + * @public + * + * @example + * getMemoizedSelectedDrives() + */ +const getMemoizedSelectedDrives = utils.memoize(selectionState.getSelectedDrives, _.isEqual) + +/** + * @summary Should the drive selection button be shown + * @function + * @public + * + * @returns {Boolean} + * + * @example + * shouldShowDrivesButton() + */ +const shouldShowDrivesButton = () => { + return !settings.get('disableExplicitDriveSelection') +} + +const getDriveSelectionStateSlice = () => ({ + showDrivesButton: shouldShowDrivesButton(), + driveListLabel: getDriveListLabel(), + targets: getMemoizedSelectedDrives() +}) + +const DriveSelector = ({ + webviewShowing, + disabled, + nextStepDisabled, + hasDrive, + flashing, + DriveSelectorService +}) => { + // TODO: inject these from redux-connector + const [ { + showDrivesButton, + driveListLabel, + targets + }, setStateSlice ] = React.useState(getDriveSelectionStateSlice()) + + React.useEffect(() => { + return store.observe(() => { + setStateSlice(getDriveSelectionStateSlice()) + }) + }, []) + + const showStepConnectingLines = !webviewShowing || !flashing + + return ( +
+ + {showStepConnectingLines && ( + +
+
+
+ )} + +
+ +
+ +
+ openDriveSelector(DriveSelectorService)} + reselectDrive={() => reselectDrive(DriveSelectorService)} + flashing={flashing} + constraints={driveConstraints} + targets={targets} + /> +
+ +
+ ) +} + +DriveSelector.propTypes = { + webviewShowing: propTypes.bool, + disabled: propTypes.bool, + nextStepDisabled: propTypes.bool, + hasDrive: propTypes.bool, + flashing: propTypes.bool +} + +module.exports = DriveSelector 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/main.js b/lib/gui/app/pages/main/main.js index d0862407..24e9389c 100644 --- a/lib/gui/app/pages/main/main.js +++ b/lib/gui/app/pages/main/main.js @@ -49,7 +49,16 @@ const MainPage = angular.module(MODULE_NAME, [ ]) MainPage.controller('MainController', require('./controllers/main')) -MainPage.controller('DriveSelectionController', require('./controllers/drive-selection')) +MainPage.component('driveSelector', react2angular(require('./DriveSelector.jsx'), + [ + 'webviewShowing', + 'disabled', + 'nextStepDisabled', + 'hasDrive', + 'flashing' + ], + [ 'DriveSelectorService' ] +)) MainPage.component('flash', react2angular(require('./Flash.jsx'), [ 'shouldFlashStepBeDisabled', 'lastFlashErrorCode', 'progressMessage' ], [ '$timeout', '$state', 'WarningModalService', 'DriveSelectorService', 'FlashErrorModalService' ])) diff --git a/lib/gui/app/pages/main/templates/main.tpl.html b/lib/gui/app/pages/main/templates/main.tpl.html index 4121b7be..c17acc03 100644 --- a/lib/gui/app/pages/main/templates/main.tpl.html +++ b/lib/gui/app/pages/main/templates/main.tpl.html @@ -7,33 +7,14 @@ -
-
- -
-
- -
- -
- -
- - -
- -
+
+
diff --git a/tests/gui/pages/main.spec.js b/tests/gui/pages/main.spec.js index d4030c78..13fc794e 100644 --- a/tests/gui/pages/main.spec.js +++ b/tests/gui/pages/main.spec.js @@ -164,89 +164,6 @@ describe('Browser: MainPage', function () { }) }) - 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