Browse Source

Merge branch 'master' of github.com:ledgerhq/ledger-live-desktop into sanity-check-at-boot

master
Gaëtan Renaudeau 7 years ago
parent
commit
d9197657e8
  1. 2
      src/commands/index.js
  2. 7
      src/commands/installOsuFirmware.js
  3. 26
      src/commands/testApdu.js
  4. 12
      src/components/CurrentAddress/index.js
  5. 80
      src/components/DashboardPage/EmptyState.js
  6. 160
      src/components/DashboardPage/index.js
  7. 9
      src/components/ManagerPage/EnsureDashboard.js
  8. 9
      src/components/ManagerPage/EnsureDevice.js
  9. 9
      src/components/ManagerPage/EnsureGenuine.js
  10. 31
      src/components/ManagerPage/FirmwareUpdate.js
  11. 25
      src/components/ManagerPage/index.js
  12. 17
      src/components/SideBar/index.js
  13. 25
      src/components/modals/Debug.js
  14. 4
      src/components/modals/ImportAccounts/steps/02-step-connect-device.js
  15. 2
      src/helpers/firmware/installOsuFirmware.js
  16. 2
      src/main/bridge.js
  17. 8
      static/i18n/en/emptyState.yml
  18. 6
      static/i18n/en/manager.yml
  19. BIN
      static/images/logos/emptyStateDashboard.png

2
src/commands/index.js

@ -18,6 +18,7 @@ import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import listApps from 'commands/listApps'
import listenDevices from 'commands/listenDevices'
import signTransaction from 'commands/signTransaction'
import testApdu from 'commands/testApdu'
import testCrash from 'commands/testCrash'
import testInterval from 'commands/testInterval'
import uninstallApp from 'commands/uninstallApp'
@ -39,6 +40,7 @@ const all: Array<Command<any, any>> = [
listApps,
listenDevices,
signTransaction,
testApdu,
testCrash,
testInterval,
uninstallApp,

7
src/commands/installOsuFirmware.js

@ -11,12 +11,7 @@ type Input = {
firmware: Object,
}
type Result = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
type Result = *
const cmd: Command<Input, Result> = createCommand(
'installOsuFirmware',

26
src/commands/testApdu.js

@ -0,0 +1,26 @@
// @flow
// This is a test example for dev testing purpose.
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
type Input = {
devicePath: string,
apduHex: string,
}
type Result = {
responseHex: string,
}
const cmd: Command<Input, Result> = createCommand('testApdu', ({ apduHex, devicePath }) =>
fromPromise(
withDevice(devicePath)(async transport => {
const res = await transport.exchange(Buffer.from(apduHex, 'hex'))
return { responseHex: res.toString('hex') }
}),
),
)
export default cmd

12
src/components/CurrentAddress/index.js

@ -14,13 +14,11 @@ import { rgba } from 'styles/helpers'
import Box from 'components/base/Box'
import CopyToClipboard from 'components/base/CopyToClipboard'
import Print from 'components/base/Print'
import QRCode from 'components/base/QRCode'
import IconCheck from 'icons/Check'
import IconCopy from 'icons/Copy'
import IconInfoCircle from 'icons/InfoCircle'
import IconPrint from 'icons/Print'
import IconShare from 'icons/Share'
import IconShield from 'icons/Shield'
@ -214,16 +212,6 @@ class CurrentAddress extends PureComponent<Props> {
<FooterButton icon={<IconCopy size={16} />} label="Copy" onClick={copy} />
)}
/>
<Print
data={{ address, amount, accountName }}
render={(print, isLoading) => (
<FooterButton
icon={<IconPrint size={16} />}
label={isLoading ? '...' : 'Print'}
onClick={print}
/>
)}
/>
<FooterButton icon={<IconShare size={16} />} label="Share" onClick={onShare} />
</Footer>
)}

80
src/components/DashboardPage/EmptyState.js

@ -0,0 +1,80 @@
// @flow
import React, { PureComponent } from 'react'
import { i } from 'helpers/staticPath'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import { push } from 'react-router-redux'
import { openModal } from 'reducers/modals'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
const mapDispatchToProps = {
openModal,
push,
}
type Props = {
t: T,
push: Function,
openModal: Function,
}
class EmptyState extends PureComponent<Props, *> {
handleInstallApp = () => {
const { push } = this.props
const url = '/manager'
push(url)
}
render() {
const { t, openModal } = this.props
return (
<Box mt={7} alignItems="center">
<img
alt="emptyState Dashboard logo"
src={i('logos/emptyStateDashboard.png')}
width="413"
height="157"
/>
<Box mt={5} alignItems="center">
<Title>{t('emptyState:dashboard.title')}</Title>
<Description>{t('emptyState:dashboard.desc')}</Description>
<Box mt={3} horizontal justifyContent="space-around" style={{ width: 300 }}>
<Button
padded
primary
style={{ width: 120 }}
onClick={() => openModal('importAccounts')}
>
{t('emptyState:dashboard.buttons.addAccount')}
</Button>
<Button padded primary style={{ width: 120 }} onClick={this.handleInstallApp}>
{t('emptyState:dashboard.buttons.installApp')}
</Button>
</Box>
</Box>
</Box>
)
}
}
const Title = styled(Box).attrs({
ff: 'Museo Sans|Regular',
fontSize: 6,
color: p => p.theme.colors.dark,
})``
const Description = styled(Box).attrs({
ff: 'Open Sans|Regular',
fontSize: 4,
color: p => p.theme.colors.graphite,
})`
margin: 10px auto 25px;
`
export default compose(connect(null, mapDispatchToProps), translate())(EmptyState)

160
src/components/DashboardPage/index.js

@ -28,6 +28,7 @@ import OperationsList from 'components/OperationsList'
import AccountCard from './AccountCard'
import AccountsOrder from './AccountsOrder'
import EmptyState from './EmptyState'
const mapStateToProps = state => ({
accounts: getVisibleAccounts(state),
@ -109,88 +110,93 @@ class DashboardPage extends PureComponent<Props, State> {
return (
<Box flow={7}>
<UpdateNotifier mt={-5} />
<Box horizontal alignItems="flex-end">
<Box grow>
<Text color="dark" ff="Museo Sans" fontSize={7}>
{t(timeFrame)}
</Text>
<Text color="grey" fontSize={5} ff="Museo Sans|Light">
{totalAccounts > 0
? t('dashboard:summary', { count: totalAccounts })
: t('dashboard:noAccounts')}
</Text>
</Box>
{totalAccounts > 0 ? (
<Box>
<PillsDaysCount selectedTime={selectedTime} onChange={this.handleChangeSelectedTime} />
</Box>
</Box>
{totalAccounts > 0 && (
<Fragment>
<BalanceSummary
counterValue={counterValue}
chartId="dashboard-chart"
chartColor={colors.wallet}
accounts={accounts}
selectedTime={selectedTime}
daysCount={daysCount}
renderHeader={({ totalBalance, selectedTime, sinceBalance, refBalance }) => (
<BalanceInfos
t={t}
counterValue={counterValue}
totalBalance={totalBalance}
since={selectedTime}
sinceBalance={sinceBalance}
refBalance={refBalance}
/>
)}
/>
<Box flow={4}>
<Box horizontal alignItems="flex-end">
<Text color="dark" ff="Museo Sans" fontSize={6}>
{t('sidebar:accounts')}
<UpdateNotifier mt={-5} />
<Box horizontal alignItems="flex-end">
<Box grow>
<Text color="dark" ff="Museo Sans" fontSize={7}>
{t(timeFrame)}
</Text>
<Text color="grey" fontSize={5} ff="Museo Sans|Light">
{t('dashboard:summary', { count: totalAccounts })}
</Text>
<Box ml="auto" horizontal flow={1}>
<AccountsOrder />
</Box>
</Box>
<Box flow={5}>
{accountsChunk.map((accountsByLine, i) => (
<Box
key={i} // eslint-disable-line react/no-array-index-key
horizontal
flow={5}
>
{accountsByLine.map(
(account: any, j) =>
account === null ? (
<Box
key={j} // eslint-disable-line react/no-array-index-key
p={4}
flex={1}
/>
) : (
<AccountCard
counterValue={counterValue}
account={account}
daysCount={daysCount}
key={account.id}
onClick={() => push(`/account/${account.id}`)}
/>
),
)}
</Box>
))}
<Box>
<PillsDaysCount
selectedTime={selectedTime}
onChange={this.handleChangeSelectedTime}
/>
</Box>
</Box>
<OperationsList
canShowMore
onAccountClick={account => push(`/account/${account.id}`)}
accounts={accounts}
title={t('dashboard:recentActivity')}
withAccount
/>
</Fragment>
<Fragment>
<BalanceSummary
counterValue={counterValue}
chartId="dashboard-chart"
chartColor={colors.wallet}
accounts={accounts}
selectedTime={selectedTime}
daysCount={daysCount}
renderHeader={({ totalBalance, selectedTime, sinceBalance, refBalance }) => (
<BalanceInfos
t={t}
counterValue={counterValue}
totalBalance={totalBalance}
since={selectedTime}
sinceBalance={sinceBalance}
refBalance={refBalance}
/>
)}
/>
<Box flow={4}>
<Box horizontal alignItems="flex-end">
<Text color="dark" ff="Museo Sans" fontSize={6}>
{t('sidebar:accounts')}
</Text>
<Box ml="auto" horizontal flow={1}>
<AccountsOrder />
</Box>
</Box>
<Box flow={5}>
{accountsChunk.map((accountsByLine, i) => (
<Box
key={i} // eslint-disable-line react/no-array-index-key
horizontal
flow={5}
>
{accountsByLine.map(
(account: any, j) =>
account === null ? (
<Box
key={j} // eslint-disable-line react/no-array-index-key
p={4}
flex={1}
/>
) : (
<AccountCard
counterValue={counterValue}
account={account}
daysCount={daysCount}
key={account.id}
onClick={() => push(`/account/${account.id}`)}
/>
),
)}
</Box>
))}
</Box>
</Box>
<OperationsList
canShowMore
onAccountClick={account => push(`/account/${account.id}`)}
accounts={accounts}
title={t('dashboard:recentActivity')}
withAccount
/>
</Fragment>
</Box>
) : (
<EmptyState />
)}
</Box>
)

9
src/components/ManagerPage/EnsureDashboard.js

@ -3,8 +3,7 @@ import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import isEqual from 'lodash/isEqual'
// import type { Device, T } from 'types/common'
import type { Device } from 'types/common'
import type { Device, T } from 'types/common'
import getDeviceInfo from 'commands/getDeviceInfo'
@ -16,7 +15,7 @@ type DeviceInfo = {
}
type Props = {
// t: T,
t: T,
device: Device,
children: Function,
}
@ -75,7 +74,7 @@ class EnsureDashboard extends PureComponent<Props, State> {
render() {
const { deviceInfo, error } = this.state
const { children } = this.props
const { children, t } = this.props
if (deviceInfo) {
return children(deviceInfo)
@ -84,7 +83,7 @@ class EnsureDashboard extends PureComponent<Props, State> {
return error ? (
<Fragment>
<span>{error.message}</span>
<span>Please make sure your device is on the dashboard screen</span>
<span>{t('manager:erros:noDashboard')}</span>
</Fragment>
) : null
}

9
src/components/ManagerPage/EnsureDevice.js

@ -4,8 +4,7 @@ import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import { compose } from 'redux'
// import type { Device, T } from 'types/common'
import type { Device } from 'types/common'
import type { Device, T } from 'types/common'
import { getCurrentDevice, getDevices } from 'reducers/devices'
@ -15,7 +14,7 @@ const mapStateToProps = state => ({
})
type Props = {
// t: T,
t: T,
device: ?Device,
nbDevices: number,
children: Function,
@ -29,8 +28,8 @@ class EnsureDevice extends PureComponent<Props, State> {
}
render() {
const { device, nbDevices, children } = this.props
return device ? children(device, nbDevices) : <span>Please connect your device</span>
const { device, nbDevices, children, t } = this.props
return device ? children(device, nbDevices) : <span>{t('manager:errors.noDevice')}</span>
}
}

9
src/components/ManagerPage/EnsureGenuine.js

@ -4,13 +4,12 @@ import { translate } from 'react-i18next'
import isEqual from 'lodash/isEqual'
import type { Node } from 'react'
// import type { Device, T } from 'types/common'
import type { Device } from 'types/common'
import type { Device, T } from 'types/common'
import getIsGenuine from 'commands/getIsGenuine'
type Props = {
// t: T,
t: T,
device: Device,
children: Node,
}
@ -69,7 +68,7 @@ class EnsureGenuine extends PureComponent<Props, State> {
render() {
const { error, genuine } = this.state
const { children } = this.props
const { children, t } = this.props
if (genuine) {
return children
@ -78,7 +77,7 @@ class EnsureGenuine extends PureComponent<Props, State> {
return error ? (
<Fragment>
<span>{error.message}</span>
<span>You did not approve request on your device or your device is not genuine</span>
<span>{t('manager:errors.noGenuine')}</span>
</Fragment>
) : null
}

31
src/components/ManagerPage/FirmwareUpdate.js

@ -7,11 +7,12 @@ import isEmpty from 'lodash/isEmpty'
import type { Device, T } from 'types/common'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import installOsuFirmware from 'commands/installOsuFirmware'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
// let CACHED_LATEST_FIRMWARE = null
let CACHED_LATEST_FIRMWARE = null
type FirmwareInfos = {
name: string,
@ -19,7 +20,7 @@ type FirmwareInfos = {
}
type DeviceInfos = {
targetId: number,
targetId: number | string,
version: string,
}
@ -43,7 +44,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
}
componentDidUpdate() {
if (/* !CACHED_LATEST_FIRMWARE || */ isEmpty(this.state.latestFirmware)) {
if (!CACHED_LATEST_FIRMWARE || isEmpty(this.state.latestFirmware)) {
this.fetchLatestFirmware()
}
}
@ -57,23 +58,29 @@ class FirmwareUpdate extends PureComponent<Props, State> {
fetchLatestFirmware = async () => {
const { infos } = this.props
const latestFirmware =
// CACHED_LATEST_FIRMWARE ||
await getLatestFirmwareForDevice
CACHED_LATEST_FIRMWARE ||
(await getLatestFirmwareForDevice
.send({ targetId: infos.targetId, version: infos.version })
.toPromise()
.toPromise())
if (
!isEmpty(latestFirmware) &&
!isEqual(this.state.latestFirmware, latestFirmware) &&
!this._unmounting
) {
// CACHED_LATEST_FIRMWARE = latestFirmware
CACHED_LATEST_FIRMWARE = latestFirmware
this.setState({ latestFirmware })
}
}
installFirmware = async () => {
installFirmware = (firmware: FirmwareInfos) => async () => {
try {
// TODO
const {
device: { path: devicePath },
} = this.props
const { success } = await installOsuFirmware.send({ devicePath, firmware }).toPromise()
if (success) {
this.fetchLatestFirmware()
}
} catch (err) {
console.log(err)
}
@ -94,9 +101,9 @@ class FirmwareUpdate extends PureComponent<Props, State> {
</Box>
<Card flow={2} {...props}>
<Box horizontal align="center" flow={2}>
<Box ff="Museo Sans">{`Latest firmware: ${latestFirmware.name}`}</Box>
<Button outline onClick={this.installFirmware}>
{'Install'}
<Box ff="Museo Sans">{`${t('manager:latestFirmware')}: ${latestFirmware.name}`}</Box>
<Button outline onClick={this.installFirmware(latestFirmware)}>
{t('manager:install')}
</Button>
</Box>
<Box

25
src/components/ManagerPage/index.js

@ -4,15 +4,22 @@ import React, { Fragment } from 'react'
import { translate } from 'react-i18next'
import type { Node } from 'react'
import type { T } from 'types/common'
import type { T, Device } from 'types/common'
import AppsList from './AppsList'
// import DeviceInfos from './DeviceInfos'
// import FirmwareUpdate from './FirmwareUpdate'
import FirmwareUpdate from './FirmwareUpdate'
import EnsureDevice from './EnsureDevice'
import EnsureDashboard from './EnsureDashboard'
import EnsureGenuine from './EnsureGenuine'
type DeviceInfo = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
type Props = {
t: T,
}
@ -20,24 +27,23 @@ type Props = {
const ManagerPage = ({ t }: Props): Node => (
<Fragment>
<EnsureDevice>
{device => (
{(device: Device) => (
<EnsureDashboard device={device}>
{deviceInfo => (
{(deviceInfo: DeviceInfo) => (
<Fragment>
{deviceInfo.mcu && <span>bootloader mode</span>}
{deviceInfo.final && <span>osu mode</span>}
{deviceInfo.mcu && <span> bootloader mode </span>}
{deviceInfo.final && <span> osu mode </span>}
{!deviceInfo.mcu &&
!deviceInfo.final && (
<EnsureGenuine device={device} t={t}>
{/* <FirmwareUpdate
<FirmwareUpdate
infos={{
targetId: deviceInfo.targetId,
version: deviceInfo.version,
}}
device={device}
t={t}
/> */}
/>
<AppsList device={device} targetId={deviceInfo.targetId} />
</EnsureGenuine>
)}
@ -48,4 +54,5 @@ const ManagerPage = ({ t }: Props): Node => (
</EnsureDevice>
</Fragment>
)
export default translate()(ManagerPage)

17
src/components/SideBar/index.js

@ -58,6 +58,7 @@ type Props = {
t: T,
openModal: Function,
updateStatus: UpdateStatus,
accounts: Account[],
}
const mapStateToProps = state => ({
@ -71,7 +72,7 @@ const mapDispatchToProps: Object = {
class SideBar extends PureComponent<Props> {
render() {
const { t, openModal, updateStatus } = this.props
const { t, openModal, updateStatus, accounts } = this.props
return (
<Container bg="white">
@ -110,7 +111,11 @@ class SideBar extends PureComponent<Props> {
</Tooltip>
</CapsSubtitle>
<GrowScroll pb={4} px={4} flow={2}>
<AccountsList />
{accounts.length > 0 ? (
<AccountsList />
) : (
<NoAccountsText>{t('emptyState:sidebar.text')}</NoAccountsText>
)}
</GrowScroll>
</Box>
</Box>
@ -153,3 +158,11 @@ export default compose(
connect(mapStateToProps, mapDispatchToProps, null, { pure: false }),
translate(),
)(SideBar)
export const NoAccountsText = styled(Box).attrs({
ff: 'Open Sans|Regular',
fontSize: 3,
color: p => p.theme.colors.grey,
shrink: true,
mt: 3,
})``

25
src/components/modals/Debug.js

@ -5,15 +5,18 @@ import React, { Component } from 'react'
import Modal, { ModalBody, ModalTitle, ModalContent } from 'components/base/Modal'
import Button from 'components/base/Button'
import Box from 'components/base/Box'
import Input from 'components/base/Input'
import EnsureDevice from 'components/ManagerPage/EnsureDevice'
import { getDerivations } from 'helpers/derivations'
import getAddress from 'commands/getAddress'
import testInterval from 'commands/testInterval'
import testCrash from 'commands/testCrash'
import testApdu from 'commands/testApdu'
class Debug extends Component<*, *> {
state = {
logs: [],
apdu: '',
}
onStartPeriod = (period: number) => () => {
@ -62,6 +65,12 @@ class Debug extends Component<*, *> {
}
periodSubs = []
runApdu = (device: *) => () => {
testApdu
.send({ devicePath: device.path, apduHex: this.state.apdu })
.subscribe(o => this.log(o.responseHex), e => this.error(e))
}
log = (txt: string) => {
this.setState(({ logs }) => ({ logs: logs.concat({ txt, type: 'log' }) }))
}
@ -103,6 +112,22 @@ class Debug extends Component<*, *> {
</Button>
<Button onClick={this.cancelAllPeriods}>Cancel</Button>
</Box>
<EnsureDevice>
{device => (
<Box horizontal style={{ padding: 10 }}>
<Box grow>
<Input
placeholder="APDU hex ( e.g. E016000000 )"
value={this.state.apdu}
onChange={apdu => this.setState({ apdu })}
/>
</Box>
<Button onClick={this.runApdu(device)} primary>
RUN
</Button>
</Box>
)}
</EnsureDevice>
</Box>
<Box
style={{

4
src/components/modals/ImportAccounts/steps/02-step-connect-device.js

@ -32,8 +32,8 @@ function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps)
t={t}
deviceSelected={currentDevice}
currency={currency}
onStatusChange={s => {
if (s === 'connected') {
onStatusChange={(deviceStatus, appStatus) => {
if (appStatus === 'success') {
setState({ isAppOpened: true })
}
}}

2
src/helpers/firmware/installOsuFirmware.js

@ -9,7 +9,7 @@ type Input = {
firmware: Object,
}
type Result = *
type Result = Promise<{ success: boolean, error?: any }>
const buildOsuParams = buildParamsFromFirmware('osu')

2
src/main/bridge.js

@ -27,7 +27,7 @@ const forkBundlePath = path.resolve(__dirname, `${__DEV__ ? '../../' : './'}dist
const bootInternalProcess = () => {
console.log('booting internal process...')
internalProcess = fork(forkBundlePath, {
env: { LEDGER_LIVE_SQLITE_PATH },
env: { ...process.env, LEDGER_LIVE_SQLITE_PATH },
})
internalProcess.on('exit', code => {
console.log(`Internal process ended with code ${code}`)

8
static/i18n/en/emptyState.yml

@ -0,0 +1,8 @@
sidebar:
text: You don’t have any account for the moment. Press the + button to create an account
dashboard:
title: This is a title, use it with caution
desc: Please create a new account or recover an old account from your Ledger device.
buttons:
addAccount: Add Account
installApp: Install App

6
static/i18n/en/manager.yml

@ -3,7 +3,13 @@ tabs:
device: My device
install: Install
allApps: All apps
firmwareUpdate: Firmware update
latestFirmware: Latest firmware
plugYourDevice:
title: Plug your device
desc: Lorem Ipsum is simply dummy text of the printing and typesetting industry’s standard dummy text
cta: Plug my device
errors:
noDevice: Please make sur your device is connected
noDashboard: Please make sure your device is on the dashboard screen
noGenuine: You did not approve request on your device or your device is not genuine

BIN
static/images/logos/emptyStateDashboard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Loading…
Cancel
Save