Browse Source

modal ui for firmware update

master
Valentin D. Pinkman 7 years ago
parent
commit
c7745bc91e
No known key found for this signature in database GPG Key ID: E7D110669FFB8D3E
  1. 11
      src/components/ManagerPage/FirmwareFinalUpdate.js
  2. 70
      src/components/ManagerPage/FirmwareUpdate.js
  3. 4
      src/components/ManagerPage/UpdateFirmwareButton.js
  4. 1
      src/components/ManagerPage/index.js
  5. 80
      src/components/modals/UpdateFirmware/Disclaimer.js
  6. 122
      src/components/modals/UpdateFirmware/index.js
  7. 72
      src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js
  8. 72
      src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js
  9. 70
      src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js
  10. 52
      src/components/modals/UpdateFirmware/steps/03-step-confirmation.js
  11. 1
      src/helpers/common.js
  12. 1
      src/helpers/socket.js
  13. 14
      static/i18n/en/app.yml
  14. BIN
      static/images/logos/unplugDevice.png

11
src/components/ManagerPage/FirmwareFinalUpdate.js

@ -10,7 +10,7 @@ import type { Device, T } from 'types/common'
import installFinalFirmware from 'commands/installFinalFirmware'
import Box, { Card } from 'components/base/Box'
// import Button from 'components/base/Button'
import Button from 'components/base/Button'
type Props = {
t: T,
@ -33,7 +33,10 @@ class FirmwareFinalUpdate extends PureComponent<Props, State> {
try {
const { device, deviceInfo } = this.props
const { success } = await installFinalFirmware
.send({ devicePath: device.path, deviceInfo })
.send({
devicePath: device.path,
deviceInfo,
})
.toPromise()
if (success) {
this.setState()
@ -52,7 +55,9 @@ class FirmwareFinalUpdate extends PureComponent<Props, State> {
{t('app:manager.firmware.update')}
</Box>
<Card flow={2} {...props}>
<Box horizontal align="center" flow={2} />
<Box horizontal align="center" flow={2}>
<Button onClick={this.installFinalFirmware}>Install Final Firmware</Button>
</Box>
</Card>
</Box>
)

70
src/components/ManagerPage/FirmwareUpdate.js

@ -1,8 +1,8 @@
// @flow
/* eslint-disable react/jsx-no-literals */ // FIXME
import React, { PureComponent, Fragment } from 'react'
import { translate, Trans } from 'react-i18next'
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import isEqual from 'lodash/isEqual'
import isEmpty from 'lodash/isEmpty'
import invariant from 'invariant'
@ -14,14 +14,13 @@ import type { LedgerScriptParams } from 'helpers/common'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import installOsuFirmware from 'commands/installOsuFirmware'
import DisclaimerModal from 'components/modals/UpdateFirmware/Disclaimer'
import UpdateModal from 'components/modals/UpdateFirmware'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import Tooltip from 'components/base/Tooltip'
import Box, { Card } from 'components/base/Box'
import Text from 'components/base/Text'
import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal'
import Button from 'components/base/Button'
// import Progress from 'components/base/Progress'
import NanoS from 'icons/device/NanoS'
import CheckFull from 'icons/CheckFull'
@ -29,12 +28,10 @@ import CheckFull from 'icons/CheckFull'
import { PreventDeviceChangeRecheck } from 'components/EnsureDevice'
import UpdateFirmwareButton from './UpdateFirmwareButton'
let CACHED_LATEST_FIRMWARE = null
export const getCleanVersion = (input: string): string =>
input.endsWith('-osu') ? input.replace('-osu', '') : input
type ModalStatus = 'closed' | 'disclaimer' | 'installing' | 'error' | 'success'
export type ModalStatus = 'closed' | 'disclaimer' | 'install' | 'error' | 'success'
type Props = {
t: T,
@ -58,7 +55,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
}
componentDidUpdate() {
if (!CACHED_LATEST_FIRMWARE || isEmpty(this.state.latestFirmware)) {
if (isEmpty(this.state.latestFirmware)) {
this.fetchLatestFirmware()
}
}
@ -71,14 +68,12 @@ class FirmwareUpdate extends PureComponent<Props, State> {
fetchLatestFirmware = async () => {
const { deviceInfo } = this.props
const latestFirmware =
CACHED_LATEST_FIRMWARE || (await getLatestFirmwareForDevice.send(deviceInfo).toPromise())
const latestFirmware = await getLatestFirmwareForDevice.send(deviceInfo).toPromise()
if (
!isEmpty(latestFirmware) &&
!isEqual(this.state.latestFirmware, latestFirmware) &&
!this._unmounting
) {
CACHED_LATEST_FIRMWARE = latestFirmware
this.setState({ latestFirmware })
}
}
@ -91,7 +86,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
const {
device: { path: devicePath },
} = this.props
this.setState({ modal: 'installing' })
this.setState({ modal: 'install' })
const { success } = await installOsuFirmware
.send({ devicePath, firmware: latestFirmware, targetId: deviceInfo.targetId })
.toPromise()
@ -105,43 +100,8 @@ class FirmwareUpdate extends PureComponent<Props, State> {
handleCloseModal = () => this.setState({ modal: 'closed' })
handleInstallModal = () => this.setState({ modal: 'disclaimer' })
renderModal = () => {
const { t } = this.props
const { modal, latestFirmware } = this.state
return (
<Modal
isOpened={modal !== 'closed'}
render={() => (
<ModalBody grow align="center" justify="center" mt={3}>
<Fragment>
<ModalTitle>{t('app:manager.firmware.update')}</ModalTitle>
<ModalContent>
<Text ff="Open Sans|Regular" fontSize={4} color="graphite" align="center">
<Trans i18nKey="app:manager.firmware.disclaimerTitle">
You are about to install the latest
<Text ff="Open Sans|SemiBold" color="dark">
{`firmware ${latestFirmware ? getCleanVersion(latestFirmware.name) : ''}`}
</Text>
</Trans>
</Text>
<Text ff="Open Sans|Regular" fontSize={4} color="graphite" align="center">
{t('app:manager.firmware.disclaimerAppDelete')}
{t('app:manager.firmware.disclaimerAppReinstall')}
</Text>
</ModalContent>
<ModalFooter horizontal justifyContent="flex-end" style={{ width: '100%' }}>
<Button primary padded onClick={this.installFirmware}>
{t('app:manager.firmware.continue')}
</Button>
</ModalFooter>
</Fragment>
</ModalBody>
)}
/>
)
}
handleDisclaimerModal = () => this.setState({ modal: 'disclaimer' })
handleInstallModal = () => this.setState({ modal: 'install' })
render() {
const { deviceInfo, t } = this.props
@ -172,11 +132,17 @@ class FirmwareUpdate extends PureComponent<Props, State> {
</Box>
<UpdateFirmwareButton
firmware={latestFirmware}
installFirmware={this.handleInstallModal}
installFirmware={this.handleDisclaimerModal}
/>
</Box>
{modal !== 'closed' ? <PreventDeviceChangeRecheck /> : null}
{this.renderModal()}
<DisclaimerModal
firmware={latestFirmware}
status={modal}
goToNextStep={this.handleInstallModal}
onClose={this.handleCloseModal}
/>
<UpdateModal status={modal} onClose={this.handleCloseModal} firmware={latestFirmware} />
</Card>
)
}

4
src/components/ManagerPage/UpdateFirmwareButton.js

@ -4,7 +4,7 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common'
import { EXPERIMENTAL_FIRMWARE_UPDATE } from 'config/constants'
// import { EXPERIMENTAL_FIRMWARE_UPDATE } from 'config/constants'
import Button from 'components/base/Button'
import Text from 'components/base/Text'
@ -27,7 +27,7 @@ const UpdateFirmwareButton = ({ t, firmware, installFirmware }: Props) =>
<Text ff="Open Sans|Regular" fontSize={4} style={{ marginLeft: 'auto', marginRight: 15 }}>
{t('app:manager.firmware.latest', { version: getCleanVersion(firmware.name) })}
</Text>
<Button primary onClick={installFirmware} disabled={!EXPERIMENTAL_FIRMWARE_UPDATE}>
<Button primary onClick={installFirmware}>
{t('app:manager.firmware.update')}
</Button>
</Fragment>

1
src/components/ManagerPage/index.js

@ -7,7 +7,6 @@ import type { Device } from 'types/common'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import Dashboard from './Dashboard'
// import FlashMcu from './FlashMcu'
import ManagerGenuineCheck from './ManagerGenuineCheck'

80
src/components/modals/UpdateFirmware/Disclaimer.js

@ -0,0 +1,80 @@
// @flow
/* eslint react/jsx-no-literals: 0 */
import React, { PureComponent, Fragment } from 'react'
import { translate, Trans } from 'react-i18next'
import type { T } from 'types/common'
import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal'
import Text from 'components/base/Text'
import Button from 'components/base/Button'
import GrowScroll from 'components/base/GrowScroll'
import GradientBox from 'components/GradientBox'
import { getCleanVersion } from 'components/ManagerPage/FirmwareUpdate'
import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate'
type FirmwareInfos = {
name: string,
notes: string,
}
type Props = {
t: T,
status: ModalStatus,
firmware: FirmwareInfos,
goToNextStep: () => void,
onClose: () => void,
}
type State = *
class DisclaimerModal extends PureComponent<Props, State> {
render(): React$Node {
const { status, firmware, onClose, t, goToNextStep } = this.props
return (
<Modal
isOpened={status === 'disclaimer'}
onClose={onClose}
render={({ onClose }) => (
<ModalBody onClose={onClose} grow align="center" justify="center" mt={3}>
<Fragment>
<ModalTitle>{t('app:manager.firmware.update')}</ModalTitle>
<ModalContent>
<Text ff="Open Sans|Regular" fontSize={4} color="graphite" align="center">
<Trans i18nKey="app:manager.firmware.disclaimerTitle">
You are about to install the latest
<Text ff="Open Sans|SemiBold" color="dark">
{`firmware ${firmware ? getCleanVersion(firmware.name) : ''}`}
</Text>
</Trans>
</Text>
<Text ff="Open Sans|Regular" fontSize={4} color="graphite" align="center">
{t('app:manager.firmware.disclaimerAppDelete')}
{t('app:manager.firmware.disclaimerAppReinstall')}
</Text>
</ModalContent>
<ModalContent style={{ height: 250, width: '100%' }}>
<GrowScroll>
<Text ff="Open Sans|Regular" fontSize={4} color="graphite">
{firmware.notes}
</Text>
</GrowScroll>
<GradientBox />
</ModalContent>
<ModalFooter horizontal justifyContent="flex-end" style={{ width: '100%' }}>
<Button primary padded onClick={goToNextStep}>
{t('app:manager.firmware.continue')}
</Button>
</ModalFooter>
</Fragment>
</ModalBody>
)}
/>
)
}
}
export default translate()(DisclaimerModal)

122
src/components/modals/UpdateFirmware/index.js

@ -0,0 +1,122 @@
// @flow
import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Modal from 'components/base/Modal'
import Stepper from 'components/base/Stepper'
import Button from 'components/base/Button'
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority'
import type { StepProps as DefaultStepProps, Step } from 'components/base/Stepper'
import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate'
import type { LedgerScriptParams } from 'helpers/common'
import StepOSUInstaller from './steps/01-step-osu-installer'
import StepFlashMcu from './steps/02-step-flash-mcu'
import StepConfirmation, { StepConfirmFooter } from './steps/03-step-confirmation'
export type StepProps = DefaultStepProps & {
firmware: ?LedgerScriptParams,
onCloseModal: () => void,
}
type StepId = 'idCheck' | 'updateMCU' | 'finish'
// FIXME: Debugging for now to move between steps
// Remove when plugged to firmware update
function DebugFooter({
transitionTo,
where,
}: {
where: string,
transitionTo: (where: string) => void,
}) {
return <Button onClick={() => transitionTo(where)}>{where}</Button>
}
const createSteps = ({ t }: { t: T }) => [
{
id: 'idCheck',
label: t('app:manager.modal.steps.idCheck'),
component: StepOSUInstaller,
footer: ({ firmware, ...props }: StepProps) => (
<DebugFooter firmware={firmware} {...props} where="updateMCU" />
),
onBack: null,
hideFooter: false,
},
{
id: 'updateMCU',
label: t('app:manager.modal.steps.updateMCU'),
component: StepFlashMcu,
footer: ({ firmware, ...props }: StepProps) => (
<Fragment>
<DebugFooter firmware={firmware} {...props} where="idCheck" />
<DebugFooter firmware={firmware} {...props} where="finish" />
</Fragment>
),
onBack: null,
hideFooter: false,
},
{
id: 'finish',
label: t('app:addAccounts.breadcrumb.finish'),
component: StepConfirmation,
footer: StepConfirmFooter,
onBack: null,
hideFooter: false,
},
]
type Props = {
t: T,
status: ModalStatus,
onClose: () => void,
firmware: ?LedgerScriptParams,
}
type State = {
stepId: StepId | string,
}
class UpdateModal extends PureComponent<Props, State> {
state = {
stepId: 'idCheck',
}
STEPS = createSteps({ t: this.props.t })
handleStepChange = (step: Step) => this.setState({ stepId: step.id })
render(): React$Node {
const { status, t, firmware, onClose } = this.props
const { stepId } = this.state
const additionalProps = {
firmware,
onCloseModal: onClose,
}
return (
<Modal
onClose={onClose}
isOpened={status === 'install'}
refocusWhenChange={stepId}
render={() => (
<Stepper
title={t('app:manager.firmware.update')}
initialStepId="idCheck"
steps={this.STEPS}
{...additionalProps}
>
<SyncSkipUnderPriority priority={100} />
</Stepper>
)}
/>
)
}
}
export default translate()(UpdateModal)

72
src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js

@ -0,0 +1,72 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import Button from 'components/base/Button'
import DeviceConfirm from 'components/DeviceConfirm'
import type { StepProps } from '../'
const Container = styled(Box).attrs({
alignItems: 'center',
fontSize: 4,
color: 'dark',
px: 7,
})``
const Title = styled(Box).attrs({
ff: 'Museo Sans|Regular',
fontSize: 5,
mb: 3,
})``
const Address = styled(Box).attrs({
bg: p => (p.notValid ? 'transparent' : p.withQRCode ? 'white' : 'lightGrey'),
borderRadius: 1,
color: 'dark',
ff: 'Open Sans|SemiBold',
fontSize: 4,
mt: 2,
px: p => (p.notValid ? 0 : 4),
py: p => (p.notValid ? 0 : 3),
})`
border: ${p => (p.notValid ? 'none' : `1px dashed ${p.theme.colors.fog}`)};
cursor: text;
user-select: text;
width: 325px;
`
const Ellipsis = styled.span`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
`
// TODO: Change to class component and add osu firmware install
function StepFullFirmwareInstall({ t, firmware }: StepProps) {
return (
<Container>
<Title>{t('app:manager.modal.confirmIdentifier')}</Title>
<Text ff="Open Sans|Regular" align="center" color="smoke">
{t('app:manager.modal.confirmIdentifierText')}
</Text>
<Box mx={7} mt={5}>
<Text ff="Open Sans|SemiBold" align="center" color="smoke">
{t('app:manager.modal.identifier')}
</Text>
<Address>
<Ellipsis>{firmware && firmware.hash}</Ellipsis>
</Address>
</Box>
<Box mt={5}>
<DeviceConfirm />
</Box>
</Container>
)
}
export default StepFullFirmwareInstall

72
src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js

@ -0,0 +1,72 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import Button from 'components/base/Button'
import DeviceConfirm from 'components/DeviceConfirm'
import type { StepProps } from '../'
const Container = styled(Box).attrs({
alignItems: 'center',
fontSize: 4,
color: 'dark',
px: 7,
})``
const Title = styled(Box).attrs({
ff: 'Museo Sans|Regular',
fontSize: 5,
mb: 3,
})``
const Address = styled(Box).attrs({
bg: p => (p.notValid ? 'transparent' : p.withQRCode ? 'white' : 'lightGrey'),
borderRadius: 1,
color: 'dark',
ff: 'Open Sans|SemiBold',
fontSize: 4,
mt: 2,
px: p => (p.notValid ? 0 : 4),
py: p => (p.notValid ? 0 : 3),
})`
border: ${p => (p.notValid ? 'none' : `1px dashed ${p.theme.colors.fog}`)};
cursor: text;
user-select: text;
width: 325px;
`
const Ellipsis = styled.span`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
`
// TODO: Change to class component and add osu firmware install
function StepOSUInstaller({ t, firmware }: StepProps) {
return (
<Container>
<Title>{t('app:manager.modal.confirmIdentifier')}</Title>
<Text ff="Open Sans|Regular" align="center" color="smoke">
{t('app:manager.modal.confirmIdentifierText')}
</Text>
<Box mx={7} mt={5}>
<Text ff="Open Sans|SemiBold" align="center" color="smoke">
{t('app:manager.modal.identifier')}
</Text>
<Address>
<Ellipsis>{firmware && firmware.hash}</Ellipsis>
</Address>
</Box>
<Box mt={5}>
<DeviceConfirm />
</Box>
</Container>
)
}
export default StepOSUInstaller

70
src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js

@ -0,0 +1,70 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import type { StepProps } from '../'
const Container = styled(Box).attrs({
alignItems: 'center',
fontSize: 4,
color: 'dark',
})``
const Title = styled(Box).attrs({
ff: 'Museo Sans|Regular',
fontSize: 5,
mb: 3,
})``
const Bullet = styled.span`
font-weight: 600;
color: #142533;
`
const Separator = styled(Box).attrs({
color: 'fog',
})`
height: 1px;
width: 100%;
background-color: currentColor;
`
// TODO: Change to class component and add flash mcu and final
function StepFlashMcu({ t }: StepProps) {
return (
<Container>
<Title>{t('app:manager.modal.mcuTitle')}</Title>
<Box mx={7}>
<Text ff="Open Sans|Regular" align="center" color="smoke">
<Bullet>{'1.'}</Bullet>
{t('app:manager.modal.mcuFirst')}
</Text>
<img
src={i('logos/unplugDevice.png')}
style={{ width: '100%', maxWidth: 330, marginTop: 30 }}
alt={t('app:manager.modal.mcuFirst')}
/>
</Box>
<Separator my={6} />
<Box mx={7}>
<Text ff="Open Sans|Regular" align="center" color="smoke">
<Bullet>{'2.'}</Bullet>
{t('app:manager.modal.mcuSecond')}
</Text>
<img
src={i('logos/unplugDevice.png')}
style={{ width: '100%', maxWidth: 330, marginTop: 30 }}
alt={t('app:manager.modal.mcuFirst')}
/>
</Box>
</Container>
)
}
export default StepFlashMcu

52
src/components/modals/UpdateFirmware/steps/03-step-confirmation.js

@ -0,0 +1,52 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import Button from 'components/base/Button'
import CheckCircle from 'icons/CheckCircle'
import type { StepProps } from '../'
const Container = styled(Box).attrs({
alignItems: 'center',
fontSize: 4,
color: 'dark',
})``
const Title = styled(Box).attrs({
fontFamily: 'Museo Sans',
fontSize: 6,
color: 'dark',
})`
font-weight: 500;
`
function StepConfirmation({ t }: StepProps) {
return (
<Container>
<Box mx={7} color="positiveGreen" my={4}>
<CheckCircle size={44} />
</Box>
<Title>{t('app:manager.modal.successTitle')}</Title>
<Box mt={2} mb={8}>
<Text ff="Open Sans|Regular" fontSize={4} color="graphite">
{t('app:manager.modal.successText')}
</Text>
</Box>
<Box mx={7} />
</Container>
)
}
export function StepConfirmFooter({ t, onCloseModal }: StepProps) {
return (
<Button primary onClick={onCloseModal}>
{t('app:common.close')}
</Button>
)
}
export default StepConfirmation

1
src/helpers/common.js

@ -20,6 +20,7 @@ export type LedgerScriptParams = {
version: string,
icon: string,
app?: number,
hash?: string,
}
/**

1
src/helpers/socket.js

@ -74,6 +74,7 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) =>
for (const apdu of data) {
const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex'))
lastStatus = r.slice(r.length - 2)
if (lastStatus.toString('hex') !== '9000') break
}

14
static/i18n/en/app.yml

@ -204,12 +204,24 @@ manager:
firmware:
installed: 'Firmware version {{version}}'
update: Update firmware
updateTitle: Firmware update
continue: Continue update
latest: 'Firmware version {{version}} is available'
disclaimerTitle: 'You are about to install the latest <1><0>firmware {{version}}</0></1>'
disclaimerAppDelete: Please note that all the apps installed on your device will be deleted.
disclaimerAppReinstall: You will be able to re-install your apps after the firmware update
modal:
steps:
idCheck: Identifier check
updateMCU: Update MCU
confirm: Confirmation
confirmIdentifier: Confirm identifier
confirmIdentifierText: Please confirm identifier on your Device. Be sure the identifier is the same as below
identifier: Identifier
mcuTitle: Updating MCU
mcuFirst: Unplug your device from your computer
mcuSecond: Press and hold left button and plug your device until the processing screen appears
successTitle: Firmware has been updated with success
successText: You can now re-install your applications on your device
title: Manager
subtitle: Install or uninstall apps on your device
device:

BIN
static/images/logos/unplugDevice.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Loading…
Cancel
Save