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