meriadec
7 years ago
12 changed files with 366 additions and 155 deletions
@ -0,0 +1,151 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import styled from 'styled-components' |
|||
|
|||
import { runJob } from 'renderer/events' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import Modal, { ModalBody } from 'components/base/Modal' |
|||
|
|||
import type { Device } from 'types/common' |
|||
|
|||
import ManagerApp from './ManagerApp' |
|||
|
|||
const List = styled(Box).attrs({ |
|||
horizontal: true, |
|||
m: -2, |
|||
})` |
|||
flex-wrap: wrap; |
|||
` |
|||
|
|||
let CACHED_APPS = null |
|||
|
|||
const ICONS_FALLBACK = { |
|||
bitcoin_testnet: 'bitcoin', |
|||
} |
|||
|
|||
type Status = 'loading' | 'idle' | 'busy' | 'success' | 'error' |
|||
|
|||
type jobHandlerOptions = { |
|||
job: string, |
|||
successResponse: string, |
|||
errorResponse: string, |
|||
} |
|||
|
|||
type LedgerApp = { |
|||
name: string, |
|||
icon: string, |
|||
app: Object, |
|||
} |
|||
|
|||
type Props = { |
|||
device: Device, |
|||
} |
|||
|
|||
type State = { |
|||
status: Status, |
|||
error: string | null, |
|||
appsList: LedgerApp[], |
|||
} |
|||
|
|||
class AppsList extends PureComponent<Props, State> { |
|||
state = { |
|||
status: 'loading', |
|||
error: null, |
|||
appsList: [], |
|||
} |
|||
|
|||
componentDidMount() { |
|||
this.fetchList() |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
this._unmounted = true |
|||
} |
|||
|
|||
_unmounted = false |
|||
|
|||
async fetchList() { |
|||
const appsList = |
|||
CACHED_APPS || |
|||
(await runJob({ |
|||
channel: 'usb', |
|||
job: 'manager.listApps', |
|||
successResponse: 'manager.listAppsSuccess', |
|||
errorResponse: 'manager.listAppsError', |
|||
})) |
|||
CACHED_APPS = appsList |
|||
if (!this._unmounted) { |
|||
this.setState({ appsList, status: 'idle' }) |
|||
} |
|||
} |
|||
|
|||
createDeviceJobHandler = (options: jobHandlerOptions) => (args: { app: any }) => async () => { |
|||
const appParams = args.app |
|||
this.setState({ status: 'busy' }) |
|||
try { |
|||
const { job, successResponse, errorResponse } = options |
|||
const { device: { path: devicePath } } = this.props |
|||
const data = { appParams, devicePath } |
|||
await runJob({ channel: 'usb', job, successResponse, errorResponse, data }) |
|||
this.setState({ status: 'success' }) |
|||
} catch (err) { |
|||
this.setState({ status: 'error', error: err.message }) |
|||
} |
|||
} |
|||
|
|||
handleInstall = this.createDeviceJobHandler({ |
|||
job: 'manager.installApp', |
|||
successResponse: 'device.appInstalled', |
|||
errorResponse: 'device.appInstallError', |
|||
}) |
|||
|
|||
handleUninstall = this.createDeviceJobHandler({ |
|||
job: 'manager.uninstallApp', |
|||
successResponse: 'device.appUninstalled', |
|||
errorResponse: 'device.appUninstallError', |
|||
}) |
|||
|
|||
handleCloseModal = () => this.setState({ status: 'idle' }) |
|||
|
|||
render() { |
|||
const { status, error } = this.state |
|||
return ( |
|||
<List> |
|||
{this.state.appsList.map(c => ( |
|||
<ManagerApp |
|||
key={c.name} |
|||
name={c.name} |
|||
icon={ICONS_FALLBACK[c.icon] || c.icon} |
|||
onInstall={this.handleInstall(c)} |
|||
onUninstall={this.handleUninstall(c)} |
|||
/> |
|||
))} |
|||
<Modal |
|||
isOpened={status !== 'idle' && status !== 'loading'} |
|||
render={() => ( |
|||
<ModalBody p={6} align="center" justify="center" style={{ height: 300 }}> |
|||
{status === 'busy' ? ( |
|||
<Box>{'Loading...'}</Box> |
|||
) : status === 'error' ? ( |
|||
<Box> |
|||
<div>{'error happened'}</div> |
|||
{error} |
|||
<button onClick={this.handleCloseModal}>close</button> |
|||
</Box> |
|||
) : status === 'success' ? ( |
|||
<Box> |
|||
{'success'} |
|||
<button onClick={this.handleCloseModal}>close</button> |
|||
</Box> |
|||
) : null} |
|||
</ModalBody> |
|||
)} |
|||
/> |
|||
</List> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default AppsList |
@ -0,0 +1,75 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
|
|||
import { runJob } from 'renderer/events' |
|||
import Text from 'components/base/Text' |
|||
import Box, { Card } from 'components/base/Box' |
|||
import Button from 'components/base/Button' |
|||
|
|||
import type { Device, MemoryInfos } from 'types/common' |
|||
|
|||
import MemInfos from './MemInfos' |
|||
|
|||
type Props = { |
|||
device: Device, |
|||
} |
|||
|
|||
type State = { |
|||
memoryInfos: ?MemoryInfos, |
|||
isLoading: boolean, |
|||
} |
|||
|
|||
class DeviceInfos extends PureComponent<Props, State> { |
|||
state = { |
|||
isLoading: false, |
|||
memoryInfos: null, |
|||
} |
|||
|
|||
handleGetMemInfos = async () => { |
|||
try { |
|||
this.setState({ isLoading: true }) |
|||
const { device: { path: devicePath } } = this.props |
|||
const memoryInfos = await runJob({ |
|||
channel: 'usb', |
|||
job: 'manager.getMemInfos', |
|||
successResponse: 'device.getMemInfosSuccess', |
|||
errorResponse: 'device.getMemInfosError', |
|||
data: { devicePath }, |
|||
}) |
|||
this.setState({ memoryInfos, isLoading: false }) |
|||
} catch (err) { |
|||
this.setState({ isLoading: false }) |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
const { device } = this.props |
|||
const { memoryInfos, isLoading } = this.state |
|||
|
|||
const title = ( |
|||
<Text> |
|||
{device.manufacturer} |
|||
<Text ff="Museo Sans|Bold">{` ${device.product}`}</Text> |
|||
</Text> |
|||
) |
|||
return ( |
|||
<Card title={title} p={6}> |
|||
{memoryInfos ? ( |
|||
<MemInfos memoryInfos={memoryInfos} /> |
|||
) : ( |
|||
<Box flow={2}> |
|||
<Box horizontal> |
|||
<Button primary onClick={this.handleGetMemInfos} disabled={isLoading}> |
|||
{isLoading ? 'Loading...' : 'Read device memory'} |
|||
</Button> |
|||
</Box> |
|||
{isLoading && <Box>{'If asked, confirm operation on device'}</Box>} |
|||
</Box> |
|||
)} |
|||
</Card> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default DeviceInfos |
@ -0,0 +1,41 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
import styled from 'styled-components' |
|||
|
|||
import Box from 'components/base/Box' |
|||
|
|||
import type { MemoryInfos } from 'types/common' |
|||
|
|||
const Container = styled(Box).attrs({ |
|||
bg: 'lightgrey', |
|||
horizontal: true, |
|||
})` |
|||
border-radius: ${p => p.theme.radii[1]}px; |
|||
overflow: hidden; |
|||
height: 24px; |
|||
` |
|||
|
|||
const Step = styled(Box).attrs({ |
|||
bg: p => p.theme.colors[p.c || 'grey'], |
|||
px: 1, |
|||
color: 'white', |
|||
})` |
|||
width: ${p => (p.last ? '' : `${p.percent}%`)}; |
|||
flex-grow: ${p => (p.last ? '1' : '')}; |
|||
text-align: ${p => (p.last ? 'right' : '')}; |
|||
` |
|||
|
|||
export default function MemInfos(props: { memoryInfos: MemoryInfos }) { |
|||
const { memoryInfos: infos } = props |
|||
const totalSize = infos.applicationsSize + infos.systemSize |
|||
const appPercent = infos.applicationsSize * 100 / totalSize |
|||
return ( |
|||
<Container> |
|||
<Step c="wallet" percent={appPercent}>{`${Math.round( |
|||
infos.applicationsSize / 1000, |
|||
)}kb`}</Step>
|
|||
<Step last>{`${Math.round(infos.freeSize / 1000)}kb`}</Step> |
|||
</Container> |
|||
) |
|||
} |
@ -0,0 +1,19 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
import { storiesOf } from '@storybook/react' |
|||
|
|||
import MemInfos from '../MemInfos' |
|||
|
|||
const memoryInfos = { |
|||
applicationsSize: 36862, |
|||
freeSize: 118784, |
|||
systemSize: 171776, |
|||
totalAppSlots: 30, |
|||
usedAppSlots: 2, |
|||
} |
|||
|
|||
const stories = storiesOf('Components', module) |
|||
|
|||
stories.add('MemInfos', () => <MemInfos memoryInfos={memoryInfos} />) |
@ -0,0 +1,3 @@ |
|||
tabs: |
|||
apps: Apps |
|||
device: My device |
Loading…
Reference in new issue