Valentin D. Pinkman
7 years ago
21 changed files with 610 additions and 183 deletions
@ -0,0 +1,19 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
import { withDevice } from 'helpers/deviceAccess' |
||||
|
|
||||
|
import isDashboardOpen from '../helpers/devices/isDashboardOpen' |
||||
|
|
||||
|
type Input = { |
||||
|
devicePath: string, |
||||
|
} |
||||
|
|
||||
|
type Result = boolean |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand('isDashboardOpen', ({ devicePath }) => |
||||
|
fromPromise(withDevice(devicePath)(transport => isDashboardOpen(transport))), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -0,0 +1,106 @@ |
|||||
|
// @flow
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
import Box from 'components/base/Box' |
||||
|
import Search from 'components/base/Search' |
||||
|
|
||||
|
import SearchIcon from 'icons/Search' |
||||
|
import CrossIcon from 'icons/Cross' |
||||
|
|
||||
|
type LedgerApp = { |
||||
|
name: string, |
||||
|
version: string, |
||||
|
icon: string, |
||||
|
app: Object, |
||||
|
bolos_version: { |
||||
|
min: number, |
||||
|
max: number, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
list: Array<LedgerApp>, |
||||
|
children: (list: Array<LedgerApp>) => React$Node, |
||||
|
} |
||||
|
|
||||
|
type State = { |
||||
|
query: string, |
||||
|
focused: boolean, |
||||
|
} |
||||
|
|
||||
|
const SearchBarWrapper = styled(Box).attrs({ |
||||
|
horizontal: true, |
||||
|
borderRadius: 4, |
||||
|
})` |
||||
|
height: 42px; |
||||
|
width: 100%; |
||||
|
margin: 0 0 20px 0; |
||||
|
background-color: white; |
||||
|
padding: 0 13px; |
||||
|
` |
||||
|
|
||||
|
const Input = styled.input` |
||||
|
width: 100%; |
||||
|
border: 0; |
||||
|
margin: 0 13px; |
||||
|
flex: 1; |
||||
|
outline: none; |
||||
|
background: transparent; |
||||
|
color: black; |
||||
|
font-family: 'Open Sans'; |
||||
|
font-weight: 600; |
||||
|
` |
||||
|
|
||||
|
class AppSearchBar extends PureComponent<Props, State> { |
||||
|
state = { |
||||
|
query: '', |
||||
|
focused: false, |
||||
|
} |
||||
|
|
||||
|
handleChange = (e: any) => this.setState({ query: e.target.value }) |
||||
|
|
||||
|
handleFocus = (bool: boolean) => () => this.setState({ focused: bool }) |
||||
|
|
||||
|
reset = () => { |
||||
|
const { input } = this |
||||
|
this.setState(state => ({ ...state, query: '' }), () => input && input.focus()) |
||||
|
} |
||||
|
|
||||
|
input = null |
||||
|
|
||||
|
render() { |
||||
|
const { children, list } = this.props |
||||
|
const { query, focused } = this.state |
||||
|
|
||||
|
const color = focused ? 'black' : 'grey' |
||||
|
|
||||
|
return ( |
||||
|
<Box> |
||||
|
<SearchBarWrapper align="center"> |
||||
|
<SearchIcon size={16} style={{ color }} /> |
||||
|
<Input |
||||
|
innerRef={c => (this.input = c)} |
||||
|
type="text" |
||||
|
value={query} |
||||
|
onChange={this.handleChange} |
||||
|
onFocus={this.handleFocus(true)} |
||||
|
onBlur={this.handleFocus(false)} |
||||
|
/> |
||||
|
{!!query && <CrossIcon size={16} cursor="pointer" onClick={this.reset} />} |
||||
|
</SearchBarWrapper> |
||||
|
<Search |
||||
|
fuseOptions={{ |
||||
|
threshold: 0.5, |
||||
|
keys: ['name'], |
||||
|
}} |
||||
|
value={query} |
||||
|
items={list} |
||||
|
render={items => children(items)} |
||||
|
/> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default AppSearchBar |
@ -1,39 +1,28 @@ |
|||||
// @flow
|
// @flow
|
||||
import React, { PureComponent } from 'react' |
import { PureComponent } from 'react' |
||||
import { connect } from 'react-redux' |
import { connect } from 'react-redux' |
||||
import { translate } from 'react-i18next' |
|
||||
import { compose } from 'redux' |
|
||||
|
|
||||
import type { Device, T } from 'types/common' |
import type { Node } from 'react' |
||||
|
import type { Device } from 'types/common' |
||||
|
|
||||
import { getCurrentDevice, getDevices } from 'reducers/devices' |
import { getCurrentDevice } from 'reducers/devices' |
||||
|
|
||||
const mapStateToProps = state => ({ |
|
||||
device: getCurrentDevice(state), |
|
||||
nbDevices: getDevices(state).length, |
|
||||
}) |
|
||||
|
|
||||
type Props = { |
type Props = { |
||||
t: T, |
device: Device, |
||||
device: ?Device, |
children: (device: Device) => Node, |
||||
nbDevices: number, |
|
||||
children: Function, |
|
||||
} |
} |
||||
|
|
||||
type State = {} |
type State = {} |
||||
|
|
||||
class EnsureDevice extends PureComponent<Props, State> { |
class EnsureDevice extends PureComponent<Props, State> { |
||||
static defaultProps = { |
|
||||
device: null, |
|
||||
} |
|
||||
|
|
||||
render() { |
render() { |
||||
const { device, nbDevices, children, t } = this.props |
const { device, children } = this.props |
||||
return device ? children(device, nbDevices) : <span>{t('manager:errors.noDevice')}</span> |
return children(device) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
export default compose( |
const mapStateToProps = state => ({ |
||||
translate(), |
device: getCurrentDevice(state), |
||||
connect(mapStateToProps), |
}) |
||||
)(EnsureDevice) |
|
||||
|
export default connect(mapStateToProps)(EnsureDevice) |
||||
|
@ -0,0 +1,249 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
import { Trans, translate } from 'react-i18next' |
||||
|
import isNull from 'lodash/isNull' |
||||
|
|
||||
|
import type { Node } from 'react' |
||||
|
import type { Device, T } from 'types/common' |
||||
|
|
||||
|
import { i } from 'helpers/staticPath' |
||||
|
import Box from 'components/base/Box' |
||||
|
import Space from 'components/base/Space' |
||||
|
import Spinner from 'components/base/Spinner' |
||||
|
import Text from 'components/base/Text' |
||||
|
|
||||
|
import IconCheck from 'icons/Check' |
||||
|
import IconExclamationCircle from 'icons/ExclamationCircle' |
||||
|
import IconUsb from 'icons/Usb' |
||||
|
import IconHome from 'icons/Home' |
||||
|
|
||||
|
import EnsureDevice from './EnsureDevice' |
||||
|
import EnsureDashboard from './EnsureDashboard' |
||||
|
import EnsureGenuine from './EnsureGenuine' |
||||
|
|
||||
|
type DeviceInfo = { |
||||
|
targetId: number | string, |
||||
|
version: string, |
||||
|
final: boolean, |
||||
|
mcu: boolean, |
||||
|
} |
||||
|
|
||||
|
type Error = { |
||||
|
message: string, |
||||
|
stack: string, |
||||
|
} |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
renderMcuUpdate: (deviceInfo: DeviceInfo) => Node, |
||||
|
renderFinalUpdate: (deviceInfo: DeviceInfo) => Node, |
||||
|
renderDashboard: (device: Device, deviceInfo: DeviceInfo) => Node, |
||||
|
renderError: (dashboardError: ?Error, genuineError: ?Error) => Node, |
||||
|
} |
||||
|
type State = {} |
||||
|
|
||||
|
const Step = styled(Box).attrs({ |
||||
|
borderRadius: 1, |
||||
|
justifyContent: 'center', |
||||
|
fontSize: 4, |
||||
|
})` |
||||
|
border: 1px solid |
||||
|
${p => |
||||
|
p.validated |
||||
|
? p.theme.colors.wallet |
||||
|
: p.hasErrors |
||||
|
? p.theme.colors.alertRed |
||||
|
: p.theme.colors.fog}; |
||||
|
` |
||||
|
|
||||
|
const StepIcon = styled(Box).attrs({ |
||||
|
alignItems: 'center', |
||||
|
justifyContent: 'center', |
||||
|
})` |
||||
|
width: 64px; |
||||
|
` |
||||
|
|
||||
|
const StepContent = styled(Box).attrs({ |
||||
|
color: 'dark', |
||||
|
horizontal: true, |
||||
|
alignItems: 'center', |
||||
|
})` |
||||
|
height: 60px; |
||||
|
line-height: 1.2; |
||||
|
|
||||
|
strong { |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
const StepCheck = ({ checked, hasErrors }: { checked: ?boolean, hasErrors?: boolean }) => ( |
||||
|
<Box pr={5}> |
||||
|
{checked ? ( |
||||
|
<Box color="wallet"> |
||||
|
<IconCheck size={16} /> |
||||
|
</Box> |
||||
|
) : hasErrors ? ( |
||||
|
<Box color="alertRed"> |
||||
|
<IconExclamationCircle size={16} /> |
||||
|
</Box> |
||||
|
) : ( |
||||
|
<Spinner size={16} /> |
||||
|
)} |
||||
|
</Box> |
||||
|
) |
||||
|
|
||||
|
StepCheck.defaultProps = { |
||||
|
hasErrors: false, |
||||
|
} |
||||
|
|
||||
|
const WrapperIconCurrency = styled(Box).attrs({ |
||||
|
alignItems: 'center', |
||||
|
justifyContent: 'center', |
||||
|
})` |
||||
|
border: 1px solid ${p => p.theme.colors[p.color]}; |
||||
|
border-radius: 8px; |
||||
|
height: 24px; |
||||
|
width: 24px; |
||||
|
` |
||||
|
|
||||
|
class Workflow extends PureComponent<Props, State> { |
||||
|
render() { |
||||
|
const { renderDashboard, renderFinalUpdate, renderMcuUpdate, renderError, t } = this.props |
||||
|
return ( |
||||
|
<EnsureDevice> |
||||
|
{(device: Device) => ( |
||||
|
<EnsureDashboard device={device}> |
||||
|
{(deviceInfo: ?DeviceInfo, dashboardError: ?Error) => ( |
||||
|
<EnsureGenuine device={device}> |
||||
|
{(isGenuine: ?boolean, genuineError: ?Error) => { |
||||
|
if (dashboardError || genuineError) { |
||||
|
return renderError ? ( |
||||
|
renderError(dashboardError, genuineError) |
||||
|
) : ( |
||||
|
<div> |
||||
|
{dashboardError && <span>{dashboardError.message}</span>} |
||||
|
{genuineError && <span>{genuineError.message}</span>} |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (deviceInfo && deviceInfo.mcu) { |
||||
|
return renderMcuUpdate(deviceInfo) |
||||
|
} |
||||
|
|
||||
|
if (deviceInfo && deviceInfo.final) { |
||||
|
return renderFinalUpdate(deviceInfo) |
||||
|
} |
||||
|
|
||||
|
if (isGenuine && deviceInfo && device && !dashboardError && !genuineError) { |
||||
|
return renderDashboard(device, deviceInfo) |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<Box align="center"> |
||||
|
<Space of={152} /> |
||||
|
<Box align="center" style={{ maxWidth: 460, padding: '0 10px' }}> |
||||
|
<img |
||||
|
src={i('logos/connectDevice.png')} |
||||
|
alt="connect your device" |
||||
|
style={{ marginBottom: 30, maxWidth: 360, width: '100%' }} |
||||
|
/> |
||||
|
<Text |
||||
|
ff="Museo Sans|Regular" |
||||
|
fontSize={7} |
||||
|
color="black" |
||||
|
style={{ marginBottom: 10 }} |
||||
|
> |
||||
|
{t('manager:plugYourDevice:title')} |
||||
|
</Text> |
||||
|
<Text ff="Museo Sans|Light" fontSize={5} color="grey" align="center"> |
||||
|
{t('manager:plugYourDevice:desc')} |
||||
|
</Text> |
||||
|
</Box> |
||||
|
<Box flow={4} style={{ maxWidth: 460, padding: '60px 10px 0' }}> |
||||
|
{/* DEVICE CHECK */} |
||||
|
<Step validated={!!device}> |
||||
|
<StepContent> |
||||
|
<StepIcon> |
||||
|
<IconUsb size={36} /> |
||||
|
</StepIcon> |
||||
|
<Box grow shrink> |
||||
|
<Trans i18nKey="deviceConnect:step1.connect" parent="div"> |
||||
|
{'Connect your '} |
||||
|
<strong>Ledger device</strong> |
||||
|
{' to your computer and enter your '} |
||||
|
<strong>PIN code</strong> |
||||
|
{' on your device'} |
||||
|
</Trans> |
||||
|
</Box> |
||||
|
<StepCheck checked={!!device} /> |
||||
|
</StepContent> |
||||
|
</Step> |
||||
|
|
||||
|
{/* DASHBOARD CHECK */} |
||||
|
<Step |
||||
|
validated={!!device && !!deviceInfo} |
||||
|
hasErrors={!!device && !!dashboardError} |
||||
|
> |
||||
|
<StepContent> |
||||
|
<StepIcon> |
||||
|
<WrapperIconCurrency> |
||||
|
<IconHome size={12} /> |
||||
|
</WrapperIconCurrency> |
||||
|
</StepIcon> |
||||
|
<Box grow shrink> |
||||
|
<Trans i18nKey="deviceConnect:dashboard.open" parent="div"> |
||||
|
{'Go to the '} |
||||
|
<strong>{'dashboard'}</strong> |
||||
|
{' on your device'} |
||||
|
</Trans> |
||||
|
</Box> |
||||
|
<StepCheck |
||||
|
checked={!!device && !!deviceInfo} |
||||
|
hasErrors={!!device && !!dashboardError} |
||||
|
/> |
||||
|
</StepContent> |
||||
|
</Step> |
||||
|
|
||||
|
{/* GENUINE CHECK */} |
||||
|
<Step |
||||
|
validated={(!!device && !isNull(isGenuine) && isGenuine) || undefined} |
||||
|
hasErrors={(!!device && !isNull(isGenuine) && !isGenuine) || undefined} |
||||
|
> |
||||
|
<StepContent> |
||||
|
<StepIcon> |
||||
|
<WrapperIconCurrency> |
||||
|
<IconCheck size={12} /> |
||||
|
</WrapperIconCurrency> |
||||
|
</StepIcon> |
||||
|
<Box grow shrink> |
||||
|
<Trans i18nKey="deviceConnect:stepGenuine.open" parent="div"> |
||||
|
{'Confirm '} |
||||
|
<strong>{'authentication'}</strong> |
||||
|
{' on your device'} |
||||
|
</Trans> |
||||
|
</Box> |
||||
|
<StepCheck |
||||
|
checked={(!!device && !isNull(isGenuine) && isGenuine) || undefined} |
||||
|
hasErrors={ |
||||
|
(!!device && !isNull(isGenuine) && !isGenuine) || undefined |
||||
|
} |
||||
|
/> |
||||
|
</StepContent> |
||||
|
</Step> |
||||
|
</Box> |
||||
|
</Box> |
||||
|
) |
||||
|
}} |
||||
|
</EnsureGenuine> |
||||
|
)} |
||||
|
</EnsureDashboard> |
||||
|
)} |
||||
|
</EnsureDevice> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(Workflow) |
@ -1,62 +1,82 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
// import React, { Fragment } from 'react'
|
import React, { PureComponent } from 'react' |
||||
import React from 'react' |
import { translate } from 'react-i18next' |
||||
// import { translate } from 'react-i18next'
|
|
||||
|
|
||||
import type { Node } from 'react' |
import type { Node } from 'react' |
||||
// import type { T, Device } from 'types/common'
|
import type { T, Device } from 'types/common' |
||||
import type { Device } from 'types/common' |
|
||||
|
import Box from 'components/base/Box' |
||||
|
import Text from 'components/base/Text' |
||||
|
|
||||
import AppsList from './AppsList' |
import AppsList from './AppsList' |
||||
// import DeviceInfos from './DeviceInfos'
|
import FirmwareUpdate from './FirmwareUpdate' |
||||
// import FirmwareUpdate from './FirmwareUpdate'
|
import Workflow from './Workflow' |
||||
import EnsureDevice from './EnsureDevice' |
|
||||
// import EnsureDashboard from './EnsureDashboard'
|
type DeviceInfo = { |
||||
// import EnsureGenuine from './EnsureGenuine'
|
targetId: number | string, |
||||
|
version: string, |
||||
// type DeviceInfo = {
|
final: boolean, |
||||
// targetId: number | string,
|
mcu: boolean, |
||||
// version: string,
|
} |
||||
// final: boolean,
|
|
||||
// mcu: boolean,
|
type Error = { |
||||
// }
|
message: string, |
||||
|
stack: string, |
||||
// type Props = {
|
} |
||||
// t: T,
|
|
||||
// }
|
type Props = { |
||||
|
t: T, |
||||
// const ManagerPage = ({ t }: Props): Node => (
|
} |
||||
// <EnsureDevice>
|
|
||||
// {(device: Device) => (
|
type State = { |
||||
// <EnsureDashboard device={device}>
|
modalOpen: boolean, |
||||
// {(deviceInfo: DeviceInfo) => (
|
} |
||||
// <Fragment>
|
|
||||
// {deviceInfo.mcu && <span> bootloader mode </span>}
|
class ManagerPage extends PureComponent<Props, State> { |
||||
// {deviceInfo.final && <span> osu mode </span>}
|
render(): Node { |
||||
// {!deviceInfo.mcu &&
|
const { t } = this.props |
||||
// !deviceInfo.final && (
|
return ( |
||||
// <EnsureGenuine device={device} t={t}>
|
<Workflow |
||||
// <FirmwareUpdate
|
renderError={(dashboardError: ?Error, genuineError: ?Error) => { |
||||
// infos={{
|
if (dashboardError) return <span>Dashboard Error: {dashboardError.message}</span> |
||||
// targetId: deviceInfo.targetId,
|
if (genuineError) return <span>Genuine Error: {genuineError.message}</span> |
||||
// version: deviceInfo.version,
|
return <span>Error</span> |
||||
// }}
|
}} |
||||
// device={device}
|
renderFinalUpdate={(deviceInfo: DeviceInfo) => ( |
||||
// t={t}
|
<p>UPDATE FINAL FIRMARE (TEMPLATE + ACTION WIP) {deviceInfo.final}</p> |
||||
// />
|
)} |
||||
// <AppsList device={device} targetId={deviceInfo.targetId} />
|
renderMcuUpdate={(deviceInfo: DeviceInfo) => ( |
||||
// </EnsureGenuine>
|
<p>FLASH MCU (TEMPLATE + ACTION WIP) {deviceInfo.mcu}</p> |
||||
// )}
|
)} |
||||
// </Fragment>
|
renderDashboard={(device: Device, deviceInfo: DeviceInfo) => ( |
||||
// )}
|
<Box flow={4}> |
||||
// </EnsureDashboard>
|
<Box> |
||||
// )}
|
<Text ff="Museo Sans|Regular" fontSize={7} color="black"> |
||||
// </EnsureDevice>
|
{t('manager:title')} |
||||
// )
|
</Text> |
||||
|
<Text ff="Museo Sans|Light" fontSize={5}> |
||||
const ManagerPage = (): Node => ( |
{t('manager:subtitle')} |
||||
<EnsureDevice>{(device: Device) => <AppsList device={device} />}</EnsureDevice> |
</Text> |
||||
) |
</Box> |
||||
|
<Box mt={7}> |
||||
export default ManagerPage |
<FirmwareUpdate |
||||
|
infos={{ |
||||
|
targetId: deviceInfo.targetId, |
||||
|
version: deviceInfo.version, |
||||
|
}} |
||||
|
device={device} |
||||
|
t={t} |
||||
|
/> |
||||
|
</Box> |
||||
|
<Box> |
||||
|
<AppsList device={device} targetId={deviceInfo.targetId} /> |
||||
|
</Box> |
||||
|
</Box> |
||||
|
)} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(ManagerPage) |
||||
|
@ -0,0 +1,22 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import type Transport from '@ledgerhq/hw-transport' |
||||
|
|
||||
|
import { getFirmwareInfo } from 'helpers/common' |
||||
|
|
||||
|
type Result = boolean |
||||
|
|
||||
|
export default async (transport: Transport<*>): Promise<Result> => { |
||||
|
try { |
||||
|
const { targetId, version } = await getFirmwareInfo(transport) |
||||
|
if (targetId && version) { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
return false |
||||
|
} catch (err) { |
||||
|
const error = Error(err.message) |
||||
|
error.stack = err.stack |
||||
|
throw error |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React from 'react' |
||||
|
|
||||
|
const path = ( |
||||
|
<g transform="translate(670.57 190.38)"> |
||||
|
<path |
||||
|
d="m-658.54-187.18h3.2002a0.80037 0.80037 0 0 1 0 1.5993h-0.80049v10.4a2.3999 2.3999 0 0 1-2.3999 2.3999h-8.0001a2.3999 2.3999 0 0 1-2.3999-2.3999v-10.4h-0.79878a0.80037 0.80037 0 1 1 0-1.5993h3.1991v-0.80049a2.3999 2.3999 0 0 1 2.3999-2.3999h3.2003a2.3999 2.3999 0 0 1 2.3999 2.3999zm-1.5993 0v-0.80049a0.80037 0.80037 0 0 0-0.80049-0.80049h-3.2003a0.80037 0.80037 0 0 0-0.79878 0.80049v0.80049zm0.80049 1.5993a0.84357 0.84357 0 0 1-1e-3 0h-6.3995a0.84357 0.84357 0 0 1-2e-3 0h-1.5976v10.4c0 0.44224 0.35825 0.79877 0.79878 0.79877h8.0001a0.80037 0.80037 0 0 0 0.8005-0.79877v-10.4zm-5.6004 3.2003a0.80037 0.80037 0 1 1 1.5993 0v4.7997a0.80037 0.80037 0 0 1-1.5993 0zm3.1992 0a0.80049 0.80049 0 1 1 1.601 0v4.7997a0.80049 0.80049 0 0 1-1.601 0z" |
||||
|
strokeWidth="1.2" |
||||
|
/> |
||||
|
</g> |
||||
|
) |
||||
|
|
||||
|
export default ({ size, ...p }: { size: number }) => ( |
||||
|
<svg viewBox="0 0 16 17.6" height={size} width={size} {...p}> |
||||
|
{path} |
||||
|
</svg> |
||||
|
) |
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in new issue