You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
6.2 KiB
230 lines
6.2 KiB
// @flow
|
|
|
|
import React, { PureComponent } from 'react'
|
|
import styled from 'styled-components'
|
|
import { translate } from 'react-i18next'
|
|
|
|
import type { Device, T } from 'types/common'
|
|
|
|
import listApps from 'commands/listApps'
|
|
import installApp from 'commands/installApp'
|
|
import uninstallApp from 'commands/uninstallApp'
|
|
|
|
import Box from 'components/base/Box'
|
|
import Modal, { ModalBody } from 'components/base/Modal'
|
|
import Tooltip from 'components/base/Tooltip'
|
|
import Text from 'components/base/Text'
|
|
import Progress from 'components/base/Progress'
|
|
|
|
import ExclamationCircle from 'icons/ExclamationCircle'
|
|
import Update from 'icons/Update'
|
|
import Trash from 'icons/Trash'
|
|
import CheckCircle from 'icons/CheckCircle'
|
|
|
|
import ManagerApp from './ManagerApp'
|
|
import AppSearchBar from './AppSearchBar'
|
|
|
|
const List = styled(Box).attrs({
|
|
horizontal: true,
|
|
m: -3,
|
|
})`
|
|
flex-wrap: wrap;
|
|
`
|
|
|
|
let CACHED_APPS = null
|
|
|
|
const ICONS_FALLBACK = {
|
|
bitcoin_testnet: 'bitcoin',
|
|
}
|
|
|
|
type Status = 'loading' | 'idle' | 'busy' | 'success' | 'error'
|
|
type Mode = '' | 'installing' | 'uninstalling'
|
|
|
|
type LedgerApp = {
|
|
name: string,
|
|
version: string,
|
|
icon: string,
|
|
app: Object,
|
|
bolos_version: {
|
|
min: number,
|
|
max: number,
|
|
},
|
|
}
|
|
|
|
type Props = {
|
|
device: Device,
|
|
targetId: string | number,
|
|
t: T,
|
|
}
|
|
|
|
type State = {
|
|
status: Status,
|
|
error: string | null,
|
|
appsList: LedgerApp[],
|
|
app: string,
|
|
mode: Mode,
|
|
}
|
|
|
|
class AppsList extends PureComponent<Props, State> {
|
|
state = {
|
|
status: 'loading',
|
|
error: null,
|
|
appsList: [],
|
|
app: '',
|
|
mode: '',
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.fetchAppList()
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this._unmounted = true
|
|
}
|
|
|
|
_unmounted = false
|
|
|
|
async fetchAppList() {
|
|
try {
|
|
const { targetId } = this.props
|
|
const appsList = CACHED_APPS || (await listApps.send({ targetId }).toPromise())
|
|
CACHED_APPS = appsList
|
|
if (!this._unmounted) {
|
|
this.setState({ appsList, status: 'idle' })
|
|
}
|
|
} catch (err) {
|
|
this.setState({ status: 'error', error: err.message })
|
|
}
|
|
}
|
|
|
|
handleInstallApp = (args: { app: any, name: string }) => async () => {
|
|
const { app: appParams, name } = args
|
|
this.setState({ status: 'busy', app: name, mode: 'installing' })
|
|
try {
|
|
const {
|
|
device: { path: devicePath },
|
|
} = this.props
|
|
const data = { appParams, devicePath }
|
|
await installApp.send(data).toPromise()
|
|
this.setState({ status: 'success', app: '' })
|
|
} catch (err) {
|
|
this.setState({ status: 'error', error: err.message, app: '', mode: '' })
|
|
}
|
|
}
|
|
|
|
handleUninstallApp = (args: { app: any, name: string }) => async () => {
|
|
const { app: appParams, name } = args
|
|
this.setState({ status: 'busy', app: name, mode: 'uninstalling' })
|
|
try {
|
|
const {
|
|
device: { path: devicePath },
|
|
} = this.props
|
|
const data = { appParams, devicePath }
|
|
await uninstallApp.send(data).toPromise()
|
|
this.setState({ status: 'success', app: '' })
|
|
} catch (err) {
|
|
this.setState({ status: 'error', error: err.message, app: '', mode: '' })
|
|
}
|
|
}
|
|
|
|
handleCloseModal = () => this.setState({ status: 'idle', mode: '' })
|
|
|
|
renderModal = () => {
|
|
const { t } = this.props
|
|
const { app, status, error, mode } = this.state
|
|
|
|
return (
|
|
<Modal
|
|
isOpened={status !== 'idle' && status !== 'loading'}
|
|
render={() => (
|
|
<ModalBody p={6} align="center" justify="center" style={{ height: 300 }}>
|
|
{status === 'busy' || status === 'idle' ? (
|
|
<Box align="center" justify="center" flow={3}>
|
|
{mode === 'installing' ? <Update size={30} /> : <Trash size={30} />}
|
|
<Text ff="Museo Sans|Regular" fontSize={6} color="dark">
|
|
{t(`app:manager.apps.${mode}`, { app })}
|
|
</Text>
|
|
<Box my={5} style={{ width: 250 }}>
|
|
<Progress style={{ width: '100%' }} infinite />
|
|
</Box>
|
|
</Box>
|
|
) : status === 'error' ? (
|
|
<Box align="center" justify="center" flow={3}>
|
|
<div>{'error happened'}</div>
|
|
{error}
|
|
<button onClick={this.handleCloseModal}>close</button>
|
|
</Box>
|
|
) : status === 'success' ? (
|
|
<Box align="center" justify="center" flow={3}>
|
|
<Box color="positiveGreen">
|
|
<CheckCircle size={30} />
|
|
</Box>
|
|
<Text ff="Museo Sans|Regular" fontSize={6} color="dark">
|
|
{t(
|
|
`app:manager.apps.${
|
|
mode === 'installing' ? 'installSuccess' : 'uninstallSuccess'
|
|
}`,
|
|
{ app },
|
|
)}
|
|
</Text>
|
|
<button onClick={this.handleCloseModal}>close</button>
|
|
</Box>
|
|
) : null}
|
|
</ModalBody>
|
|
)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
renderList() {
|
|
const { appsList } = this.state
|
|
return (
|
|
<Box>
|
|
<AppSearchBar list={appsList}>
|
|
{items => (
|
|
<List>
|
|
{items.map(c => (
|
|
<ManagerApp
|
|
key={`${c.name}_${c.version}_${c.bolos_version.min}`}
|
|
name={c.name}
|
|
version={`Version ${c.version}`}
|
|
icon={ICONS_FALLBACK[c.icon] || c.icon}
|
|
onInstall={this.handleInstallApp(c)}
|
|
onUninstall={this.handleUninstallApp(c)}
|
|
/>
|
|
))}
|
|
</List>
|
|
)}
|
|
</AppSearchBar>
|
|
{this.renderModal()}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
render() {
|
|
const { t } = this.props
|
|
return (
|
|
<Box flow={6}>
|
|
<Box>
|
|
<Box mb={4} color="dark" ff="Museo Sans" fontSize={5} flow={2} horizontal>
|
|
<span>{t('app:manager.apps.all')}</span>
|
|
<span>
|
|
<Tooltip
|
|
render={() => (
|
|
<Box ff="Open Sans|SemiBold" fontSize={2}>
|
|
{t('app:manager.apps.help')}
|
|
</Box>
|
|
)}
|
|
>
|
|
<ExclamationCircle size={12} />
|
|
</Tooltip>
|
|
</span>
|
|
</Box>
|
|
{this.renderList()}
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default translate()(AppsList)
|
|
|