Browse Source

Integrate core account sync (without ops) in the account import process

master
meriadec 7 years ago
parent
commit
8200523b5d
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 7
      src/actions/accounts.js
  2. 81
      src/components/modals/AddAccount/03-step-import.js
  3. 178
      src/components/modals/AddAccount/index.js
  4. 6
      src/internals/usb/wallet/index.js
  5. 2
      src/internals/usb/wallet/scanAccountsOnDevice.js
  6. 4
      src/reducers/accounts.js

7
src/actions/accounts.js

@ -43,16 +43,9 @@ export type AddAccount = Account => (Function, Function) => void
export const addAccount: AddAccount = payload => (dispatch, getState) => {
const {
settings: { orderAccounts },
accounts,
} = getState()
dispatch({ type: 'ADD_ACCOUNT', payload })
dispatch(updateOrderAccounts(orderAccounts))
// Start sync accounts the first time you add an account
if (accounts.length === 0) {
const accounts = [payload]
startSyncAccounts(accounts)
}
}
export type RemoveAccount = Account => { type: string, payload: Account }

81
src/components/modals/AddAccount/03-step-import.js

@ -25,74 +25,31 @@ const AccountItem = styled(AccountCard)`
`
type Props = {
accountsImport: Object,
archivedAccounts: Account[],
currency?: ?CryptoCurrency,
importProgress: boolean,
onSelectAccount?: Function,
selectedAccounts?: Array<number>,
scannedAccounts: Account[],
selectedAccounts: Account[],
existingAccounts: Account[],
onToggleAccount: Function,
}
function StepImport(props: Props) {
const hasAccountsImports = Object.keys(props.accountsImport).length > 0
const unit = props.currency && props.currency.units[0]
const { scannedAccounts, selectedAccounts, existingAccounts, onToggleAccount } = props
return (
<Box>
{props.importProgress ? (
<Box alignItems="center">In progress...</Box>
) : (
hasAccountsImports && <Box mb={-2}>Accounts</Box>
)}
{hasAccountsImports && (
<AccountsContainer pt={5}>
{Object.keys(props.accountsImport).map(k => {
const a = props.accountsImport[k]
return (
<AccountItemWrapper key={a.id}>
<AccountItem
selected={props.selectedAccounts && props.selectedAccounts.includes(a.id)}
onClick={props.onSelectAccount && props.onSelectAccount(a.id)}
account={{
...a,
currencyId: props.currency && props.currency.id,
name: `Account ${a.accountIndex}`,
currency: props.currency,
unit,
}}
counterValue="USD"
daysCount={365}
/>
</AccountItemWrapper>
)
})}
</AccountsContainer>
)}
{!props.importProgress &&
props.archivedAccounts.length > 0 && (
<Fragment>
<Box pb={3}>Archived accounts</Box>
<AccountsContainer>
{props.archivedAccounts.map(a => (
<AccountItemWrapper key={a.id}>
<AccountItem
selected={props.selectedAccounts && props.selectedAccounts.includes(a.id)}
onClick={props.onSelectAccount && props.onSelectAccount(a.id)}
account={a}
counterValue="USD"
daysCount={365}
/>
</AccountItemWrapper>
))}
</AccountsContainer>
</Fragment>
)}
<Box flow={4}>
{scannedAccounts.map(account => {
const isSelected = selectedAccounts.find(a => a.id === account.id)
const isExisting = existingAccounts.find(a => a.id === account.id && a.archived === false)
return (
<Box
bg="lightgrey"
key={account.id}
onClick={onToggleAccount && !isExisting ? () => onToggleAccount(account) : undefined}
>
{isSelected && `[SELECTED]`} {isExisting && `[ALREADY IMPORTED]`} {account.name}
</Box>
)
})}
</Box>
)
}
StepImport.defaultProps = {
onSelectAccount: undefined,
selectedAccounts: [],
}
export default StepImport

178
src/components/modals/AddAccount/index.js

@ -13,8 +13,14 @@ import type { Device, T } from 'types/common'
import { MODAL_ADD_ACCOUNT } from 'config/constants'
import { closeModal } from 'reducers/modals'
import { canCreateAccount, getAccounts, getArchivedAccounts } from 'reducers/accounts'
import { sendEvent } from 'renderer/events'
import {
canCreateAccount,
getAccounts,
getArchivedAccounts,
decodeAccount,
} from 'reducers/accounts'
import { runJob } from 'renderer/events'
import { addAccount, updateAccount } from 'actions/accounts'
@ -35,7 +41,7 @@ const GET_STEPS = t => [
]
const mapStateToProps = state => ({
accounts: getAccounts(state),
existingAccounts: getAccounts(state),
archivedAccounts: getArchivedAccounts(state),
canCreateAccount: canCreateAccount(state),
})
@ -47,7 +53,7 @@ const mapDispatchToProps = {
}
type Props = {
accounts: Account[],
existingAccounts: Account[],
addAccount: Function,
archivedAccounts: Account[],
canCreateAccount: boolean,
@ -57,23 +63,29 @@ type Props = {
}
type State = {
accountsImport: Object,
stepIndex: number,
currency: ?CryptoCurrency,
deviceSelected: ?Device,
selectedAccounts: Account[],
scannedAccounts: Account[],
// TODO: what's that.
fetchingCounterValues: boolean,
selectedAccounts: Array<number>,
appStatus: ?string,
stepIndex: number,
}
const INITIAL_STATE = {
accountsImport: {},
stepIndex: 0,
currency: null,
deviceSelected: null,
fetchingCounterValues: false,
selectedAccounts: [],
scannedAccounts: [],
fetchingCounterValues: false,
appStatus: null,
stepIndex: 0,
}
class AddAccountModal extends PureComponent<Props, State> {
@ -88,29 +100,41 @@ class AddAccountModal extends PureComponent<Props, State> {
const { stepIndex: nextStepIndex } = nextState
if (!fetchingCounterValues && stepIndex === 0 && nextStepIndex === 1) {
// TODO: seems shady to do this here..............
await this.fetchCounterValues()
}
}
componentWillUnmount() {
this.killProcess()
this.handleReset()
ipcRenderer.removeListener('msg', this.handleMsgEvent)
clearTimeout(this._timeout)
}
importsAccounts() {
const { accounts } = this.props
async startScanAccountsDevice() {
const { deviceSelected, currency } = this.state
if (!deviceSelected || !currency) {
return
}
sendEvent('usb', 'wallet.getAccounts', {
pathDevice: deviceSelected.path,
currencyId: currency.id,
currentAccounts: accounts.map(acc => acc.id),
})
try {
// scan every account for given currency and device
await runJob({
channel: 'usb',
job: 'wallet.scanAccountsOnDevice',
successResponse: 'wallet.scanAccountsOnDevice.success',
errorResponse: 'wallet.scanAccountsOnDevice.fail',
data: {
devicePath: deviceSelected.path,
currencyId: currency.id,
},
})
// go to final step
this.setState({ stepIndex: 3 })
} catch (err) {
console.log(err)
}
}
async fetchCounterValues() {
@ -160,75 +184,46 @@ class AddAccountModal extends PureComponent<Props, State> {
_steps = GET_STEPS(this.props.t)
handleMsgEvent = (e, { data, type }) => {
const { accountsImport, currency } = this.state
const { addAccount } = this.props
const { addAccount, existingAccounts } = this.props
if (type === 'wallet.getAccounts.start') {
this._pid = data.pid
}
if (type === 'wallet.getAccounts.progress') {
this.setState(prev => ({
stepIndex: 2,
accountsImport: {
...(data !== null
? {
[data.id]: {
...data,
name: `Account ${data.accountIndex + 1}`,
},
}
: {}),
...prev.accountsImport,
},
}))
if (type === 'wallet.scanAccountsOnDevice.accountScanned') {
// create Account from AccountRaw account scanned on device
const account = {
...decodeAccount(data),
archived: true,
}
if (currency && data && data.finish) {
const { accountIndex, finish, ...account } = data
addAccount({
...account,
// As data is passed inside electron event system,
// dates are converted to their string equivalent
//
// so, quick & dirty way to put back Date objects
operations: account.operations.map(op => ({
...op,
date: new Date(op.date),
})),
name: `Account ${accountIndex + 1}`,
archived: true,
currency,
unit: currency.units[0],
})
// add it to the reducer if needed, archived
if (!existingAccounts.find(a => a.id === account.id)) {
addAccount(account)
}
}
if (type === 'wallet.getAccounts.success') {
this.setState({
selectedAccounts: Object.keys(accountsImport).map(k => accountsImport[k].id),
stepIndex: 3,
})
this.setState(state => ({
scannedAccounts: [...state.scannedAccounts, account],
}))
}
}
handleChangeDevice = d => this.setState({ deviceSelected: d })
handleSelectAccount = a => () =>
this.setState(prev => ({
selectedAccounts: prev.selectedAccounts.includes(a)
? prev.selectedAccounts.filter(x => x !== a)
: [a, ...prev.selectedAccounts],
}))
handleToggleAccount = account => {
const { selectedAccounts } = this.state
const isSelected = selectedAccounts.find(a => a === account)
this.setState({
selectedAccounts: isSelected
? selectedAccounts.filter(a => a !== account)
: [...selectedAccounts, account],
})
}
handleChangeCurrency = (currency: CryptoCurrency) => this.setState({ currency })
handleChangeStatus = (deviceStatus, appStatus) => this.setState({ appStatus })
handleImportAccount = () => {
const { archivedAccounts, updateAccount, closeModal } = this.props
const { updateAccount } = this.props
const { selectedAccounts } = this.state
const accounts = archivedAccounts.filter(a => selectedAccounts.includes(a.id))
accounts.forEach(a => updateAccount({ ...a, archived: false }))
selectedAccounts.forEach(a => updateAccount({ ...a, archived: false }))
this.setState({ selectedAccounts: [] })
closeModal(MODAL_ADD_ACCOUNT)
}
@ -242,22 +237,12 @@ class AddAccountModal extends PureComponent<Props, State> {
}
handleReset = () => {
this.killProcess()
clearTimeout(this._timeout)
this.setState(INITIAL_STATE)
}
killProcess = () =>
sendEvent('msg', 'kill.process', {
pid: this._pid,
})
_timeout = undefined
_pid = null
renderStep() {
const { accounts, archivedAccounts, t } = this.props
const { stepIndex, currency, accountsImport, deviceSelected, selectedAccounts } = this.state
const { t, existingAccounts } = this.props
const { stepIndex, scannedAccounts, currency, deviceSelected, selectedAccounts } = this.state
const step = this._steps[stepIndex]
if (!step) {
return null
@ -269,31 +254,28 @@ class AddAccountModal extends PureComponent<Props, State> {
const stepProps = {
t,
currency,
// STEP CURRENCY
...props(stepIndex === 0, {
onChangeCurrency: this.handleChangeCurrency,
}),
// STEP CONNECT DEVICE
...props(stepIndex === 1, {
deviceSelected,
onStatusChange: this.handleChangeStatus,
onChangeDevice: this.handleChangeDevice,
}),
// STEP ACCOUNT IMPORT PROGRESS
...props(stepIndex === 2, {
accountsImport,
importProgress: true,
selectedAccounts,
scannedAccounts,
existingAccounts,
}),
// STEP FINISH AND SELECT ACCOUNTS
...props(stepIndex === 3, {
accountsImport: Object.keys(accountsImport).reduce((result, k) => {
const account = accountsImport[k]
const existingAccount = accounts.find(a => a.id === account.id)
if (!existingAccount || (existingAccount && existingAccount.archived)) {
result[account.id] = account
}
return result
}, {}),
archivedAccounts: archivedAccounts.filter(a => !accountsImport[a.id]),
importProgress: false,
onSelectAccount: this.handleSelectAccount,
onToggleAccount: this.handleToggleAccount,
selectedAccounts,
scannedAccounts,
existingAccounts,
}),
}
@ -310,7 +292,7 @@ class AddAccountModal extends PureComponent<Props, State> {
case 1:
onClick = () => {
this.handleNextStep()
this.importsAccounts()
this.startScanAccountsDevice()
}
break

6
src/internals/usb/wallet/index.js

@ -18,11 +18,13 @@ export default (sendEvent: Function) => ({
currencyId: string,
}) => {
try {
sendEvent('wallet.scanAccountsOnDevice.start', { pid: process.pid }, { kill: false })
const accounts = await scanAccountsOnDevice({
devicePath,
currencyId,
onAccountScanned: account =>
sendEvent('wallet.scanAccountsOnDevice.accountScanned', account),
onAccountScanned: account => {
sendEvent('wallet.scanAccountsOnDevice.accountScanned', account, { kill: false })
},
})
sendEvent('wallet.scanAccountsOnDevice.success', accounts)
} catch (err) {

2
src/internals/usb/wallet/scanAccountsOnDevice.js

@ -173,7 +173,7 @@ async function buildRawAccount({
xpub,
path: accountPath, // TODO: this should be called `accountPath` in Account/AccountRaw types
rootPath: walletPath, // TODO: this should be `walletPath` in Account/AccountRaw types
name: '', // TODO: placeholder name?
name: `Account ${accountIndex}`, // TODO: placeholder name?
address: bitcoinAddress, // TODO: discuss about the utility of storing it here
addresses,
balance: 0,

4
src/reducers/accounts.js

@ -115,6 +115,10 @@ export function canCreateAccount(state: State): boolean {
return every(getAccounts(state), a => get(a, 'operations.length', 0) > 0)
}
export function decodeAccount(account: AccountRaw): Account {
return accountModel.decode({ data: account })
}
// Yeah. `any` should be `AccountRaw[]` but it can also be a map
// of wrapped accounts. And as flow is apparently incapable of doing
// such a simple thing, let's put any, right? I don't care.

Loading…
Cancel
Save