From d5eb679cf06754a3209bb0e3f672361a5dcd231f Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Fri, 3 Jan 2020 19:07:21 +0100 Subject: [PATCH] Remove remaining angular Change-type: patch --- lib/gui/app/app.js | 454 ++++++++----------- lib/gui/app/components/finish/finish.tsx | 8 +- lib/gui/app/components/finish/index.ts | 35 -- lib/gui/app/components/safe-webview/index.ts | 28 -- lib/gui/app/index.html | 16 +- lib/gui/app/models/store.js | 7 +- lib/gui/app/pages/main/Flash.tsx | 11 +- lib/gui/app/pages/main/MainPage.tsx | 331 ++++++++------ lib/gui/app/pages/main/main.ts | 40 -- lib/gui/{css/angular.css => app/tsapp.tsx} | 12 +- npm-shrinkwrap.json | 59 --- package.json | 4 - tests/gui/modules/image-writer.spec.js | 9 +- 13 files changed, 387 insertions(+), 627 deletions(-) delete mode 100644 lib/gui/app/components/finish/index.ts delete mode 100644 lib/gui/app/components/safe-webview/index.ts delete mode 100644 lib/gui/app/pages/main/main.ts rename lib/gui/{css/angular.css => app/tsapp.tsx} (72%) diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index 35dd597c..d1b6a7a4 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -20,12 +20,6 @@ 'use strict' -/* eslint-disable no-var */ - -var angular = require('angular') - -/* eslint-enable no-var */ - const electron = require('electron') const sdk = require('etcher-sdk') const _ = require('lodash') @@ -79,68 +73,51 @@ store.dispatch({ const applicationSessionUuid = store.getState().toJS().applicationSessionUuid const flashingWorkflowUuid = store.getState().toJS().flashingWorkflowUuid -const app = angular.module('Etcher', [ - require('angular-ui-router'), +console.log([ + ' _____ _ _', + '| ___| | | |', + '| |__ | |_ ___| |__ ___ _ __', + '| __|| __/ __| \'_ \\ / _ \\ \'__|', + '| |___| || (__| | | | __/ |', + '\\____/ \\__\\___|_| |_|\\___|_|', + '', + 'Interested in joining the Etcher team?', + 'Drop us a line at join+etcher@balena.io', + '', + `Version = ${packageJSON.version}, Type = ${packageJSON.packageType}` +].join('\n')) - // Components - require('./components/safe-webview').MODULE_NAME, +const currentVersion = packageJSON.version - // Pages - require('./pages/main/main.ts').MODULE_NAME, - require('./components/finish/index.ts').MODULE_NAME -]) - -app.run(() => { - console.log([ - ' _____ _ _', - '| ___| | | |', - '| |__ | |_ ___| |__ ___ _ __', - '| __|| __/ __| \'_ \\ / _ \\ \'__|', - '| |___| || (__| | | | __/ |', - '\\____/ \\__\\___|_| |_|\\___|_|', - '', - 'Interested in joining the Etcher team?', - 'Drop us a line at join+etcher@balena.io', - '', - `Version = ${packageJSON.version}, Type = ${packageJSON.packageType}` - ].join('\n')) +analytics.logEvent('Application start', { + packageType: packageJSON.packageType, + version: currentVersion, + applicationSessionUuid }) -app.run(() => { - const currentVersion = packageJSON.version +store.observe(() => { + if (!flashState.isFlashing()) { + return + } - analytics.logEvent('Application start', { - packageType: packageJSON.packageType, - version: currentVersion, - applicationSessionUuid - }) -}) + const currentFlashState = flashState.getFlashState() + const stateType = !currentFlashState.flashing && currentFlashState.verifying + ? `Verifying ${currentFlashState.verifying}` + : `Flashing ${currentFlashState.flashing}` -app.run(() => { - store.observe(() => { - if (!flashState.isFlashing()) { - return - } + // NOTE: There is usually a short time period between the `isFlashing()` + // property being set, and the flashing actually starting, which + // might cause some non-sense flashing state logs including + // `undefined` values. + analytics.logDebug( + `${stateType} devices, ` + + `${currentFlashState.percentage}% at ${currentFlashState.speed} MB/s ` + + `(total ${currentFlashState.totalSpeed} MB/s) ` + + `eta in ${currentFlashState.eta}s ` + + `with ${currentFlashState.failed} failed devices` + ) - const currentFlashState = flashState.getFlashState() - const stateType = !currentFlashState.flashing && currentFlashState.verifying - ? `Verifying ${currentFlashState.verifying}` - : `Flashing ${currentFlashState.flashing}` - - // NOTE: There is usually a short time period between the `isFlashing()` - // property being set, and the flashing actually starting, which - // might cause some non-sense flashing state logs including - // `undefined` values. - analytics.logDebug( - `${stateType} devices, ` + - `${currentFlashState.percentage}% at ${currentFlashState.speed} MB/s ` + - `(total ${currentFlashState.totalSpeed} MB/s) ` + - `eta in ${currentFlashState.eta}s ` + - `with ${currentFlashState.failed} failed devices` - ) - - windowProgress.set(currentFlashState) - }) + windowProgress.set(currentFlashState) }) /** @@ -197,242 +174,171 @@ const COMPUTE_MODULE_DESCRIPTIONS = { [USB_PRODUCT_ID_BCM2710_BOOT]: 'Compute Module 3' } -app.run(($timeout) => { - const BLACKLISTED_DRIVES = settings.has('driveBlacklist') - ? settings.get('driveBlacklist').split(',') - : [] +const BLACKLISTED_DRIVES = settings.has('driveBlacklist') + ? settings.get('driveBlacklist').split(',') + : [] - // eslint-disable-next-line require-jsdoc - const driveIsAllowed = (drive) => { - return !( - BLACKLISTED_DRIVES.includes(drive.devicePath) || - BLACKLISTED_DRIVES.includes(drive.device) || - BLACKLISTED_DRIVES.includes(drive.raw) - ) - } +// eslint-disable-next-line require-jsdoc +const driveIsAllowed = (drive) => { + return !( + BLACKLISTED_DRIVES.includes(drive.devicePath) || + BLACKLISTED_DRIVES.includes(drive.device) || + BLACKLISTED_DRIVES.includes(drive.raw) + ) +} - // eslint-disable-next-line require-jsdoc,consistent-return - const prepareDrive = (drive) => { - if (drive instanceof sdk.sourceDestination.BlockDevice) { - return drive.drive - } else if (drive instanceof sdk.sourceDestination.UsbbootDrive) { - // This is a workaround etcher expecting a device string and a size - drive.device = drive.usbDevice.portId - drive.size = null - drive.progress = 0 - drive.disabled = true - drive.on('progress', (progress) => { - updateDriveProgress(drive, progress) - }) - return drive - } else if (drive instanceof sdk.sourceDestination.DriverlessDevice) { - const description = COMPUTE_MODULE_DESCRIPTIONS[drive.deviceDescriptor.idProduct] || 'Compute Module' - return { - device: `${usbIdToString(drive.deviceDescriptor.idVendor)}:${usbIdToString(drive.deviceDescriptor.idProduct)}`, - displayName: 'Missing drivers', - description, - mountpoints: [], - isReadOnly: false, - isSystem: false, - disabled: true, - icon: 'warning', - size: null, - link: 'https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md', - linkCTA: 'Install', - linkTitle: 'Install missing drivers', - linkMessage: [ - 'Would you like to download the necessary drivers from the Raspberry Pi Foundation?', - 'This will open your browser.\n\n', - 'Once opened, download and run the installer from the "Windows Installer" section to install the drivers.' - ].join(' ') - } +// eslint-disable-next-line require-jsdoc,consistent-return +const prepareDrive = (drive) => { + if (drive instanceof sdk.sourceDestination.BlockDevice) { + return drive.drive + } else if (drive instanceof sdk.sourceDestination.UsbbootDrive) { + // This is a workaround etcher expecting a device string and a size + drive.device = drive.usbDevice.portId + drive.size = null + drive.progress = 0 + drive.disabled = true + drive.on('progress', (progress) => { + updateDriveProgress(drive, progress) + }) + return drive + } else if (drive instanceof sdk.sourceDestination.DriverlessDevice) { + const description = COMPUTE_MODULE_DESCRIPTIONS[drive.deviceDescriptor.idProduct] || 'Compute Module' + return { + device: `${usbIdToString(drive.deviceDescriptor.idVendor)}:${usbIdToString(drive.deviceDescriptor.idProduct)}`, + displayName: 'Missing drivers', + description, + mountpoints: [], + isReadOnly: false, + isSystem: false, + disabled: true, + icon: 'warning', + size: null, + link: 'https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md', + linkCTA: 'Install', + linkTitle: 'Install missing drivers', + linkMessage: [ + 'Would you like to download the necessary drivers from the Raspberry Pi Foundation?', + 'This will open your browser.\n\n', + 'Once opened, download and run the installer from the "Windows Installer" section to install the drivers.' + ].join(' ') } } +} - // eslint-disable-next-line require-jsdoc - const setDrives = (drives) => { - availableDrives.setDrives(_.values(drives)) +// eslint-disable-next-line require-jsdoc +const setDrives = (drives) => { + availableDrives.setDrives(_.values(drives)) +} - // Safely trigger a digest cycle. - // In some cases, AngularJS doesn't acknowledge that the - // available drives list has changed, and incorrectly - // keeps asking the user to "Connect a drive". - $timeout() +// eslint-disable-next-line require-jsdoc +const getDrives = () => { + return _.keyBy(availableDrives.getDrives() || [], 'device') +} + +// eslint-disable-next-line require-jsdoc +const addDrive = (drive) => { + const preparedDrive = prepareDrive(drive) + if (!driveIsAllowed(preparedDrive)) { + return } + const drives = getDrives() + drives[preparedDrive.device] = preparedDrive + setDrives(drives) +} - // eslint-disable-next-line require-jsdoc - const getDrives = () => { - return _.keyBy(availableDrives.getDrives() || [], 'device') - } +// eslint-disable-next-line require-jsdoc +const removeDrive = (drive) => { + const preparedDrive = prepareDrive(drive) + const drives = getDrives() + // eslint-disable-next-line prefer-reflect + delete drives[preparedDrive.device] + setDrives(drives) +} - // eslint-disable-next-line require-jsdoc - const addDrive = (drive) => { - const preparedDrive = prepareDrive(drive) - if (!driveIsAllowed(preparedDrive)) { - return - } - const drives = getDrives() - drives[preparedDrive.device] = preparedDrive +// eslint-disable-next-line require-jsdoc +const updateDriveProgress = (drive, progress) => { + const drives = getDrives() + const driveInMap = drives[drive.device] + if (driveInMap) { + driveInMap.progress = progress setDrives(drives) } +} - // eslint-disable-next-line require-jsdoc - const removeDrive = (drive) => { - const preparedDrive = prepareDrive(drive) - const drives = getDrives() - // eslint-disable-next-line prefer-reflect - delete drives[preparedDrive.device] - setDrives(drives) - } +driveScanner.on('attach', addDrive) +driveScanner.on('detach', removeDrive) - // eslint-disable-next-line require-jsdoc - const updateDriveProgress = (drive, progress) => { - const drives = getDrives() - const driveInMap = drives[drive.device] - if (driveInMap) { - driveInMap.progress = progress - setDrives(drives) - } - } +driveScanner.on('error', (error) => { + // Stop the drive scanning loop in case of errors, + // otherwise we risk presenting the same error over + // and over again to the user, while also heavily + // spamming our error reporting service. + driveScanner.stop() - driveScanner.on('attach', addDrive) - driveScanner.on('detach', removeDrive) - - driveScanner.on('error', (error) => { - // Stop the drive scanning loop in case of errors, - // otherwise we risk presenting the same error over - // and over again to the user, while also heavily - // spamming our error reporting service. - driveScanner.stop() - - return exceptionReporter.report(error) - }) - - driveScanner.start() + return exceptionReporter.report(error) }) -app.run(($window) => { - let popupExists = false +driveScanner.start() - $window.addEventListener('beforeunload', (event) => { - if (!flashState.isFlashing() || popupExists) { - analytics.logEvent('Close application', { - isFlashing: flashState.isFlashing(), - applicationSessionUuid - }) - return - } +let popupExists = false - // Don't close window while flashing - event.returnValue = false - - // Don't open any more popups - popupExists = true - - analytics.logEvent('Close attempt while flashing', { applicationSessionUuid, flashingWorkflowUuid }) - - osDialog.showWarning({ - confirmationLabel: 'Yes, quit', - rejectionLabel: 'Cancel', - title: 'Are you sure you want to close Etcher?', - description: messages.warning.exitWhileFlashing() - }).then((confirmed) => { - if (confirmed) { - analytics.logEvent('Close confirmed while flashing', { - flashInstanceUuid: flashState.getFlashUuid(), - applicationSessionUuid, - flashingWorkflowUuid - }) - - // This circumvents the 'beforeunload' event unlike - // electron.remote.app.quit() which does not. - electron.remote.process.exit(EXIT_CODES.SUCCESS) - } - - analytics.logEvent('Close rejected while flashing', { applicationSessionUuid, flashingWorkflowUuid }) - popupExists = false - }).catch(exceptionReporter.report) - }) - - /** - * @summary Helper fn for events - * @function - * @private - * @example - * window.addEventListener('click', extendLock) - */ - const extendLock = () => { - updateLock.extend() - } - - $window.addEventListener('click', extendLock) - $window.addEventListener('touchstart', extendLock) - - // Initial update lock acquisition - extendLock() -}) - -app.run(($rootScope) => { - $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => { - // Ignore first navigation - if (!fromState.name) { - return - } - - analytics.logEvent('Navigate', { - to: toState.name, - from: fromState.name, +window.addEventListener('beforeunload', (event) => { + if (!flashState.isFlashing() || popupExists) { + analytics.logEvent('Close application', { + isFlashing: flashState.isFlashing(), applicationSessionUuid }) - }) -}) + return + } -app.config(($urlRouterProvider) => { - $urlRouterProvider.otherwise('/main') -}) + // Don't close window while flashing + event.returnValue = false -app.config(($provide) => { - $provide.decorator('$exceptionHandler', ($delegate) => { - return (exception, cause) => { - exceptionReporter.report(exception) - $delegate(exception, cause) + // Don't open any more popups + popupExists = true + + analytics.logEvent('Close attempt while flashing', { applicationSessionUuid, flashingWorkflowUuid }) + + osDialog.showWarning({ + confirmationLabel: 'Yes, quit', + rejectionLabel: 'Cancel', + title: 'Are you sure you want to close Etcher?', + description: messages.warning.exitWhileFlashing() + }).then((confirmed) => { + if (confirmed) { + analytics.logEvent('Close confirmed while flashing', { + flashInstanceUuid: flashState.getFlashUuid(), + applicationSessionUuid, + flashingWorkflowUuid + }) + + // This circumvents the 'beforeunload' event unlike + // electron.remote.app.quit() which does not. + electron.remote.process.exit(EXIT_CODES.SUCCESS) } - }) -}) -app.config(($locationProvider) => { - // NOTE(Shou): this seems to invoke a minor perf decrease when set to true - $locationProvider.html5Mode({ - rewriteLinks: false - }) -}) - -app.controller('StateController', function ($rootScope, $scope) { - const unregisterStateChange = $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => { - this.currentName = toState.name - }) - - $scope.$on('$destroy', unregisterStateChange) - - /** - * @summary Get the current state name - * @function - * @public - * - * @returns {String} current state name - * - * @example - * if (StateController.currentName === 'main') { - * console.log('We are on the main screen!'); - * } - */ - this.currentName = null -}) - -// Ensure user settings are loaded before -// we bootstrap the Angular.js application -angular.element(document).ready(() => { - settings.load().then(() => { - angular.bootstrap(document, [ 'Etcher' ]) + analytics.logEvent('Close rejected while flashing', { applicationSessionUuid, flashingWorkflowUuid }) + popupExists = false }).catch(exceptionReporter.report) }) + +/** + * @summary Helper fn for events + * @function + * @private + * @example + * window.addEventListener('click', extendLock) + */ +const extendLock = () => { + updateLock.extend() +} + +window.addEventListener('click', extendLock) +window.addEventListener('touchstart', extendLock) + +// Initial update lock acquisition +extendLock() + +settings.load().catch(exceptionReporter.report) + +require('./tsapp.tsx') diff --git a/lib/gui/app/components/finish/finish.tsx b/lib/gui/app/components/finish/finish.tsx index e3d2a7e4..c76bc77a 100644 --- a/lib/gui/app/components/finish/finish.tsx +++ b/lib/gui/app/components/finish/finish.tsx @@ -29,7 +29,7 @@ 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 restart = (options: any, goToMain: () => void) => { const { applicationSessionUuid, flashingWorkflowUuid, @@ -54,7 +54,7 @@ const restart = (options: any, $state: any) => { data: uuidV4(), }); - $state.go('main'); + goToMain(); }; const formattedErrors = () => { @@ -67,7 +67,7 @@ const formattedErrors = () => { return errors.join('\n'); }; -function FinishPage({ $state }: any) { +function FinishPage({ goToMain }: { goToMain: () => void }) { // @ts-ignore const results = flashState.getFlashResults().results || {}; const progressMessage = messages.progress; @@ -82,7 +82,7 @@ function FinishPage({ $state }: any) { > restart(options, $state)} + onClick={(options: any) => restart(options, goToMain)} > diff --git a/lib/gui/app/components/finish/index.ts b/lib/gui/app/components/finish/index.ts deleted file mode 100644 index 25c580cb..00000000 --- a/lib/gui/app/components/finish/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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. - */ - -/** - * @module Etcher.Pages.Finish - */ - -import * as angular from 'angular'; -import { react2angular } from 'react2angular'; -import FinishPage from './finish'; - -export const MODULE_NAME = 'Etcher.Pages.Finish'; -const Finish = angular.module(MODULE_NAME, []); - -Finish.component('finish', react2angular(FinishPage, [], ['$state'])); - -Finish.config(($stateProvider: any) => { - $stateProvider.state('success', { - url: '/success', - template: '', - }); -}); diff --git a/lib/gui/app/components/safe-webview/index.ts b/lib/gui/app/components/safe-webview/index.ts deleted file mode 100644 index c405c044..00000000 --- a/lib/gui/app/components/safe-webview/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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. - */ - -/** - * @module Etcher.Components.SafeWebview - */ - -import * as angular from 'angular'; -import { react2angular } from 'react2angular'; -import * as SafeWebview from './safe-webview'; - -export const MODULE_NAME = 'Etcher.Components.SafeWebview'; -const AngularSafeWebview = angular.module(MODULE_NAME, []); - -AngularSafeWebview.component('safeWebview', react2angular(SafeWebview)); diff --git a/lib/gui/app/index.html b/lib/gui/app/index.html index 6706b418..168e79e9 100644 --- a/lib/gui/app/index.html +++ b/lib/gui/app/index.html @@ -6,21 +6,9 @@ - - - -
- -
- - -
- +
+ diff --git a/lib/gui/app/models/store.js b/lib/gui/app/models/store.js index ab03f273..3c6c559e 100644 --- a/lib/gui/app/models/store.js +++ b/lib/gui/app/models/store.js @@ -112,8 +112,7 @@ const ACTIONS = _.fromPairs(_.map([ 'DESELECT_DRIVE', 'DESELECT_IMAGE', 'SET_APPLICATION_SESSION_UUID', - 'SET_FLASHING_WORKFLOW_UUID', - 'SET_WEBVIEW_SHOWING_STATUS' + 'SET_FLASHING_WORKFLOW_UUID' ], (message) => { return [ message, message ] })) @@ -507,10 +506,6 @@ const storeReducer = (state = DEFAULT_STATE, action) => { return state.set('flashingWorkflowUuid', action.data) } - case ACTIONS.SET_WEBVIEW_SHOWING_STATUS: { - return state.set('isWebviewShowing', action.data) - } - default: { return state } diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index 311732ca..31ca0710 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -160,15 +160,10 @@ const formatSeconds = (totalSeconds: number) => { return `${minutes}m${seconds}s`; }; -export const Flash = ({ - shouldFlashStepBeDisabled, - lastFlashErrorCode, - progressMessage, - goToSuccess, -}: any) => { +export const Flash = ({ shouldFlashStepBeDisabled, goToSuccess }: any) => { const state: any = flashState.getFlashState(); const isFlashing = flashState.isFlashing(); - const flashErrorCode = lastFlashErrorCode(); + const flashErrorCode = flashState.getLastFlashErrorCode(); const [warningMessages, setWarningMessages] = React.useState([]); const [errorMessage, setErrorMessage] = React.useState(''); @@ -272,7 +267,7 @@ export const Flash = ({ {state.failed} - {progressMessage.failed(state.failed)}{' '} + {messages.progress.failed(state.failed)}{' '} diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx index 4d062ba7..513252f3 100644 --- a/lib/gui/app/pages/main/MainPage.tsx +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -21,8 +21,10 @@ import * as React from 'react'; import { Button } from 'rendition'; import * as FeaturedProject from '../../components/featured-project/featured-project'; +import FinishPage from '../../components/finish/finish'; import * as ImageSelector from '../../components/image-selector/image-selector'; import * as ReducedFlashingInfos from '../../components/reduced-flashing-infos/reduced-flashing-infos'; +import * as SafeWebview from '../../components/safe-webview/safe-webview'; import { SettingsModal } from '../../components/settings/settings'; import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx'; import * as flashState from '../../models/flash-state'; @@ -34,19 +36,16 @@ 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(); +function getDrivesTitle() { + const drives = selectionState.getSelectedDrives(); if (drives.length === 1) { + // @ts-ignore return drives[0].description || 'Untitled Device'; } @@ -55,162 +54,204 @@ const getDrivesTitle = (selection: any) => { } return `${drives.length} Targets`; -}; +} -const getImageBasename = (selection: any) => { - if (!selection.hasImage()) { +function getImageBasename() { + if (!selectionState.hasImage()) { return ''; } - const selectionImageName = selection.getImageName(); - const imageBasename = path.basename(selection.getImagePath()); + const selectionImageName = selectionState.getImageName(); + const imageBasename = path.basename(selectionState.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); +interface MainPageStateFromStore { + isFlashing: boolean; + hasImage: boolean; + hasDrive: boolean; + imageLogo: string; + imageSize: number; + imageName: string; + driveTitle: string; +} + +interface MainPageState { + current: 'main' | 'success'; + isWebviewShowing: boolean; + hideSettings: boolean; +} + +export class MainPage extends React.Component< + {}, + MainPageState & MainPageStateFromStore +> { + constructor(props: {}) { + super(props); + this.state = { + current: 'main', + isWebviewShowing: false, + hideSettings: true, + ...this.stateHelper(), + }; + } + + private stateHelper(): MainPageStateFromStore { + return { + isFlashing: flashState.isFlashing(), + hasImage: selectionState.hasImage(), + hasDrive: selectionState.hasDrive(), + imageLogo: selectionState.getImageLogo(), + imageSize: selectionState.getImageSize(), + imageName: getImageBasename(), + driveTitle: getDrivesTitle(), + }; + } + + public componentDidMount() { + (store as any).observe(() => { + this.setState(this.stateHelper()); }); - }, []); + } - const setWebviewShowing = (isShowing: boolean) => { - setIsWebviewShowing(isShowing); - store.dispatch({ - type: 'SET_WEBVIEW_SHOWING_STATUS', - data: Boolean(isShowing), - }); - }; + public render() { + const shouldDriveStepBeDisabled = !this.state.hasImage; + const shouldFlashStepBeDisabled = + !this.state.hasImage || !this.state.hasDrive; - 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} - > - - - - -
+ {this.state.hideSettings ? null : ( + { + this.setState({ hideSettings: !value }); + }} /> )} - - - {hideSettings ? null : ( - { - setHideSettings(!value); - }} - /> - )} -
-
- -
- -
- -
- - {isFlashing && (
- +
+ +
+ +
+ +
+ + {this.state.isFlashing && ( +
+ { + this.setState({ isWebviewShowing }); + }} + /> +
+ )} + +
+ +
+ +
+ this.setState({ current: 'success' })} + shouldFlashStepBeDisabled={shouldFlashStepBeDisabled} + /> +
- )} - -
- + + ); + } else if (this.state.current === 'success') { + return ( +
+ this.setState({ current: 'main' })} /> +
- -
- $state.go('success')} - shouldFlashStepBeDisabled={shouldFlashStepBeDisabled} - lastFlashErrorCode={lastFlashErrorCode} - progressMessage={progressMessage} - /> -
-
- - ); -}; + ); + } + } +} export default MainPage; diff --git a/lib/gui/app/pages/main/main.ts b/lib/gui/app/pages/main/main.ts deleted file mode 100644 index 83d4ce02..00000000 --- a/lib/gui/app/pages/main/main.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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. - */ - -/** - * 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'; - -export const MODULE_NAME = 'Etcher.Pages.Main'; - -const Main = angular.module(MODULE_NAME, [angularRouter]); - -Main.component('mainPage', react2angular(MainPage, [], ['$state'])); - -Main.config(($stateProvider: any) => { - $stateProvider.state('main', { - url: '/main', - template: '', - }); -}); diff --git a/lib/gui/css/angular.css b/lib/gui/app/tsapp.tsx similarity index 72% rename from lib/gui/css/angular.css rename to lib/gui/app/tsapp.tsx index 5c9daea5..a4907968 100644 --- a/lib/gui/css/angular.css +++ b/lib/gui/app/tsapp.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2016 balena.io + * Copyright 2020 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,7 +14,9 @@ * limitations under the License. */ -[ng-click] { - cursor: pointer; - -webkit-app-region: no-drag; -} +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import MainPage from './pages/main/MainPage'; + +ReactDOM.render(, document.getElementById('main')); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c5f5fb42..3a9c9976 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1092,11 +1092,6 @@ "defer-to-connect": "^1.0.1" } }, - "@types/angular": { - "version": "1.6.56", - "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.56.tgz", - "integrity": "sha512-HxtqilvklZ7i6XOaiP7uIJIrFXEVEhfbSY45nfv2DeBRngncI58Y4ZOUMiUkcT8sqgLL1ablmbfylChUg7A3GA==" - }, "@types/bluebird": { "version": "3.5.28", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.28.tgz", @@ -1186,14 +1181,6 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz", "integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==" }, - "@types/lodash.frompairs": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/lodash.frompairs/-/lodash.frompairs-4.0.6.tgz", - "integrity": "sha512-rwCUf4NMKhXpiVjL/RXP8YOk+rd02/J4tACADEgaMXRVnzDbSSlBMKFZoX/ARmHVLg3Qc98Um4PErGv8FbxU7w==", - "requires": { - "@types/lodash": "*" - } - }, "@types/marked": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz", @@ -1597,25 +1584,6 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, - "angular": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.6.tgz", - "integrity": "sha512-QELpvuMIe1FTGniAkRz93O6A+di0yu88niDwcdzrSqtUHNtZMgtgFS4f7W/6Gugbuwej8Kyswlmymwdp8iPCWg==" - }, - "angular-mocks": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.6.tgz", - "integrity": "sha512-t3eQmuAZczdOVdOQj7muCBwH+MBNwd+/FaAsV1SNp+597EQVWABQwxI6KXE0k0ZlyJ5JbtkNIKU8kGAj1znxhw==", - "dev": true - }, - "angular-ui-router": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/angular-ui-router/-/angular-ui-router-0.4.3.tgz", - "integrity": "sha512-EGBG7G7ArFVkJPM+ZIgPKuMYuT16UQrr3zj3BEiXHKwxss867bGt3u7QD9g4BxR+K2qQOSWok6JGvgTWXAko3A==", - "requires": { - "angular": "^1.0.8" - } - }, "ansi-align": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", @@ -9081,11 +9049,6 @@ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" }, - "lodash.frompairs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.frompairs/-/lodash.frompairs-4.0.1.tgz", - "integrity": "sha1-vE5SB/onV8E25XNhTpZkUGsrG9I=" - }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -10115,17 +10078,6 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, - "ngcomponent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ngcomponent/-/ngcomponent-4.1.0.tgz", - "integrity": "sha512-cGL3iVoqMWTpCfaIwgRKhdaGqiy2Z+CCG0cVfjlBvdqE8saj8xap9B4OTf+qwObxLVZmDTJPDgx3bN6Q/lZ7BQ==", - "requires": { - "@types/angular": "^1.6.39", - "@types/lodash": "^4.14.85", - "angular": ">=1.5.0", - "lodash": "^4.17.4" - } - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -11569,17 +11521,6 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, - "react2angular": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/react2angular/-/react2angular-4.0.6.tgz", - "integrity": "sha512-MDl2WRoTyu7Gyh4+FAIlmsM2mxIa/DjSz6G/d90L1tK8ZRubqVEayKF6IPyAruC5DMhGDVJ7tlAIcu/gMNDjXg==", - "requires": { - "@types/lodash.frompairs": "^4.0.5", - "angular": ">=1.5", - "lodash.frompairs": "^4.0.1", - "ngcomponent": "^4.1.0" - } - }, "read-config-file": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-5.0.0.tgz", diff --git a/package.json b/package.json index c83bcd37..5da7d602 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,6 @@ "@fortawesome/free-brands-svg-icons": "^5.11.2", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/react-fontawesome": "^0.1.7", - "angular": "1.7.6", - "angular-ui-router": "^0.4.2", "bindings": "^1.3.0", "bluebird": "^3.5.3", "bootstrap-sass": "^3.3.6", @@ -79,7 +77,6 @@ "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", "request": "^2.81.0", @@ -98,7 +95,6 @@ "@babel/preset-env": "^7.6.0", "@babel/preset-react": "^7.0.0", "@types/react-dom": "^16.8.4", - "angular-mocks": "1.7.6", "babel-loader": "^8.0.4", "chalk": "^1.1.3", "electron": "6.1.4", diff --git a/tests/gui/modules/image-writer.spec.js b/tests/gui/modules/image-writer.spec.js index 8aadf5df..7235cb18 100644 --- a/tests/gui/modules/image-writer.spec.js +++ b/tests/gui/modules/image-writer.spec.js @@ -1,12 +1,11 @@ 'use strict' +const _ = require('lodash') const m = require('mochainon') const ipc = require('node-ipc') -const angular = require('angular') const Bluebird = require('bluebird') const flashState = require('../../../lib/gui/app/models/flash-state') const imageWriter = require('../../../lib/gui/app/modules/image-writer') -require('angular-mocks') describe('Browser: imageWriter', () => { describe('.flash()', () => { @@ -41,7 +40,7 @@ describe('Browser: imageWriter', () => { }) const writing = imageWriter.flash('foo.img', [ '/dev/disk2' ]) - imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(angular.noop) + imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(_.noop) writing.finally(() => { m.chai.expect(this.performWriteStub).to.have.been.calledOnce }) @@ -73,13 +72,13 @@ describe('Browser: imageWriter', () => { }) it('should set flashing to false when done', () => { - imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(angular.noop).finally(() => { + imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(_.noop).finally(() => { m.chai.expect(flashState.isFlashing()).to.be.false }) }) it('should set the error code in the flash results', () => { - imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(angular.noop).finally(() => { + imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(_.noop).finally(() => { const flashResults = flashState.getFlashResults() m.chai.expect(flashResults.errorCode).to.equal('FOO') })