From d90e3a816e0cd9b23cee6af999730e12c6c49954 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Fri, 17 Apr 2020 18:57:27 +0200 Subject: [PATCH 1/6] Update leds behaviour Changelog-entry: Update leds behaviour Change-type: patch --- .../progress-button/progress-button.tsx | 3 - lib/gui/app/models/flash-state.ts | 23 +- lib/gui/app/models/leds.ts | 201 ++++++++++++++---- lib/gui/app/models/store.ts | 24 ++- lib/gui/app/modules/image-writer.ts | 8 +- lib/gui/app/pages/main/MainPage.tsx | 7 +- lib/shared/drive-constraints.ts | 18 +- scripts/resin | 2 +- tests/shared/drive-constraints.spec.ts | 67 ------ 9 files changed, 221 insertions(+), 132 deletions(-) diff --git a/lib/gui/app/components/progress-button/progress-button.tsx b/lib/gui/app/components/progress-button/progress-button.tsx index 1d2080bb..77eae911 100644 --- a/lib/gui/app/components/progress-button/progress-button.tsx +++ b/lib/gui/app/components/progress-button/progress-button.tsx @@ -55,9 +55,6 @@ const colors = { verifying: '#1ac135', } as const; -/** - * Progress Button component - */ export class ProgressButton extends React.Component { public render() { if (this.props.active) { diff --git a/lib/gui/app/models/flash-state.ts b/lib/gui/app/models/flash-state.ts index 50de3932..2823ca6b 100644 --- a/lib/gui/app/models/flash-state.ts +++ b/lib/gui/app/models/flash-state.ts @@ -68,6 +68,24 @@ export function unsetFlashingFlag(results: { }); } +export function setDevicePaths(devicePaths: string[]) { + store.dispatch({ + type: Actions.SET_DEVICE_PATHS, + data: devicePaths, + }); +} + +export function addFailedDevicePath(devicePath: string) { + const failedDevicePathsSet = new Set( + store.getState().toJS().failedDevicePaths, + ); + failedDevicePathsSet.add(devicePath); + store.dispatch({ + type: Actions.SET_FAILED_DEVICE_PATHS, + data: Array.from(failedDevicePathsSet), + }); +} + /** * @summary Set the flashing state */ @@ -76,7 +94,8 @@ export function setProgressState( ) { // Preserve only one decimal place const PRECISION = 1; - const data = _.assign({}, state, { + const data = { + ...state, percentage: state.percentage !== undefined && _.isFinite(state.percentage) ? Math.floor(state.percentage) @@ -89,7 +108,7 @@ export function setProgressState( return null; }), - }); + }; store.dispatch({ type: Actions.SET_FLASH_STATE, diff --git a/lib/gui/app/models/leds.ts b/lib/gui/app/models/leds.ts index 406c2566..6478233e 100644 --- a/lib/gui/app/models/leds.ts +++ b/lib/gui/app/models/leds.ts @@ -14,22 +14,20 @@ * limitations under the License. */ -import { - AnimationFunction, - blinkWhite, - breatheGreen, - Color, - RGBLed, -} from 'sys-class-rgb-led'; +import { Drive as DrivelistDrive } from 'drivelist'; +import * as _ from 'lodash'; +import { AnimationFunction, Color, RGBLed } from 'sys-class-rgb-led'; +import { isSourceDrive } from '../../../shared/drive-constraints'; import * as settings from './settings'; -import { observe } from './store'; +import { DEFAULT_STATE, observe } from './store'; const leds: Map = new Map(); function setLeds( drivesPaths: Set, colorOrAnimation: Color | AnimationFunction, + frequency?: number, ) { for (const path of drivesPaths) { const led = leds.get(path); @@ -37,28 +35,123 @@ function setLeds( if (Array.isArray(colorOrAnimation)) { led.setStaticColor(colorOrAnimation); } else { - led.setAnimation(colorOrAnimation); + led.setAnimation(colorOrAnimation, frequency); } } } } -export function updateLeds( - availableDrives: string[], - selectedDrives: string[], -) { - const off = new Set(leds.keys()); - const available = new Set(availableDrives); - const selected = new Set(selectedDrives); - for (const s of selected) { - available.delete(s); +const red: Color = [1, 0, 0]; +const green: Color = [0, 1, 0]; +const blue: Color = [0, 0, 1]; +const white: Color = [1, 1, 1]; +const black: Color = [0, 0, 0]; +const purple: Color = [0.5, 0, 0.5]; + +function createAnimationFunction( + intensityFunction: (t: number) => number, + color: Color, +): AnimationFunction { + return (t: number): Color => { + const intensity = intensityFunction(t); + return color.map((v) => v * intensity) as Color; + }; +} + +function blink(t: number) { + return Math.floor(t / 1000) % 2; +} + +function breathe(t: number) { + return (1 + Math.sin(t / 1000)) / 2; +} + +const breatheBlue = createAnimationFunction(breathe, blue); +const blinkGreen = createAnimationFunction(blink, green); +const blinkPurple = createAnimationFunction(blink, purple); + +interface LedsState { + step: 'main' | 'flashing' | 'verifying' | 'finish'; + sourceDrive: string | undefined; + availableDrives: string[]; + selectedDrives: string[]; + failedDrives: string[]; +} + +// Source slot (1st slot): behaves as a target unless it is chosen as source +// No drive: black +// Drive plugged: blue - on +// +// Other slots (2 - 16): +// +// +----------------+---------------+-----------------------------+----------------------------+---------------------------------+ +// | | main screen | flashing | validating | results screen | +// +----------------+---------------+-----------------------------+----------------------------+---------------------------------+ +// | no drive | black | black | black | black | +// +----------------+---------------+-----------------------------+----------------------------+---------------------------------+ +// | drive plugged | black | black | black | black | +// +----------------+---------------+-----------------------------+----------------------------+---------------------------------+ +// | drive selected | white | blink purple, red if failed | blink green, red if failed | green if success, red if failed | +// +----------------+---------------+-----------------------------+----------------------------+---------------------------------+ +export function updateLeds({ + step, + sourceDrive, + availableDrives, + selectedDrives, + failedDrives, +}: LedsState) { + const unplugged = new Set(leds.keys()); + const plugged = new Set(availableDrives); + const selectedOk = new Set(selectedDrives); + const selectedFailed = new Set(failedDrives); + + // Remove selected devices from plugged set + for (const d of selectedOk) { + plugged.delete(d); } - for (const a of available) { - off.delete(a); + + // Remove plugged devices from unplugged set + for (const d of plugged) { + unplugged.delete(d); + } + + // Remove failed devices from selected set + for (const d of selectedFailed) { + selectedOk.delete(d); + } + + // Handle source slot + if (sourceDrive !== undefined) { + if (unplugged.has(sourceDrive)) { + unplugged.delete(sourceDrive); + // TODO + setLeds(new Set([sourceDrive]), breatheBlue, 2); + } else if (plugged.has(sourceDrive)) { + plugged.delete(sourceDrive); + setLeds(new Set([sourceDrive]), blue); + } + } + if (step === 'main') { + setLeds(unplugged, black); + setLeds(plugged, black); + setLeds(selectedOk, white); + setLeds(selectedFailed, white); + } else if (step === 'flashing') { + setLeds(unplugged, black); + setLeds(plugged, black); + setLeds(selectedOk, blinkPurple, 2); + setLeds(selectedFailed, red); + } else if (step === 'verifying') { + setLeds(unplugged, black); + setLeds(plugged, black); + setLeds(selectedOk, blinkGreen, 2); + setLeds(selectedFailed, red); + } else if (step === 'finish') { + setLeds(unplugged, black); + setLeds(plugged, black); + setLeds(selectedOk, green); + setLeds(selectedFailed, red); } - setLeds(off, [0, 0, 0]); - setLeds(available, breatheGreen); - setLeds(selected, blinkWhite); } interface DeviceFromState { @@ -66,6 +159,46 @@ interface DeviceFromState { device: string; } +let ledsState: LedsState | undefined; + +function stateObserver(state: typeof DEFAULT_STATE) { + const s = state.toJS(); + let step: 'main' | 'flashing' | 'verifying' | 'finish'; + if (s.isFlashing) { + step = s.flashState.type; + } else { + step = s.lastAverageFlashingSpeed == null ? 'main' : 'finish'; + } + const availableDrives = s.availableDrives.filter( + (d: DeviceFromState) => d.devicePath, + ); + const sourceDrivePath = availableDrives.filter((d: DrivelistDrive) => + isSourceDrive(d, s.selection.image), + )[0]?.devicePath; + const availableDrivesPaths = availableDrives.map( + (d: DeviceFromState) => d.devicePath, + ); + let selectedDrivesPaths: string[]; + if (step === 'main') { + selectedDrivesPaths = availableDrives + .filter((d: DrivelistDrive) => s.selection.devices.includes(d.device)) + .map((d: DrivelistDrive) => d.devicePath); + } else { + selectedDrivesPaths = s.devicePaths; + } + const newLedsState = { + step, + sourceDrive: sourceDrivePath, + availableDrives: availableDrivesPaths, + selectedDrives: selectedDrivesPaths, + failedDrives: s.failedDevicePaths, + }; + if (!_.isEqual(newLedsState, ledsState)) { + updateLeds(newLedsState); + ledsState = newLedsState; + } +} + export async function init(): Promise { // ledsMapping is something like: // { @@ -78,22 +211,10 @@ export async function init(): Promise { // } const ledsMapping: _.Dictionary<[string, string, string]> = (await settings.get('ledsMapping')) || {}; - for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) { - leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames)); + if (!_.isEmpty(ledsMapping)) { + for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) { + leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames)); + } + observe(_.debounce(stateObserver, 1000, { maxWait: 1000 })); } - observe((state) => { - const availableDrives = state - .get('availableDrives') - .toJS() - .filter((d: DeviceFromState) => d.devicePath); - const availableDrivesPaths = availableDrives.map( - (d: DeviceFromState) => d.devicePath, - ); - // like /dev/sda - const selectedDrivesDevices = state.getIn(['selection', 'devices']).toJS(); - const selectedDrivesPaths = availableDrives - .filter((d: DeviceFromState) => selectedDrivesDevices.includes(d.device)) - .map((d: DeviceFromState) => d.devicePath); - updateLeds(availableDrivesPaths, selectedDrivesPaths); - }); } diff --git a/lib/gui/app/models/store.ts b/lib/gui/app/models/store.ts index 06ba71b7..1d8ff2e9 100644 --- a/lib/gui/app/models/store.ts +++ b/lib/gui/app/models/store.ts @@ -55,7 +55,7 @@ const selectImageNoNilFields = ['path', 'extension']; /** * @summary Application default state */ -const DEFAULT_STATE = Immutable.fromJS({ +export const DEFAULT_STATE = Immutable.fromJS({ applicationSessionUuid: '', flashingWorkflowUuid: '', availableDrives: [], @@ -63,6 +63,8 @@ const DEFAULT_STATE = Immutable.fromJS({ devices: Immutable.OrderedSet(), }, isFlashing: false, + devicePaths: [], + failedDevicePaths: [], flashResults: {}, flashState: { active: 0, @@ -78,6 +80,8 @@ const DEFAULT_STATE = Immutable.fromJS({ * @summary Application supported action messages */ export enum Actions { + SET_DEVICE_PATHS, + SET_FAILED_DEVICE_PATHS, SET_AVAILABLE_DRIVES, SET_FLASH_STATE, RESET_FLASH_STATE, @@ -264,6 +268,12 @@ function storeReducer( .set('isFlashing', false) .set('flashState', DEFAULT_STATE.get('flashState')) .set('flashResults', DEFAULT_STATE.get('flashResults')) + .set('devicePaths', DEFAULT_STATE.get('devicePaths')) + .set('failedDevicePaths', DEFAULT_STATE.get('failedDevicePaths')) + .set( + 'lastAverageFlashingSpeed', + DEFAULT_STATE.get('lastAverageFlashingSpeed'), + ) .delete('flashUuid'); } @@ -328,10 +338,6 @@ function storeReducer( return state .set('isFlashing', false) .set('flashResults', Immutable.fromJS(action.data)) - .set( - 'lastAverageFlashingSpeed', - DEFAULT_STATE.get('lastAverageFlashingSpeed'), - ) .set('flashState', DEFAULT_STATE.get('flashState')); } @@ -542,6 +548,14 @@ function storeReducer( return state.set('flashingWorkflowUuid', action.data); } + case Actions.SET_DEVICE_PATHS: { + return state.set('devicePaths', action.data); + } + + case Actions.SET_FAILED_DEVICE_PATHS: { + return state.set('failedDevicePaths', action.data); + } + default: { return state; } diff --git a/lib/gui/app/modules/image-writer.ts b/lib/gui/app/modules/image-writer.ts index bdf87207..358f7b46 100644 --- a/lib/gui/app/modules/image-writer.ts +++ b/lib/gui/app/modules/image-writer.ts @@ -172,7 +172,10 @@ export async function performWrite( validateWriteOnSuccess, }; - ipc.server.on('fail', ({ error }: { error: Error & { code: string } }) => { + ipc.server.on('fail', ({ device, error }) => { + if (device.devicePath) { + flashState.addFailedDevicePath(device.devicePath); + } handleErrorLogging(error, analyticsData); }); @@ -264,6 +267,9 @@ export async function flash( } flashState.setFlashingFlag(); + flashState.setDevicePaths( + drives.map((d) => d.devicePath).filter((p) => p != null) as string[], + ); const analyticsData = { image, diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx index 9207c929..77fa8de5 100644 --- a/lib/gui/app/pages/main/MainPage.tsx +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -263,7 +263,12 @@ export class MainPage extends React.Component< private renderSuccess() { return (
- this.setState({ current: 'main' })} /> + { + flashState.resetState(); + this.setState({ current: 'main' }); + }} + />
); diff --git a/lib/shared/drive-constraints.ts b/lib/shared/drive-constraints.ts index 3dde3f2d..a919c900 100644 --- a/lib/shared/drive-constraints.ts +++ b/lib/shared/drive-constraints.ts @@ -44,7 +44,7 @@ export function isSystemDrive(drive: DrivelistDrive): boolean { } export interface Image { - path: string; + path?: string; isSizeEstimated?: boolean; compressedSize?: number; recommendedDriveSize?: number; @@ -59,18 +59,12 @@ export interface Image { * containing the image. */ export function isSourceDrive(drive: DrivelistDrive, image: Image): boolean { - const mountpoints = _.get(drive, ['mountpoints'], []); - const imagePath = _.get(image, ['path']); - - if (!imagePath || _.isEmpty(mountpoints)) { - return false; + for (const mountpoint of drive.mountpoints || []) { + if (image.path !== undefined && pathIsInside(image.path, mountpoint.path)) { + return true; + } } - - return _.some( - _.map(mountpoints, (mountpoint) => { - return pathIsInside(imagePath, mountpoint.path); - }), - ); + return false; } /** diff --git a/scripts/resin b/scripts/resin index 862ad8d5..02c8c7ca 160000 --- a/scripts/resin +++ b/scripts/resin @@ -1 +1 @@ -Subproject commit 862ad8d53e091ba2ffc580c133b36e04084c8a5b +Subproject commit 02c8c7ca1ffdcaf5c8d566c4fb91e869f9223ab8 diff --git a/tests/shared/drive-constraints.spec.ts b/tests/shared/drive-constraints.spec.ts index d39d2b35..e22ca18f 100644 --- a/tests/shared/drive-constraints.spec.ts +++ b/tests/shared/drive-constraints.spec.ts @@ -116,15 +116,6 @@ describe('Shared: DriveConstraints', function () { expect(result).to.be.false; }); - it('should return false if no drive', function () { - // @ts-ignore - const result = constraints.isSourceDrive(undefined, { - path: '/Volumes/Untitled/image.img', - }); - - expect(result).to.be.false; - }); - it('should return false if there are no mount points', function () { const result = constraints.isSourceDrive( { @@ -1134,64 +1125,6 @@ describe('Shared: DriveConstraints', function () { }); }); - describe('given the image is null', () => { - it('should return an empty list', function () { - const result = constraints.getDriveImageCompatibilityStatuses( - this.drive, - // @ts-ignore - null, - ); - - expect(result).to.deep.equal([]); - }); - }); - - describe('given the drive is null', () => { - it('should return an empty list', function () { - const result = constraints.getDriveImageCompatibilityStatuses( - // @ts-ignore - null, - this.image, - ); - - expect(result).to.deep.equal([]); - }); - }); - - describe('given a locked drive and image is null', () => { - it('should return locked drive error', function () { - this.drive.isReadOnly = true; - - const result = constraints.getDriveImageCompatibilityStatuses( - this.drive, - // @ts-ignore - null, - ); - // @ts-ignore - const expectedTuples = [['ERROR', 'locked']]; - - // @ts-ignore - expectStatusTypesAndMessagesToBe(result, expectedTuples); - }); - }); - - describe('given a system drive and image is null', () => { - it('should return system drive warning', function () { - this.drive.isSystem = true; - - const result = constraints.getDriveImageCompatibilityStatuses( - this.drive, - // @ts-ignore - null, - ); - // @ts-ignore - const expectedTuples = [['WARNING', 'system']]; - - // @ts-ignore - expectStatusTypesAndMessagesToBe(result, expectedTuples); - }); - }); - describe('given the drive contains the image and the drive is locked', () => { it('should return the contains-image drive error by precedence', function () { this.drive.isReadOnly = true; From b71482284f2cedfab7fc920bd0426992b0e123d7 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 14 May 2020 14:29:11 +0200 Subject: [PATCH 2/6] Remove commented code Change-type: patch --- lib/gui/app/modules/progress-status.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/gui/app/modules/progress-status.ts b/lib/gui/app/modules/progress-status.ts index cdcb958b..2250165d 100644 --- a/lib/gui/app/modules/progress-status.ts +++ b/lib/gui/app/modules/progress-status.ts @@ -15,7 +15,6 @@ */ import { bytesToClosestUnit } from '../../../shared/units'; -// import * as settings from '../models/settings'; export interface FlashState { active: number; From f9cbff1eec963b8dbf98d4016964a73f072e2e5a Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 14 May 2020 14:29:47 +0200 Subject: [PATCH 3/6] ProgressButton is a PureComponent Change-type: patch --- lib/gui/app/components/progress-button/progress-button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gui/app/components/progress-button/progress-button.tsx b/lib/gui/app/components/progress-button/progress-button.tsx index 77eae911..97d9d2a2 100644 --- a/lib/gui/app/components/progress-button/progress-button.tsx +++ b/lib/gui/app/components/progress-button/progress-button.tsx @@ -55,7 +55,7 @@ const colors = { verifying: '#1ac135', } as const; -export class ProgressButton extends React.Component { +export class ProgressButton extends React.PureComponent { public render() { if (this.props.active) { return ( From a3a9edd41a0e570b996f58ff6379e53e2f8a3fc3 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 14 May 2020 14:35:08 +0200 Subject: [PATCH 4/6] Make Flash component a class & rename it FlashStep Change-type: patch --- lib/gui/app/pages/main/Flash.tsx | 272 +++++++++++++++------------- lib/gui/app/pages/main/MainPage.tsx | 4 +- 2 files changed, 149 insertions(+), 127 deletions(-) diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index ee0bef13..4dc8bf17 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -164,167 +164,189 @@ const formatSeconds = (totalSeconds: number) => { return `${minutes}m${seconds}s`; }; -interface FlashProps { +interface FlashStepProps { shouldFlashStepBeDisabled: boolean; goToSuccess: () => void; source: SourceOptions; } -export const Flash = ({ - shouldFlashStepBeDisabled, - goToSuccess, - source, -}: FlashProps) => { - const state: any = flashState.getFlashState(); - const isFlashing = flashState.isFlashing(); - const flashErrorCode = flashState.getLastFlashErrorCode(); +interface FlashStepState { + warningMessages: string[]; + errorMessage: string; + showDriveSelectorModal: boolean; +} - const [warningMessages, setWarningMessages] = React.useState([]); - const [errorMessage, setErrorMessage] = React.useState(''); - const [showDriveSelectorModal, setShowDriveSelectorModal] = React.useState( - false, - ); - - const handleWarningResponse = async (shouldContinue: boolean) => { - setWarningMessages([]); +export class FlashStep extends React.Component { + constructor(props: FlashStepProps) { + super(props); + this.state = { + warningMessages: [], + errorMessage: '', + showDriveSelectorModal: false, + }; + } + private async handleWarningResponse(shouldContinue: boolean) { + this.setState({ warningMessages: [] }); if (!shouldContinue) { - setShowDriveSelectorModal(true); + this.setState({ showDriveSelectorModal: true }); return; } + this.setState({ + errorMessage: await flashImageToDrive( + this.props.goToSuccess, + this.props.source, + ), + }); + } - setErrorMessage(await flashImageToDrive(goToSuccess, source)); - }; - - const handleFlashErrorResponse = (shouldRetry: boolean) => { - setErrorMessage(''); + private handleFlashErrorResponse(shouldRetry: boolean) { + this.setState({ errorMessage: '' }); flashState.resetState(); if (shouldRetry) { analytics.logEvent('Restart after failure'); } else { selection.clear(); } - }; + } - const tryFlash = async () => { + private async tryFlash() { const devices = selection.getSelectedDevices(); const image = selection.getImage(); - const drives = _.filter(availableDrives.getDrives(), (drive: any) => { - return _.includes(devices, drive.device); - }); - + const drives = _.filter( + availableDrives.getDrives(), + (drive: { device: string }) => { + return _.includes(devices, drive.device); + }, + ); if (drives.length === 0 || flashState.isFlashing()) { return; } - const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus( drives, image, ); if (hasDangerStatus) { - setWarningMessages(getWarningMessages(drives, image)); + this.setState({ warningMessages: getWarningMessages(drives, image) }); return; } + this.setState({ + errorMessage: await flashImageToDrive( + this.props.goToSuccess, + this.props.source, + ), + }); + } - setErrorMessage(await flashImageToDrive(goToSuccess, source)); - }; - - 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)}`} - )} -

- )} +
+ + { + this.tryFlash(); + }} + /> + - {Boolean(state.failed) && ( -
-
- - {state.failed} - - {messages.progress.failed(state.failed)}{' '} - + {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} + + {messages.progress.failed(state.failed)}{' '} + +
-
- )} + )} +
-
- {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'} - > - - {_.map(errorMessage.split('\n'), (message, key) => ( -

{message}

+ {this.state.warningMessages.length > 0 && ( + this.handleWarningResponse(false)} + done={() => this.handleWarningResponse(true)} + cancelButtonProps={{ + children: 'Change', + }} + action={'Continue'} + primaryButtonProps={{ primary: false, warning: true }} + > + {_.map(this.state.warningMessages, (message, key) => ( + + {message} + ))} -
-
- )} + + )} - {showDriveSelectorModal && ( - setShowDriveSelectorModal(false)} - > - )} - - ); -}; + {this.state.errorMessage && ( + this.handleFlashErrorResponse(false)} + done={() => this.handleFlashErrorResponse(true)} + action={'Retry'} + > + + {_.map(this.state.errorMessage.split('\n'), (message, key) => ( +

{message}

+ ))} +
+
+ )} + + {this.state.showDriveSelectorModal && ( + this.setState({ showDriveSelectorModal: false })} + /> + )} + + ); + } +} diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx index 77fa8de5..f9905a6e 100644 --- a/lib/gui/app/pages/main/MainPage.tsx +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -47,7 +47,7 @@ import { middleEllipsis } from '../../utils/middle-ellipsis'; import { bytesToClosestUnit } from '../../../../shared/units'; import { DriveSelector } from './DriveSelector'; -import { Flash } from './Flash'; +import { FlashStep } from './Flash'; const Icon = styled(BaseIcon)` margin-right: 20px; @@ -249,7 +249,7 @@ export class MainPage extends React.Component<
- this.setState({ current: 'success' })} shouldFlashStepBeDisabled={shouldFlashStepBeDisabled} source={this.state.source} From 52f80293a29ba841112cd5cb75a13e5d6b877ea2 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 14 May 2020 14:42:32 +0200 Subject: [PATCH 5/6] Remove dead code Change-type: patch --- lib/gui/app/pages/main/Flash.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index 4dc8bf17..c7f382c1 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -108,24 +108,17 @@ async function flashImageToDrive( 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.', messages.error.flashFailure(path.basename(image.path), drives), iconPath, ); - let errorMessage = getErrorMessageFromCode(error.code); if (!errorMessage) { error.image = basename; analytics.logException(error); errorMessage = messages.error.genericFlashError(error); } - return errorMessage; } finally { availableDrives.setDrives([]); From 72c9d616fd2aa05f4589e9af8385cb56f5beb16e Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 14 May 2020 14:43:44 +0200 Subject: [PATCH 6/6] Remove useless comment Change-type: patch --- lib/gui/app/pages/main/Flash.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index c7f382c1..72940320 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -128,23 +128,11 @@ async function flashImageToDrive( 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!'; } - - // TODO: no any - return progressStatus.fromFlashState(flashState.getFlashState() as any); + return progressStatus.fromFlashState(flashState.getFlashState()); }; const formatSeconds = (totalSeconds: number) => {