diff --git a/packages/neuron-ui/src/components/Addresses/index.tsx b/packages/neuron-ui/src/components/Addresses/index.tsx index 25d95b0bbb..f922a12fc9 100644 --- a/packages/neuron-ui/src/components/Addresses/index.tsx +++ b/packages/neuron-ui/src/components/Addresses/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from 'react' import { RouteComponentProps } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { - DetailsList, + ShimmeredDetailsList, TextField, IColumn, DetailsListLayoutMode, @@ -11,19 +11,21 @@ import { getTheme, } from 'office-ui-fabric-react' +import { contextMenu } from 'services/remote' import { StateWithDispatch } from 'states/stateProvider/reducer' -import { appCalls } from 'services/UILayer' - import { useLocalDescription } from 'utils/hooks' import { MIN_CELL_WIDTH, Routes } from 'utils/const' import { localNumberFormatter, shannonToCKBFormatter } from 'utils/formatters' const Addresses = ({ - wallet: { id, addresses = [] }, + app: { + loadings: { addressList: isLoading }, + }, + wallet: { addresses = [], id: walletID }, settings: { showAddressBook = false }, - dispatch, history, + dispatch, }: React.PropsWithoutRef) => { const [t] = useTranslation() useEffect(() => { @@ -34,7 +36,7 @@ const Addresses = ({ const { localDescription, onDescriptionPress, onDescriptionFieldBlur, onDescriptionChange } = useLocalDescription( 'address', - id, + walletID, useMemo( () => addresses.map(({ address: key = '', description = '' }) => ({ @@ -97,15 +99,17 @@ const Addresses = ({ maxWidth: 350, isResizable: true, isCollapsible: false, - onRender: (item?: State.Address, idx?: number) => { - return item && undefined !== idx ? ( + onRender: (item?: State.Address) => { + return item ? ( local.key === item.address) || { description: '' }).description || '' + } + onKeyPress={onDescriptionPress(item.address)} + onBlur={onDescriptionFieldBlur(item.address)} + onChange={onDescriptionChange(item.address)} styles={(props: ITextFieldStyleProps) => { return { root: { @@ -160,27 +164,14 @@ const Addresses = ({ ) return ( - ({ ...col, name: t(col.name) }))} items={addresses} onItemContextMenu={item => { - appCalls.contextMenu({ type: 'addressList', id: item.identifier }) - }} - styles={{ - contentWrapper: { - selectors: { - '.ms-DetailsRow-cell': { - display: 'flex', - alignItems: 'center', - }, - '.text-overflow': { - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - }, - }, + contextMenu({ type: 'addressList', id: item.identifier }) }} /> ) diff --git a/packages/neuron-ui/src/components/ErrorBoundary/index.tsx b/packages/neuron-ui/src/components/ErrorBoundary/index.tsx index 9fd0e25ca9..7b99d2cbf7 100644 --- a/packages/neuron-ui/src/components/ErrorBoundary/index.tsx +++ b/packages/neuron-ui/src/components/ErrorBoundary/index.tsx @@ -1,12 +1,10 @@ import React, { Component } from 'react' -import { appCalls } from 'services/UILayer' import { Stack, Spinner } from 'office-ui-fabric-react' +import { handleViewError } from 'services/remote' const handleError = (error: Error) => { - appCalls.handleViewError(error.toString()) - setTimeout(() => { - window.location.reload() - }, 0) + handleViewError(error.toString()) + window.location.reload() return { hasError: true } } diff --git a/packages/neuron-ui/src/components/GeneralSetting/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/index.tsx index ccf99e36dd..b4ff6155d5 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/index.tsx @@ -3,12 +3,12 @@ import { Stack, Toggle } from 'office-ui-fabric-react' import { useTranslation } from 'react-i18next' import { StateWithDispatch } from 'states/stateProvider/reducer' -import actionCreators from 'states/stateProvider/actionCreators' +import { toggleAddressBook } from 'states/stateProvider/actionCreators' const GeneralSetting = ({ settings: { showAddressBook }, dispatch }: React.PropsWithoutRef) => { const [t] = useTranslation() const onToggle = useCallback(() => { - dispatch(actionCreators.toggleAddressBook()) + dispatch(toggleAddressBook()) }, [dispatch]) return ( diff --git a/packages/neuron-ui/src/components/History/hooks.ts b/packages/neuron-ui/src/components/History/hooks.ts index a8dff387a5..2733e4f59f 100644 --- a/packages/neuron-ui/src/components/History/hooks.ts +++ b/packages/neuron-ui/src/components/History/hooks.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import { AppActions } from 'states/stateProvider/reducer' -import actionCreators from 'states/stateProvider/actionCreators' +import { updateTransactionList } from 'states/stateProvider/actionCreators/transactions' import { queryParsers } from 'utils/parser' const backToTop = () => { @@ -31,8 +31,7 @@ export const useSearch = (search: string = '', walletID: string = '', dispatch: type: AppActions.CleanTransactions, payload: null, }) - - dispatch(actionCreators.getTransactions({ ...params, keywords: params.keywords, walletID })) + updateTransactionList({ ...params, keywords: params.keywords, walletID })(dispatch) }, [search, walletID, dispatch]) return { keywords, onKeywordsChange, setKeywords } } diff --git a/packages/neuron-ui/src/components/History/index.tsx b/packages/neuron-ui/src/components/History/index.tsx index 0f99c15067..cbb19b3572 100644 --- a/packages/neuron-ui/src/components/History/index.tsx +++ b/packages/neuron-ui/src/components/History/index.tsx @@ -34,6 +34,9 @@ registerIcons({ }) const History = ({ + app: { + loadings: { transactionList: isLoading }, + }, wallet: { id }, chain: { transactions: { pageNo = 1, pageSize = 15, totalCount = 0, items = [] }, @@ -64,7 +67,7 @@ const History = ({ iconProps={{ iconName: 'Search', styles: { root: { height: '18px' } } }} /> - + - useCallback(() => { - dispatch(actionCreators.createOrUpdateNetwork({ id, name, remote }, networks)) - }, [id, name, remote, networks, dispatch]) + useCallback(async () => { + const warning = { + type: 'warning', + timestamp: Date.now(), + content: '', + } + if (!name) { + return dispatch({ + type: AppActions.AddNotification, + payload: { + ...warning, + content: i18n.t(Message.NameRequired), + }, + }) + } + if (name.length > MAX_NETWORK_NAME_LENGTH) { + return dispatch({ + type: AppActions.AddNotification, + payload: { + ...warning, + content: i18n.t(Message.LengthOfNameShouldBeLessThanOrEqualTo, { + length: MAX_NETWORK_NAME_LENGTH, + }), + }, + }) + } + if (!remote) { + return dispatch({ + type: AppActions.AddNotification, + payload: { + ...warning, + content: i18n.t(Message.URLRequired), + }, + }) + } + if (!remote.startsWith('http')) { + return dispatch({ + type: AppActions.AddNotification, + payload: { + ...warning, + content: i18n.t(Message.ProtocolRequired), + }, + }) + } + // verification, for now, only name is unique + if (id === 'new') { + if (networks.some(network => network.name === name)) { + return dispatch({ + type: AppActions.AddNotification, + payload: { + ...warning, + content: i18n.t(Message.NetworkNameUsed), + }, + }) + } + return createNetwork({ + name, + remote, + })(dispatch, history) + } + if (networks.some(network => network.name === name && network.id !== id)) { + return dispatch({ + type: AppActions.AddNotification, + payload: { + ...warning, + content: i18n.t(Message.NetworkNameUsed), + }, + }) + } + return updateNetwork({ + networkID: id!, + options: { + name, + remote, + }, + })(dispatch, history) + }, [id, name, remote, networks, history, dispatch]) export default { useInitialize, diff --git a/packages/neuron-ui/src/components/NetworkEditor/index.tsx b/packages/neuron-ui/src/components/NetworkEditor/index.tsx index 82903bc3ae..e41d6df20a 100644 --- a/packages/neuron-ui/src/components/NetworkEditor/index.tsx +++ b/packages/neuron-ui/src/components/NetworkEditor/index.tsx @@ -24,7 +24,7 @@ const NetworkEditor = ({ const cachedNetworks = useRef(networks) const cachedNetwork = cachedNetworks.current.find(network => network.id === id) const { invalidParams, notModified } = useIsInputsValid(editor, cachedNetwork) - const handleSubmit = useHandleSubmit(id, editor.name.value, editor.remote.value, networks, dispatch) + const handleSubmit = useHandleSubmit(id, editor.name.value, editor.remote.value, networks, history, dispatch) return ( diff --git a/packages/neuron-ui/src/components/NetworkSetting/index.tsx b/packages/neuron-ui/src/components/NetworkSetting/index.tsx index 6bc3a74e6c..32a07c7f40 100644 --- a/packages/neuron-ui/src/components/NetworkSetting/index.tsx +++ b/packages/neuron-ui/src/components/NetworkSetting/index.tsx @@ -4,32 +4,27 @@ import { useTranslation } from 'react-i18next' import { Stack, PrimaryButton, ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react' import { StateWithDispatch } from 'states/stateProvider/reducer' -import actionCreators from 'states/stateProvider/actionCreators' import chainState from 'states/initStates/chain' -import { appCalls } from 'services/UILayer' +import { setCurrentNetowrk, contextMenu } from 'services/remote' import { Routes } from 'utils/const' const onContextMenu = (id: string = '') => () => { - appCalls.contextMenu({ type: 'networkList', id }) + contextMenu({ type: 'networkList', id }) } const NetworkSetting = ({ chain = chainState, settings: { networks = [] }, - dispatch, history, }: React.PropsWithoutRef) => { const [t] = useTranslation() - const onChoiceChange = useCallback( - (_e, option?: IChoiceGroupOption) => { - if (option) { - dispatch(actionCreators.setNetwork(option.key)) - } - }, - [dispatch] - ) + const onChoiceChange = useCallback((_e, option?: IChoiceGroupOption) => { + if (option) { + setCurrentNetowrk(option.key) + } + }, []) const goToCreateNetwork = useCallback(() => { history.push(`${Routes.NetworkEditor}/new`) diff --git a/packages/neuron-ui/src/components/Overview/index.tsx b/packages/neuron-ui/src/components/Overview/index.tsx index 4136a16f85..a8f55fe6f6 100644 --- a/packages/neuron-ui/src/components/Overview/index.tsx +++ b/packages/neuron-ui/src/components/Overview/index.tsx @@ -22,7 +22,7 @@ import { } from 'office-ui-fabric-react' import { StateWithDispatch } from 'states/stateProvider/reducer' -import actionCreators from 'states/stateProvider/actionCreators' +import { updateTransactionList } from 'states/stateProvider/actionCreators' import { showErrorMessage } from 'services/remote' @@ -100,7 +100,12 @@ const Overview = ({ const minerInfoRef = useRef(null) useEffect(() => { - dispatch(actionCreators.getTransactions({ pageNo: 1, pageSize: PAGE_SIZE, keywords: '', walletID: id })) + updateTransactionList({ + pageNo: 1, + pageSize: PAGE_SIZE, + keywords: '', + walletID: id, + })(dispatch) }, [id, dispatch]) const onTransactionRowRender = useCallback((props?: IDetailsRowProps) => { diff --git a/packages/neuron-ui/src/components/PasswordRequest/index.tsx b/packages/neuron-ui/src/components/PasswordRequest/index.tsx index c2db0839ac..e57c2f8439 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/index.tsx +++ b/packages/neuron-ui/src/components/PasswordRequest/index.tsx @@ -1,9 +1,10 @@ import React, { useCallback, useMemo } from 'react' +import { RouteComponentProps } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Stack, Text, Label, Modal, TextField, PrimaryButton, DefaultButton } from 'office-ui-fabric-react' import { StateWithDispatch, AppActions } from 'states/stateProvider/reducer' -import actionCreators from 'states/stateProvider/actionCreators' -import { priceToFee } from 'utils/formatters' +import { sendTransaction, deleteWallet, backupWallet } from 'states/stateProvider/actionCreators' +import { priceToFee, CKBToShannonFormatter } from 'utils/formatters' const PasswordRequest = ({ app: { @@ -11,8 +12,9 @@ const PasswordRequest = ({ passwordRequest: { walletID = '', actionType = null, password = '' }, }, settings: { wallets = [] }, + history, dispatch, -}: React.PropsWithoutRef) => { +}: React.PropsWithoutRef) => { const [t] = useTranslation() const wallet = useMemo(() => wallets.find(w => w.id === walletID), [walletID, wallets]) const onDismiss = useCallback(() => { @@ -23,27 +25,40 @@ const PasswordRequest = ({ }, [dispatch]) const onConfirm = useCallback(() => { - const params = { id: walletID, password } switch (actionType) { - case 'delete': { - dispatch(actionCreators.deleteWallet(params)) + case 'send': { + sendTransaction({ + id: txID, + walletID, + items: outputs.map(output => ({ + address: output.address, + capacity: CKBToShannonFormatter(output.amount, output.unit), + })), + description, + password, + fee: priceToFee(price, cycles), + })(dispatch, history) break } - case 'backup': { - dispatch(actionCreators.backupWallet(params)) + case 'delete': { + deleteWallet({ + id: walletID, + password, + })(dispatch) break } - case 'send': { - dispatch( - actionCreators.submitTransaction(txID, walletID, outputs, description, password, priceToFee(price, cycles)) - ) + case 'backup': { + backupWallet({ + id: walletID, + password, + })(dispatch) break } default: { break } } - }, [dispatch, walletID, password, actionType, txID, description, outputs, cycles, price]) + }, [dispatch, walletID, password, actionType, txID, description, outputs, cycles, price, history]) const onChange = useCallback( (_e, value?: string) => { diff --git a/packages/neuron-ui/src/components/Receive/index.tsx b/packages/neuron-ui/src/components/Receive/index.tsx index 19a3801b6a..bfd0821141 100644 --- a/packages/neuron-ui/src/components/Receive/index.tsx +++ b/packages/neuron-ui/src/components/Receive/index.tsx @@ -32,7 +32,7 @@ const Receive = ({ return ( <> - + diff --git a/packages/neuron-ui/src/components/Send/index.tsx b/packages/neuron-ui/src/components/Send/index.tsx index d905190856..c35577f261 100644 --- a/packages/neuron-ui/src/components/Send/index.tsx +++ b/packages/neuron-ui/src/components/Send/index.tsx @@ -33,8 +33,11 @@ export interface TransactionOutput { } const Send = ({ - app: { send = appState.send }, - wallet: { id: walletID = '', sending = false, balance = '' }, + app: { + send = appState.send, + loadings: { sending = false }, + }, + wallet: { id: walletID = '', balance = '' }, dispatch, history, match: { @@ -61,7 +64,7 @@ const Send = ({ ) return ( - + { } const TransactionList = ({ - walletID, + isLoading = false, items = [], + walletID, dispatch, }: { + isLoading: boolean walletID: string items: State.Transaction[] dispatch: StateDispatch @@ -115,14 +117,16 @@ const TransactionList = ({ fieldName: 'description', minWidth: MIN_CELL_WIDTH, maxWidth: 200, - onRender: (item?: FormatTransaction, idx?: number) => { - return item && undefined !== idx ? ( + onRender: (item?: FormatTransaction) => { + return item ? ( local.key === item.hash) || { description: '' }).description || '' + } + onKeyPress={onDescriptionPress(item.hash)} + onBlur={onDescriptionFieldBlur(item.hash)} + onChange={onDescriptionChange(item.hash)} borderless styles={(props: ITextFieldStyleProps) => { return { @@ -191,7 +195,8 @@ const TransactionList = ({ }, [items]) return ( - group.count !== 0)} @@ -201,23 +206,9 @@ const TransactionList = ({ checkboxVisibility={CheckboxVisibility.hidden} onItemContextMenu={item => { if (item) { - appCalls.contextMenu({ type: 'transactionList', id: item.hash }) + contextMenu({ type: 'transactionList', id: item.hash }) } }} - styles={{ - contentWrapper: { - selectors: { - '.ms-DetailsRow-cell': { - display: 'flex', - alignItems: 'center', - }, - '.text-overflow': { - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - }, - }, - }} /> ) } diff --git a/packages/neuron-ui/src/components/WalletEditor/hooks.ts b/packages/neuron-ui/src/components/WalletEditor/hooks.ts index 0afb93d271..38190028cb 100644 --- a/packages/neuron-ui/src/components/WalletEditor/hooks.ts +++ b/packages/neuron-ui/src/components/WalletEditor/hooks.ts @@ -1,5 +1,5 @@ import { useState, useMemo, useCallback } from 'react' -import actionCreators from 'states/stateProvider/actionCreators' +import { updateWalletProperty } from 'states/stateProvider/actionCreators' import { StateDispatch } from 'states/stateProvider/reducer' import i18n from 'utils/i18n' @@ -35,15 +35,13 @@ export const useInputs = ({ name }: ReturnType) => { ) } -export const useOnConfirm = (name: string = '', id: string = '', dispatch: StateDispatch) => { +export const useOnConfirm = (name: string = '', id: string = '', history: any, dispatch: StateDispatch) => { return useCallback(() => { - dispatch( - actionCreators.updateWallet({ - id, - name, - }) - ) - }, [name, id, dispatch]) + updateWalletProperty({ + id, + name, + })(dispatch, history) + }, [name, id, history, dispatch]) } export const useAreParamsValid = (name: string) => { diff --git a/packages/neuron-ui/src/components/WalletEditor/index.tsx b/packages/neuron-ui/src/components/WalletEditor/index.tsx index d2d95951bb..fd40c3ef19 100644 --- a/packages/neuron-ui/src/components/WalletEditor/index.tsx +++ b/packages/neuron-ui/src/components/WalletEditor/index.tsx @@ -43,7 +43,7 @@ const WalletEditor = ({ const inputs = useInputs(editor) const areParamsValid = useAreParamsValid(editor.name.value) - const onConfirm = useOnConfirm(editor.name.value, wallet.id, dispatch) + const onConfirm = useOnConfirm(editor.name.value, wallet.id, history, dispatch) const goBack = useGoBack(history) if (!wallet.id) { diff --git a/packages/neuron-ui/src/components/WalletSetting/index.tsx b/packages/neuron-ui/src/components/WalletSetting/index.tsx index fe2c74a9e4..0a745e1b4a 100644 --- a/packages/neuron-ui/src/components/WalletSetting/index.tsx +++ b/packages/neuron-ui/src/components/WalletSetting/index.tsx @@ -4,11 +4,11 @@ import { useTranslation } from 'react-i18next' import { Stack, PrimaryButton, ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react' import { StateWithDispatch } from 'states/stateProvider/reducer' -import actionCreators from 'states/stateProvider/actionCreators' +import { setCurrentWallet } from 'states/stateProvider/actionCreators' import { WalletWizardPath } from 'components/WalletWizard' -import { appCalls } from 'services/UILayer' +import { contextMenu } from 'services/remote' import { Routes, MnemonicAction } from 'utils/const' const buttons = [ @@ -32,14 +32,14 @@ const WalletSetting = ({ const onChange = useCallback( (_e, option) => { if (option) { - dispatch(actionCreators.activateWallet(option.key)) + setCurrentWallet(option.key)(dispatch) } }, [dispatch] ) const onContextMenu = useCallback( (id: string = '') => () => { - appCalls.contextMenu({ type: 'walletList', id }) + contextMenu({ type: 'walletList', id }) }, [] ) diff --git a/packages/neuron-ui/src/components/WalletWizard/index.tsx b/packages/neuron-ui/src/components/WalletWizard/index.tsx index 1e9d80909a..74dc1b072b 100644 --- a/packages/neuron-ui/src/components/WalletWizard/index.tsx +++ b/packages/neuron-ui/src/components/WalletWizard/index.tsx @@ -4,12 +4,12 @@ import { Stack, Text, Label, Image, PrimaryButton, DefaultButton, TextField, Fon import { FormAdd, FormUpload } from 'grommet-icons' import withWizard, { WizardElementProps, WithWizardState } from 'components/withWizard' +import { generateMnemonic, validateMnemonic, showErrorMessage } from 'services/remote' +import { createWalletWithMnemonic, importWalletWithMnemonic } from 'states/stateProvider/actionCreators' -import { MnemonicAction, BUTTON_GAP } from 'utils/const' -import { verifyWalletSubmission } from 'utils/validators' -import { helpersCall, walletsCall } from 'services/UILayer' -import { validateMnemonic, showErrorMessage } from 'services/remote' +import { Routes, MnemonicAction, BUTTON_GAP } from 'utils/const' import { registerIcons, buttonGrommetIconStyles } from 'utils/icons' +import { verifyWalletSubmission } from 'utils/validators' export enum WalletWizardPath { Welcome = '/welcome', @@ -44,8 +44,13 @@ registerIcons({ }, }) -const Welcome = ({ rootPath = '/wizard', history }: WizardElementProps<{ rootPath: string }>) => { +const Welcome = ({ rootPath = '/wizard', wallets = [], history }: WizardElementProps<{ rootPath: string }>) => { const [t] = useTranslation() + useEffect(() => { + if (wallets.length) { + history.push(Routes.Overview) + } + }, [wallets, history]) const next = useCallback( (link: string) => () => { @@ -104,18 +109,11 @@ const Mnemonic = ({ useEffect(() => { if (type === MnemonicAction.Create) { - helpersCall - .generateMnemonic() - .then((res: string) => { - dispatch({ - type: 'generated', - payload: res, - }) - }) - .catch(err => { - console.error(err) - history.goBack() - }) + const mnemonic = generateMnemonic() + dispatch({ + type: 'generated', + payload: mnemonic, + }) } else { dispatch({ type: 'imported', @@ -246,11 +244,11 @@ const Submission = ({ mnemonic: imported, } if (type === MnemonicAction.Create) { - walletsCall.create(p) + createWalletWithMnemonic(p)(dispatch, history) } else { - walletsCall.importMnemonic(p) + importWalletWithMnemonic(p)(dispatch, history) } - }, [type, name, password, imported]) + }, [type, name, password, imported, history, dispatch]) const disableNext = !verifyWalletSubmission({ name, password, confirmPassword }) diff --git a/packages/neuron-ui/src/containers/Footer/index.tsx b/packages/neuron-ui/src/containers/Footer/index.tsx index f2b7e66dc6..7e7ee2cc17 100644 --- a/packages/neuron-ui/src/containers/Footer/index.tsx +++ b/packages/neuron-ui/src/containers/Footer/index.tsx @@ -67,9 +67,9 @@ const Footer = ({ location: { pathname }, }: React.PropsWithoutRef) => { const { - app: { tipBlockNumber }, - chain: { networkID, connectionStatus, tipBlockNumber: syncedBlockNumber }, - settings: { networks }, + app: { tipBlockNumber = '0' }, + chain: { networkID = '', connectionStatus = ConnectionStatus.Offline, tipBlockNumber: syncedBlockNumber = '0' }, + settings: { networks = [] }, } = useContext(NeuronWalletContext) const [t] = useTranslation() @@ -88,7 +88,9 @@ const Footer = ({ horizontalAlign="space-between" verticalFill verticalAlign="center" - padding="0 15px" + tokens={{ + padding: '0 15px', + }} styles={stackStyles} > diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts index 094bb5a8e3..1fb68650c1 100644 --- a/packages/neuron-ui/src/containers/Main/hooks.ts +++ b/packages/neuron-ui/src/containers/Main/hooks.ts @@ -1,312 +1,38 @@ import { useEffect } from 'react' -import { WalletWizardPath } from 'components/WalletWizard' import { NeuronWalletActions, StateDispatch, AppActions } from 'states/stateProvider/reducer' -import { actionCreators } from 'states/stateProvider/actionCreators' +import { + toggleAddressBook, + updateTransactionList, + updateTransaction, + updateCurrentWallet, + updateWalletList, + updateAddressList, + initAppState, +} from 'states/stateProvider/actionCreators' -import UILayer, { - AppMethod, - ChainMethod, - NetworksMethod, - TransactionsMethod, - WalletsMethod, - walletsCall, - transactionsCall, - networksCall, -} from 'services/UILayer' -import { initWindow } from 'services/remote' -import { SystemScript as SystemScriptSubject, DataUpdate as DataUpdateSubject } from 'services/subjects' +import { getWinID } from 'services/remote' +import { + SystemScript as SystemScriptSubject, + DataUpdate as DataUpdateSubject, + NetworkList as NetworkListSubject, + CurrentNetworkID as CurrentNetworkIDSubject, + ConnectionStatus as ConnectionStatusSubject, + SyncedBlockNumber as SyncedBlockNumberSubject, + Command as CommandSubject, +} from 'services/subjects' import { ckbCore, getTipBlockNumber, getBlockchainInfo } from 'services/chain' -import { Routes, Channel, ConnectionStatus } from 'utils/const' +import { Routes, ConnectionStatus } from 'utils/const' +import { WalletWizardPath } from 'components/WalletWizard' import { - wallets as walletsCache, networks as networksCache, - addresses as addressesCache, currentNetworkID as currentNetworkIDCache, - currentWallet as currentWalletCache, systemScript as systemScriptCache, } from 'utils/localCache' -import addressesToBalance from 'utils/addressesToBalance' -import initializeApp from 'utils/initializeApp' let timer: NodeJS.Timeout const SYNC_INTERVAL_TIME = 10000 -export const useChannelListeners = ({ - walletID, - chain, - dispatch, - history, - i18n, -}: { - walletID: string - chain: State.Chain - dispatch: StateDispatch - history: any - i18n: any -}) => - useEffect(() => { - UILayer.on(Channel.App, (_e: Event, method: AppMethod, args: ChannelResponse) => { - if (args && args.status) { - switch (method) { - case AppMethod.NavTo: { - history.push(args.result) - break - } - case AppMethod.ToggleAddressBook: { - dispatch(actionCreators.toggleAddressBook()) - break - } - default: { - break - } - } - } - }) - - UILayer.on(Channel.Chain, (_e: Event, method: ChainMethod, args: ChannelResponse) => { - if (args && args.status) { - switch (method) { - case ChainMethod.Status: { - dispatch({ - type: NeuronWalletActions.Chain, - payload: { - connectionStatus: args.result ? ConnectionStatus.Online : ConnectionStatus.Offline, - }, - }) - break - } - case ChainMethod.TipBlockNumber: { - dispatch({ - type: NeuronWalletActions.Chain, - payload: { - tipBlockNumber: args.result || '0', - }, - }) - break - } - default: { - break - } - } - } - }) - - UILayer.on(Channel.Transactions, (_e: Event, method: TransactionsMethod, args: ChannelResponse) => { - if (args.status) { - switch (method) { - case TransactionsMethod.GetAllByKeywords: { - // TODO: verify the wallet id the transactions belong to - dispatch({ - type: NeuronWalletActions.Chain, - payload: { transactions: { ...chain.transactions, ...args.result } }, - }) - break - } - case TransactionsMethod.Get: { - dispatch({ - type: NeuronWalletActions.Chain, - payload: { transaction: args.result }, - }) - break - } - case TransactionsMethod.TransactionUpdated: { - const updatedTransaction: State.Transaction = args.result - if ( - (!chain.transactions.items.length || - updatedTransaction.timestamp === null || - +(updatedTransaction.timestamp || updatedTransaction.createdAt) > - +(chain.transactions.items[0].timestamp || chain.transactions.items[0].createdAt)) && - chain.transactions.pageNo === 1 - ) { - /** - * 1. transaction list is empty or the coming transaction is pending or the coming transaction is later than latest transaction in current list - * 2. the current page number is 1 - */ - const newTransactionItems = [updatedTransaction, ...chain.transactions.items].slice( - 0, - chain.transactions.pageSize - ) - dispatch({ - type: NeuronWalletActions.Chain, - payload: { transactions: { ...chain.transactions, items: newTransactionItems } }, - }) - } else { - const newTransactionItems = [...chain.transactions.items] - const idx = newTransactionItems.findIndex(item => item.hash === updatedTransaction.hash) - if (idx >= 0) { - newTransactionItems[idx] = updatedTransaction - dispatch({ - type: NeuronWalletActions.Chain, - payload: { transactions: { ...chain.transactions, items: newTransactionItems } }, - }) - } - } - if (chain.transaction.hash === updatedTransaction.hash) { - dispatch({ - type: NeuronWalletActions.Chain, - payload: { transaction: updatedTransaction }, - }) - } - break - } - default: { - break - } - } - } - }) - - UILayer.on(Channel.Wallets, (_e: Event, method: WalletsMethod, args: ChannelResponse) => { - if (args.status) { - switch (method) { - case WalletsMethod.Create: - case WalletsMethod.ImportMnemonic: - case WalletsMethod.Update: { - let template = '' - if (method === WalletsMethod.Create) { - template = 'messages.wallet-created-successfully' - } else if (method === WalletsMethod.Update) { - template = 'messages.wallet-updated-successfully' - } else { - template = 'messages.wallet-imported-successfully' - } - const content = i18n.t(template, { name: args.result.name }) - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'success', - content, - timestamp: Date.now(), - }, - }) - history.push(Routes.Overview) - break - } - case WalletsMethod.GetAll: { - dispatch({ - type: NeuronWalletActions.Settings, - payload: { wallets: args.result }, - }) - walletsCache.save(args.result) - break - } - case WalletsMethod.GetCurrent: { - dispatch({ - type: NeuronWalletActions.Wallet, - payload: args.result, - }) - currentWalletCache.save(args.result) - break - } - case WalletsMethod.SendCapacity: { - if (args.result) { - history.push(Routes.History) - } - break - } - case WalletsMethod.SendingStatus: { - dispatch({ - type: NeuronWalletActions.Wallet, - payload: { - sending: args.result, - }, - }) - break - } - case WalletsMethod.GetAllAddresses: { - const addresses = args.result || [] - dispatch({ - type: NeuronWalletActions.Wallet, - payload: { - addresses, - balance: addressesToBalance(addresses), - }, - }) - addressesCache.save(addresses) - break - } - case WalletsMethod.RequestPassword: { - dispatch({ - type: AppActions.RequestPassword, - payload: { - walletID: args.result.walletID || '', - actionType: args.result.actionType || '', - }, - }) - break - } - default: { - break - } - } - } else { - if (!args.msg) { - return - } - if (method === WalletsMethod.GetCurrent) { - return - } - const { content } = typeof args.msg === 'string' ? { content: args.msg } : args.msg || { content: '' } - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'alert', - content, - timestamp: Date.now(), - }, - }) - } - }) - - UILayer.on(Channel.Networks, (_e: Event, method: NetworksMethod, args: ChannelResponse) => { - if (args.status) { - switch (method) { - case NetworksMethod.GetAll: { - dispatch({ - type: NeuronWalletActions.Settings, - payload: { networks: args.result || [] }, - }) - networksCache.save(args.result || []) - break - } - case NetworksMethod.CurrentID: { - dispatch({ - type: NeuronWalletActions.Chain, - payload: { networkID: args.result }, - }) - currentNetworkIDCache.save(args.result) - break - } - case NetworksMethod.Create: - case NetworksMethod.Update: { - history.push(Routes.SettingsNetworks) - break - } - case NetworksMethod.Activate: { - dispatch({ - type: NeuronWalletActions.Chain, - payload: { network: args.result }, - }) - break - } - default: { - break - } - } - } else { - dispatch({ - type: AppActions.AddNotification, - payload: { - type: 'alert', - content: args.msg, - timestamp: Date.now(), - }, - }) - } - }) - }, [walletID, i18n, chain, dispatch, history]) - export const useSyncChainData = ({ chainURL, dispatch }: { chainURL: string; dispatch: StateDispatch }) => { useEffect(() => { const syncTipNumber = () => @@ -362,8 +88,6 @@ export const useSyncChainData = ({ chainURL, dispatch }: { chainURL: string; dis export const useOnCurrentWalletChange = ({ walletID, - chain, - i18n, history, dispatch, }: { @@ -374,41 +98,25 @@ export const useOnCurrentWalletChange = ({ dispatch: StateDispatch }) => { - const { pageNo, pageSize } = chain.transactions useEffect(() => { if (walletID) { - walletsCall.getAllAddresses(walletID) - transactionsCall.getAllByKeywords({ - walletID, - keywords: '', - pageNo, - pageSize, - }) + initAppState()(dispatch, history) } else { - initWindow() - .then((initializedState: any) => { - initializeApp({ - initializedState, - i18n, - history, - dispatch, - }) - }) - .catch((err: Error) => { - console.error(err) - history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`) - }) + history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`) + initAppState()(dispatch, history) } - }, [walletID, pageNo, pageSize, dispatch, i18n, history]) + }, [walletID, dispatch, history]) } export const useSubscription = ({ walletID, chain, + history, dispatch, }: { walletID: string chain: State.Chain + history: any dispatch: StateDispatch }) => { const { pageNo, pageSize, keywords } = chain.transactions @@ -427,27 +135,22 @@ export const useSubscription = ({ } switch (dataType) { case 'address': { - walletsCall.getAllAddresses(walletID) + updateAddressList(walletID)(dispatch) break } case 'transaction': { - transactionsCall.getAllByKeywords({ + updateTransactionList({ walletID, keywords, pageNo, pageSize, - }) - transactionsCall.get(walletID, txHash) + })(dispatch) + updateTransaction({ walletID, hash: txHash }) break } case 'wallet': { - walletsCall.getAll() - walletsCall.getCurrent() - break - } - case 'network': { - networksCall.getAll() - networksCall.currentID() + updateWalletList()(dispatch) + updateCurrentWallet()(dispatch) break } default: { @@ -455,15 +158,85 @@ export const useSubscription = ({ } } }) + const networkListSubscription = NetworkListSubject.subscribe(({ currentNetworkList = [] }) => { + dispatch({ + type: NeuronWalletActions.UpdateNetworkList, + payload: currentNetworkList, + }) + networksCache.save(currentNetworkList) + }) + const currentNetworkIDSubscription = CurrentNetworkIDSubject.subscribe(({ currentNetworkID = '' }) => { + dispatch({ + type: NeuronWalletActions.UpdateCurrentNetworkID, + payload: currentNetworkID, + }) + currentNetworkIDCache.save(currentNetworkID) + }) + const connectionStatusSubscription = ConnectionStatusSubject.subscribe(status => { + dispatch({ + type: NeuronWalletActions.UpdateConnectionStatus, + payload: status ? ConnectionStatus.Online : ConnectionStatus.Offline, + }) + }) + + const syncedBlockNumberSubscription = SyncedBlockNumberSubject.subscribe(syncedBlockNumber => { + dispatch({ + type: NeuronWalletActions.UpdateSyncedBlockNumber, + payload: syncedBlockNumber, + }) + }) + const commandSubscription = CommandSubject.subscribe( + ({ winID, type, payload }: { winID: number; type: Command.Type; payload: Command.payload }) => { + if (getWinID() === winID) { + switch (type) { + case 'nav': { + history.push(payload) + break + } + case 'toggleAddressBook': { + dispatch(toggleAddressBook()) + break + } + case 'deleteWallet': { + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID: payload || '', + actionType: 'delete', + }, + }) + break + } + case 'backupWallet': { + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID: payload || '', + actionType: 'backup', + }, + }) + break + } + default: { + break + } + } + } + } + ) return () => { systemScriptSubscription.unsubscribe() dataUpdateSubscription.unsubscribe() + networkListSubscription.unsubscribe() + currentNetworkIDSubscription.unsubscribe() + connectionStatusSubscription.unsubscribe() + syncedBlockNumberSubscription.unsubscribe() + commandSubscription.unsubscribe() } - }, [walletID, pageNo, pageSize, keywords, txHash, dispatch]) + }, [walletID, pageNo, pageSize, keywords, txHash, history, dispatch]) } export default { - useChannelListeners, useSyncChainData, useOnCurrentWalletChange, useSubscription, diff --git a/packages/neuron-ui/src/containers/Main/index.tsx b/packages/neuron-ui/src/containers/Main/index.tsx index 55095f47b6..e5685aa3ea 100644 --- a/packages/neuron-ui/src/containers/Main/index.tsx +++ b/packages/neuron-ui/src/containers/Main/index.tsx @@ -20,7 +20,7 @@ import PasswordRequest from 'components/PasswordRequest' import { Routes } from 'utils/const' -import { useChannelListeners, useSubscription, useSyncChainData, useOnCurrentWalletChange } from './hooks' +import { useSubscription, useSyncChainData, useOnCurrentWalletChange } from './hooks' export const mainContents: CustomRouter.Route[] = [ { @@ -108,23 +108,17 @@ const MainContent = ({ }: React.PropsWithoutRef<{ dispatch: StateDispatch } & RouteComponentProps>) => { const neuronWalletState = useState() const { - wallet: { id: walletID }, + wallet: { id: walletID = '' }, chain, - settings: { networks }, + settings: { networks = [] }, } = neuronWalletState const { networkID } = chain const [, i18n] = useTranslation() - useChannelListeners({ - walletID: neuronWalletState.wallet.id, - chain: neuronWalletState.chain, - dispatch, - history, - i18n, - }) useSubscription({ walletID, chain, + history, dispatch, }) diff --git a/packages/neuron-ui/src/index.tsx b/packages/neuron-ui/src/index.tsx index f49c66b6ea..83942aec94 100755 --- a/packages/neuron-ui/src/index.tsx +++ b/packages/neuron-ui/src/index.tsx @@ -2,6 +2,7 @@ import React from 'react' import ReactDOM from 'react-dom' import { HashRouter as Router, Route } from 'react-router-dom' import { loadTheme } from 'office-ui-fabric-react' +import { Alert as AlertIcon } from 'grommet-icons' import 'styles/index.scss' import 'utils/i18n' @@ -13,6 +14,7 @@ import Main from 'containers/Main' import Footer from 'containers/Footer' import ErrorBoundary from 'components/ErrorBoundary' import withProviders from 'states/stateProvider' +import { registerIcons } from 'utils/icons' loadTheme({ fonts: { @@ -30,6 +32,12 @@ loadTheme({ }, }) +registerIcons({ + icons: { + errorbadge: , + }, +}) + export const containers: CustomRouter.Route[] = [ { name: 'Navbar', diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 1239aaf03f..a11554c401 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -29,8 +29,7 @@ "lock-arg": "Lock Arg", "address": "Address", "code-hash": "Code Hash", - "copy-pubkey-hash": "Copy Lock Arg (Pubkey Hash)", - "can-not-find-the-default-address": "Cannot find the default address" + "copy-pubkey-hash": "Copy Lock Arg (Pubkey Hash)" }, "wizard": { "welcome-to-nervos-neuron": "Welcome to Neuron", @@ -216,7 +215,8 @@ "no-transactions": "No transactions", "error": "Error", "invalid-mnemonic": "Invalid mnemonic words", - "camera-not-available-or-disabled": "Camera is unavailable or disabled" + "camera-not-available-or-disabled": "Camera is unavailable or disabled", + "can-not-find-the-default-address": "Cannot find the default address" }, "sync": { "syncing": "Syncing", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index f0986b9900..fb647aa758 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -29,8 +29,7 @@ "lock-arg": "Lock Arg", "address": "地址", "code-hash": "Code Hash", - "copy-pubkey-hash": "复制 Lock Arg (Pubkey Hash)", - "can-not-find-the-default-address": "未获得默认地址" + "copy-pubkey-hash": "复制 Lock Arg (Pubkey Hash)" }, "wizard": { "welcome-to-nervos-ckb": "欢迎使用 Neuron", @@ -216,8 +215,8 @@ "no-transactions": "没有交易", "error": "错误", "invalid-mnemonic": "助记词不合法", - "can-not-find-the-default-address": "找不到默认地址", - "camera-not-available-or-disabled": "摄像头不可用或被禁用" + "camera-not-available-or-disabled": "摄像头不可用或被禁用", + "can-not-find-the-default-address": "未获得默认地址" }, "sync": { "syncing": "同步中", diff --git a/packages/neuron-ui/src/services/UILayer.ts b/packages/neuron-ui/src/services/UILayer.ts deleted file mode 100644 index 14d5a9e9aa..0000000000 --- a/packages/neuron-ui/src/services/UILayer.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { Channel } from 'utils/const' -import SyntheticEventEmitter from 'utils/SyntheticEventEmitter' -import instantiateMethodCall from 'utils/instantiateMethodCall' - -export enum AppMethod { - ToggleAddressBook = 'toggleAddressBook', - ContextMenu = 'contextMenu', - NavTo = 'navTo', - SetUILocale = 'setUILocale', - HandleViewError = 'handleViewError', -} - -export enum ChainMethod { - Status = 'status', - TipBlockNumber = 'tipBlockNumber', -} - -export enum WalletsMethod { - GetAll = 'getAll', - Get = 'get', - GenerateMnemonic = 'generateMnemonic', - ImportMnemonic = 'importMnemonic', - ImportKeystore = 'importKeystore', - Create = 'create', - Update = 'update', - Delete = 'delete', - Export = 'export', - GetCurrent = 'getCurrent', - Activate = 'activate', - Backup = 'backup', - SendCapacity = 'sendCapacity', - SendingStatus = 'sendingStatus', - UpdateAddressDescription = 'updateAddressDescription', - RequestPassword = 'requestPassword', - GetAllAddresses = 'getAllAddresses', -} - -export enum NetworksMethod { - GetAll = 'getAll', - Get = 'get', - Create = 'create', - Update = 'update', - Delete = 'delete', - Activate = 'activate', - CurrentID = 'currentID', -} - -export enum TransactionsMethod { - GetAll = 'getAll', - GetAllByKeywords = 'getAllByKeywords', - Get = 'get', - UpdateDescription = 'updateDescription', - TransactionUpdated = 'transactionUpdated', -} - -export enum HelpersMethod { - GenerateMnemonic = 'generateMnemonic', -} - -export interface GetTransactionsParams { - pageNo: number - pageSize: number - keywords?: string - walletID: string -} - -const UILayer = (() => { - if (window.bridge) { - return new SyntheticEventEmitter(window.bridge.ipcRenderer) - } - return { - send: (channel: string, ...msg: any[]) => { - console.warn(`Message: ${msg} to channel ${channel} failed due to Electron not loaded`) - }, - sendSync: (channel: string, ...msg: any[]) => { - console.warn(`Message: ${msg} to channel ${channel} failed due to Electron not loaded`) - }, - on: (channel: string, cb: Function) => { - console.warn(`Channel ${channel} and Function ${cb.toString()} failed due to Electron not loaded`) - }, - once: (channel: string, cb: Function) => { - console.warn(`Channel ${channel} and Function ${cb.toString()} failed due to Electron not loaded`) - }, - removeAllListeners: (channel?: string) => { - console.warn(`Channel ${channel} cannot be removed due to Electron not loaded`) - }, - addEventListener: (event: string, cb: EventListenerOrEventListenerObject) => window.addEventListener(event, cb), - } -})() - -export const app = (method: AppMethod, ...params: any) => { - UILayer.send(Channel.App, method, ...params) -} - -export const appCalls = instantiateMethodCall(app) as { - contextMenu: ({ type, id }: { type: string; id: string }) => void - handleViewError: (errorMessage: string) => void -} - -export const networks = (method: NetworksMethod, ...params: any[]) => { - UILayer.send(Channel.Networks, method, ...params) -} -export const networksCall = instantiateMethodCall(networks) as { - getAll: () => void - get: (id: string) => void - create: (network: State.NetworkProperty) => void - update: (id: string, options: Partial) => void - delete: (id: string) => void - currentID: () => void - activate: (id: string) => void -} - -export const transactions = (method: TransactionsMethod, params: string | GetTransactionsParams) => { - UILayer.send(Channel.Transactions, method, params) -} - -export const transactionsCall = instantiateMethodCall(transactions) as { - getAllByKeywords: (params: GetTransactionsParams) => void - get: (walletID: string, hash: string) => void - updateDescription: (params: { hash: string; description: string }) => void -} - -export const wallets = ( - method: WalletsMethod, - params: - | undefined - | string - | { name: string; password: string } - | { keystore: string; password: string } - | { mnemonic: string; password: string } - | { id: string; password: string } - | { id: string; name?: string; password: string; newPassword?: string } - | { - id: string - walletID: string - items: { address: string; capacity: string }[] - password: string - fee: string - description: string - } -) => { - UILayer.send(Channel.Wallets, method, params) -} - -export const walletsCall = instantiateMethodCall(wallets) as { - getAll: () => void - get: (id: string) => void - generateMnemonic: () => void - importKeystore: (params: { name: string; keystore: string; password: string }) => void - importMnemonic: (params: { name: string; mnemonic: string; password: string }) => void - create: (params: { name: string; mnemonic: string; password: string }) => void - update: (params: { id: string; password?: string; newPassword?: string; name?: string }) => void - delete: (params: { id: string; password: string }) => void - getCurrent: () => void - activate: (id: string) => void - backup: (params: { id: string; password: string }) => void - sendCapacity: (params: { - id: string - walletID: string - items: { - address: string - capacity: string - }[] - password: string - fee: string - description: string - }) => void - getAllAddresses: (id: string) => void - updateAddressDescription: (params: { walletID: string; address: string; description: string }) => void -} - -export const helpers = (method: HelpersMethod, ...params: any) => { - return new Promise((res, rej) => { - UILayer.send(Channel.Helpers, method, ...params) - UILayer.once(Channel.Helpers, (_e: Event, _method: HelpersMethod, args: ChannelResponse) => { - if (args.status) { - res(args.result) - } else { - rej(args.msg) - } - }) - }) -} - -export const helpersCall = instantiateMethodCall(helpers) as { - generateMnemonic: () => Promise -} - -export default UILayer diff --git a/packages/neuron-ui/src/services/remote/app.ts b/packages/neuron-ui/src/services/remote/app.ts new file mode 100644 index 0000000000..c3443322d9 --- /dev/null +++ b/packages/neuron-ui/src/services/remote/app.ts @@ -0,0 +1,19 @@ +import { controllerMethodWrapper } from './controllerMethodWrapper' + +const CONTROLLER_NAME = 'app' +export const getNeuronWalletState = controllerMethodWrapper(CONTROLLER_NAME)(controller => () => + controller.getInitState() +) + +export const handleViewError = controllerMethodWrapper(CONTROLLER_NAME)(controller => (errorMessage: string) => + controller.handleViewError(errorMessage) +) +export const contextMenu = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: { type: string; id: string }) => controller.contextMenu(params) +) + +export default { + getNeuronWalletState, + handleViewError, + contextMenu, +} diff --git a/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts new file mode 100644 index 0000000000..be8efaa72a --- /dev/null +++ b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts @@ -0,0 +1,69 @@ +// TODO: use error code +interface SuccessFromController { + status: 1 + result: any +} +interface FailureFromController { + status: 0 + message: { + title: string + content?: string + } +} +export type ControllerResponse = SuccessFromController | FailureFromController + +export const RemoteNotLoadError = { + status: 0 as 0, + message: { + title: 'remote is not supported', + }, +} + +export const controllerNotLoaded = (controllerName: string) => ({ + status: 0 as 0, + message: { + title: `${controllerName} controller not loaded`, + }, +}) + +export const controllerMethodWrapper = (controllerName: string) => ( + callControllerMethod: (controller: any) => (params: any) => Promise<{ status: any; result: any; msg: string }> +) => async (realParams?: any): Promise => { + if (!window.remote) { + return RemoteNotLoadError + } + const controller = window.remote.require(`./controllers/${controllerName}`).default + if (!controller) { + return controllerNotLoaded(controllerName) + } + const res = await callControllerMethod(controller)(realParams) + if (process.env.NODE_ENV === 'development' && window.localStorage.getItem('log-response')) { + /* eslint-disable no-console */ + console.group(`${controllerName} controller`) + console.info(JSON.stringify(res, null, 2)) + console.groupEnd() + /* eslint-enable no-console */ + } + if (!res) { + return { + status: 1, + result: null, + } + } + if (res.status) { + return { + status: 1, + result: res.result || null, + } + } + return { + status: 0, + message: { title: (res && res.msg) || '' }, + } +} + +export default { + RemoteNotLoadError, + controllerNotLoaded, + controllerMethodWrapper, +} diff --git a/packages/neuron-ui/src/services/remote.ts b/packages/neuron-ui/src/services/remote/index.ts similarity index 57% rename from packages/neuron-ui/src/services/remote.ts rename to packages/neuron-ui/src/services/remote/index.ts index 38398c3c28..c6b832ccec 100644 --- a/packages/neuron-ui/src/services/remote.ts +++ b/packages/neuron-ui/src/services/remote/index.ts @@ -1,10 +1,22 @@ -export const initWindow = () => { +export * from './app' +export * from './wallets' +export * from './networks' +export * from './transactions' + +export const getLocale = () => { + if (!window.remote) { + console.warn('remote is not supported') + return window.navigator.language + } + return window.remote.require('electron').app.getLocale() +} + +export const getWinID = () => { if (!window.remote) { console.warn('remote is not supported') - return Promise.reject(new Error('remote is not supported')) + return -1 } - const appController = window.remote.require('./controllers/app').default - return appController.getInitState() + return window.remote.getCurrentWindow().id } export const validateMnemonic = (mnemonic: string): boolean => { @@ -16,6 +28,15 @@ export const validateMnemonic = (mnemonic: string): boolean => { return remoteValidateMnemonic(mnemonic) } +export const generateMnemonic = (): string => { + if (!window.remote) { + console.warn('remote is not supported') + return '' + } + const { generateMnemonic: remoteGenerateMnemonic } = window.remote.require('./models/keys/key') + return remoteGenerateMnemonic() +} + export const showMessage = (options: any, callback: Function) => { if (!window.remote) { console.warn('remote is not supported') @@ -35,8 +56,10 @@ export const showErrorMessage = (title: string, content: string) => { } export default { - initWindow, + getLocale, validateMnemonic, + generateMnemonic, showMessage, showErrorMessage, + getWinID, } diff --git a/packages/neuron-ui/src/services/remote/networks.ts b/packages/neuron-ui/src/services/remote/networks.ts new file mode 100644 index 0000000000..5bcda628b9 --- /dev/null +++ b/packages/neuron-ui/src/services/remote/networks.ts @@ -0,0 +1,25 @@ +import { controllerMethodWrapper } from './controllerMethodWrapper' + +const CONTROLLER_NAME = 'networks' + +export const setCurrentNetowrk = controllerMethodWrapper(CONTROLLER_NAME)((controller: any) => (networkID: string) => { + return controller.activate(networkID) +}) + +export const createNetwork = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.CreateNetworkParams) => { + return controller.create(params) + } +) + +export const updateNetwork = controllerMethodWrapper(CONTROLLER_NAME)( + controller => ({ networkID, options }: Controller.UpdateNetworkParams) => { + return controller.update(networkID, options) + } +) + +export default { + createNetwork, + updateNetwork, + setCurrentNetowrk, +} diff --git a/packages/neuron-ui/src/services/remote/transactions.ts b/packages/neuron-ui/src/services/remote/transactions.ts new file mode 100644 index 0000000000..474cb3755c --- /dev/null +++ b/packages/neuron-ui/src/services/remote/transactions.ts @@ -0,0 +1,32 @@ +import { controllerMethodWrapper } from './controllerMethodWrapper' + +export interface GetTransactionListParams { + pageNo: number + pageSize: number + keywords?: string + walletID: string +} + +const CONTROLLER_NAME = 'transactions' + +export const getTransactionList = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: GetTransactionListParams) => { + return controller.getAllByKeywords(params) + } +) + +export const getTransaction = controllerMethodWrapper(CONTROLLER_NAME)( + controller => ({ walletID, hash }: { walletID: string; hash: string }) => { + return controller.get(walletID, hash) + } +) +export const updateTransactionDescription = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.UpdateTransactionDescriptionParams) => { + return controller.updateDescription(params) + } +) + +export default { + getTransactionList, + getTransaction, +} diff --git a/packages/neuron-ui/src/services/remote/wallets.ts b/packages/neuron-ui/src/services/remote/wallets.ts new file mode 100644 index 0000000000..c5c83aebb3 --- /dev/null +++ b/packages/neuron-ui/src/services/remote/wallets.ts @@ -0,0 +1,47 @@ +import { controllerMethodWrapper } from './controllerMethodWrapper' + +const CONTROLLER_NAME = 'wallets' + +export const updateWallet = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.UpdateWalletParams) => controller.update(params) +) +export const getCurrentWallet = controllerMethodWrapper(CONTROLLER_NAME)(controller => () => controller.getCurrent()) +export const getWalletList = controllerMethodWrapper(CONTROLLER_NAME)(controller => () => controller.getAll()) +export const importMnemonic = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.ImportMnemonicParams) => controller.importMnemonic(params) +) + +export const deleteWallet = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.DeleteWalletParams) => controller.delete(params) +) + +export const backupWallet = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.DeleteWalletParams) => controller.backup(params) +) + +export const setCurrentWallet = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (id: Controller.SetCurrentWalletParams) => controller.activate(id) +) +export const sendCapacity = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.SendTransaction) => controller.sendCapacity(params) +) + +export const getAddressesByWalletID = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (walletID: Controller.GetAddressesByWalletIDParams) => controller.getAllAddresses(walletID) +) + +export const updateAddressDescription = controllerMethodWrapper(CONTROLLER_NAME)( + controller => (params: Controller.UpdateAddressDescriptionParams) => controller.updateAddressDescription(params) +) + +export default { + updateWallet, + getWalletList, + importMnemonic, + deleteWallet, + backupWallet, + getCurrentWallet, + sendCapacity, + getAddressesByWalletID, + updateAddressDescription, +} diff --git a/packages/neuron-ui/src/services/subjects.ts b/packages/neuron-ui/src/services/subjects.ts index 6523260f5a..d3f4c09178 100644 --- a/packages/neuron-ui/src/services/subjects.ts +++ b/packages/neuron-ui/src/services/subjects.ts @@ -1,3 +1,5 @@ +const SUBJECT_PATH = `./models/subjects` + const FallbackSubject = { subscribe: (args: any) => { console.warn('remote is not supported') @@ -13,18 +15,51 @@ const FallbackSubject = { }, } export const SystemScript = window.remote - ? (window.remote.require('./models/subjects/system-script').default as NeuronWalletSubject<{ codeHash: string }>) + ? (window.remote.require(`${SUBJECT_PATH}/system-script`).default as NeuronWalletSubject<{ codeHash: string }>) : FallbackSubject export const DataUpdate = window.remote - ? (window.remote.require('./models/subjects/data-update').default as NeuronWalletSubject<{ + ? (window.remote.require(`${SUBJECT_PATH}/data-update`).default as NeuronWalletSubject<{ dataType: 'address' | 'transaction' | 'wallet' | 'network' actionType: 'create' | 'update' | 'delete' walletID?: string }>) : FallbackSubject +export const NetworkList = window.remote + ? (window.remote.require(`${SUBJECT_PATH}/networks`).NetworkListSubject as NeuronWalletSubject<{ + currentNetworkList: State.Network[] + }>) + : FallbackSubject + +export const CurrentNetworkID = window.remote + ? (window.remote.require(`${SUBJECT_PATH}/networks`).CurrentNetworkIDSubject as NeuronWalletSubject<{ + currentNetworkID: string + }>) + : FallbackSubject + +export const ConnectionStatus = window.remote + ? (window.remote.require(`${SUBJECT_PATH}/node`).ConnectionStatusSubject as NeuronWalletSubject) + : FallbackSubject + +export const SyncedBlockNumber = window.remote + ? (window.remote.require(`${SUBJECT_PATH}/node`).SyncedBlockNumberSubject as NeuronWalletSubject) + : FallbackSubject + +export const Command = window.remote + ? (window.remote.require(`${SUBJECT_PATH}/command`).default as NeuronWalletSubject<{ + winID: number + type: Command.Type + payload: Command.payload + }>) + : FallbackSubject + export default { SystemScript, DataUpdate, + NetworkList, + CurrentNetworkID, + ConnectionStatus, + SyncedBlockNumber, + Command, } diff --git a/packages/neuron-ui/src/states/initStates/app.ts b/packages/neuron-ui/src/states/initStates/app.ts index ef1d188b69..7124e596f0 100644 --- a/packages/neuron-ui/src/states/initStates/app.ts +++ b/packages/neuron-ui/src/states/initStates/app.ts @@ -32,6 +32,11 @@ const appState: State.App = { wizard: null, }, notifications: [], + loadings: { + sending: false, + addressList: false, + transactionList: false, + }, } export default appState diff --git a/packages/neuron-ui/src/states/initStates/wallet.ts b/packages/neuron-ui/src/states/initStates/wallet.ts index 7ce6e47bbb..7108e8ac2e 100644 --- a/packages/neuron-ui/src/states/initStates/wallet.ts +++ b/packages/neuron-ui/src/states/initStates/wallet.ts @@ -7,7 +7,6 @@ export const walletState: State.Wallet = { id: wallet.id || '', balance: '0', addresses: addresses.load(), - sending: false, } export default walletState diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts new file mode 100644 index 0000000000..46a7fa8405 --- /dev/null +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -0,0 +1,75 @@ +import { NeuronWalletActions, AppActions, StateDispatch } from 'states/stateProvider/reducer' +import { getNeuronWalletState } from 'services/remote' +import initStates from 'states/initStates' +import { Routes } from 'utils/const' +import { WalletWizardPath } from 'components/WalletWizard' +import addressesToBalance from 'utils/addressesToBalance' +import { + wallets as walletsCache, + addresses as addressesCache, + currentWallet as currentWalletCache, +} from 'utils/localCache' + +export const initAppState = () => (dispatch: StateDispatch, history: any) => { + dispatch({ + type: AppActions.UpdateLoadings, + payload: { + addressList: true, + transactionList: true, + }, + }) + setTimeout(() => {}) + getNeuronWalletState() + .then(res => { + if (res.status) { + const { + wallets = [], + currentWallet: wallet = initStates.wallet, + addresses = [], + transactions = initStates.chain.transactions, + } = res.result + dispatch({ + type: NeuronWalletActions.InitiateCurrentWalletAndWalletList, + payload: { + wallet: { ...wallet, balance: addressesToBalance(addresses), addresses }, + wallets, + }, + }) + dispatch({ + type: NeuronWalletActions.UpdateTransactionList, + payload: transactions, + }) + + currentWalletCache.save(wallet) + walletsCache.save(wallets) + addressesCache.save(addresses) + } else { + history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`) + } + }) + .finally(() => { + dispatch({ + type: AppActions.UpdateLoadings, + payload: { + addressList: false, + transactionList: false, + }, + }) + }) +} + +export const addNotification = ({ type, content }: { type: 'alert'; content: string }) => (dispatch: StateDispatch) => { + dispatch({ + type: AppActions.AddNotification, + payload: { + type, + content, + timestamp: Date.now(), + }, + }) +} + +export default { + initAppState, + addNotification, +} diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/index.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/index.ts index 02b7819f24..b1ea341f64 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/index.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/index.ts @@ -1,13 +1,15 @@ +import app from './app' import wallets from './wallets' -import networks from './networks' -import send from './send' import transactions from './transactions' import settings from './settings' +export * from './app' +export * from './wallets' +export * from './transactions' +export * from './settings' export const actionCreators = { + ...app, ...wallets, - ...networks, - ...send, ...transactions, ...settings, } diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/networks.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/networks.ts deleted file mode 100644 index 89da2f3feb..0000000000 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/networks.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { networksCall } from 'services/UILayer' - -import { Message, MAX_NETWORK_NAME_LENGTH, UNREMOVABLE_NETWORK_ID, UNREMOVABLE_NETWORK } from 'utils/const' -import i18n from 'utils/i18n' -import { AppActions } from '../reducer' - -export default { - createOrUpdateNetwork: ({ id, name, remote }: State.Network, networks: State.Network[]) => { - const warning = { - type: 'warning', - timestamp: Date.now(), - content: '', - } - if (!name) { - return { - type: AppActions.AddNotification, - payload: { - ...warning, - content: i18n.t(Message.NameRequired), - }, - } - } - if (name.length > MAX_NETWORK_NAME_LENGTH) { - return { - type: AppActions.AddNotification, - payload: { - ...warning, - content: i18n.t(Message.LengthOfNameShouldBeLessThanOrEqualTo, { - length: MAX_NETWORK_NAME_LENGTH, - }), - }, - } - } - if (!remote) { - return { - type: AppActions.AddNotification, - payload: { - ...warning, - content: i18n.t(Message.URLRequired), - }, - } - } - if (!remote.startsWith('http')) { - return { - type: AppActions.AddNotification, - payload: { - ...warning, - content: i18n.t(Message.ProtocolRequired), - }, - } - } - // verification, for now, only name is unique - if (id === 'new') { - if (networks.some(network => network.name === name)) { - return { - type: AppActions.AddNotification, - payload: { - ...warning, - content: i18n.t(Message.NetworkNameUsed), - }, - } - } - networksCall.create({ - name, - remote, - }) - } else { - if (networks.some(network => network.name === name && network.id !== id)) { - return { - type: AppActions.AddNotification, - payload: { - ...warning, - content: i18n.t(Message.NetworkNameUsed), - }, - } - } - networksCall.update(id!, { - name, - remote, - }) - } - return { - type: AppActions.Ignore, - payload: null, - } - }, - deleteNetwork: (id?: string) => { - if (id === undefined) { - throw new Error('No network id found') - } - if (id === UNREMOVABLE_NETWORK_ID) { - return { - type: AppActions.AddNotification, - payload: { - type: 'warning', - timestamp: Date.now(), - conetent: i18n.t(`messages.is-unremovable`, { target: UNREMOVABLE_NETWORK }), - }, - } - } - networksCall.delete(id) - return { - type: AppActions.Ignore, - payload: null, - } - }, - setNetwork: (id: string) => { - networksCall.activate(id) - return { - type: AppActions.Ignore, - payload: null, - } - }, -} diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/send.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/send.ts deleted file mode 100644 index 848e10f36b..0000000000 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/send.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { walletsCall } from 'services/UILayer' - -import { CKBToShannonFormatter } from 'utils/formatters' -import { TransactionOutput } from 'components/Send' - -import { AppActions } from '../reducer' - -export default { - submitTransaction: ( - id: string = '', - walletID: string = '', - items: TransactionOutput[] = [], - description: string = '', - password: string = '', - fee: string = '0' - ) => { - walletsCall.sendCapacity({ - id, - walletID, - items: items.map(item => ({ - address: item.address, - capacity: CKBToShannonFormatter(item.amount, item.unit), - })), - password, - fee, - description, - }) - return { - type: AppActions.DismissPasswordRequest, - payload: null, - } - }, -} diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts index 88309b407b..95a06310c6 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts @@ -1,14 +1,48 @@ +import { createNetwork as createRemoteNetwork, updateNetwork as updateRemoteNetwork } from 'services/remote' import { addressBook } from 'utils/localCache' -import { NeuronWalletActions } from '../reducer' +import { Routes } from 'utils/const' +import { addNotification } from './app' -export default { - toggleAddressBook: () => { - addressBook.toggleVisibility() - return { - type: NeuronWalletActions.Settings, - payload: { - toggleAddressBook: true, - }, +import { AppActions, StateDispatch } from '../reducer' + +export const toggleAddressBook = () => { + addressBook.toggleVisibility() + return { + type: AppActions.ToggleAddressBookVisibility, + payload: null, + } +} + +export const createNetwork = (params: Controller.CreateNetworkParams) => (dispatch: StateDispatch, history: any) => { + createRemoteNetwork(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + history.push(Routes.SettingsNetworks) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const updateNetwork = (params: Controller.UpdateNetworkParams) => (dispatch: StateDispatch, history: any) => { + updateRemoteNetwork(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + history.push(Routes.SettingsNetworks) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) } - }, + }) +} + +export default { + toggleAddressBook, + createNetwork, + updateNetwork, } diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts index 416ce61334..04d4254e9e 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts @@ -1,57 +1,65 @@ -import { transactionsCall, GetTransactionsParams, walletsCall } from 'services/UILayer' -import { AppActions } from '../reducer' +import { NeuronWalletActions, AppActions, StateDispatch } from 'states/stateProvider/reducer' +import { + GetTransactionListParams, + getTransaction, + getTransactionList, + updateTransactionDescription as updateRemoteTransactionDescription, +} from 'services/remote' +import { addNotification } from './app' -export default { - getTransaction: (walletID: string, hash: string) => { - transactionsCall.get(walletID, hash) - return { - type: AppActions.Ignore, - payload: null, - } - }, - - getTransactions: (params: GetTransactionsParams) => { - transactionsCall.getAllByKeywords(params) - return { - type: AppActions.Ignore, - payload: null, - } - }, - updateDescription: ({ - type, - walletID, - key, - description, - }: { - type: 'address' | 'transaction' - walletID: string - key: string - description: string - }) => { - if (type === 'address') { - walletsCall.updateAddressDescription({ - walletID, - address: key, - description, +export const updateTransaction = (params: { walletID: string; hash: string }) => (dispatch: StateDispatch) => { + getTransaction(params).then(res => { + if (res.status) { + dispatch({ + type: NeuronWalletActions.UpdateTransaction, + payload: res.result, }) - return { - type: AppActions.Ignore, - payload: null, - } + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) } - if (type === 'transaction') { - transactionsCall.updateDescription({ - hash: key, - description, - }) - return { - type: AppActions.Ignore, - payload: null, + }) +} +export const updateTransactionList = (params: GetTransactionListParams) => (dispatch: StateDispatch) => { + dispatch({ + type: AppActions.UpdateLoadings, + payload: { + transactionList: true, + }, + }) + getTransactionList(params) + .then(res => { + if (res.status) { + dispatch({ + type: NeuronWalletActions.UpdateTransactionList, + payload: res.result, + }) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) } + }) + .finally(() => { + dispatch({ + type: AppActions.UpdateLoadings, + payload: { + transactionList: false, + }, + }) + }) +} + +export const updateTransactionDescription = (params: Controller.UpdateTransactionDescriptionParams) => ( + dispatch: StateDispatch +) => { + updateRemoteTransactionDescription(params).then(res => { + if (res.status) { + dispatch({ type: AppActions.Ignore, payload: null }) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) } - return { - type: AppActions.Ignore, - payload: null, - } - }, + }) +} + +export default { + updateTransaction, + updateTransactionList, } diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts index 938147ffaf..db77f61c39 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts @@ -1,44 +1,216 @@ -import { AppActions } from 'states/stateProvider/reducer' -import { walletsCall } from 'services/UILayer' +import { AppActions, StateDispatch } from 'states/stateProvider/reducer' +import { + getWalletList, + importMnemonic, + getCurrentWallet, + updateWallet, + setCurrentWallet as setRemoteCurrentWallet, + sendCapacity, + getAddressesByWalletID, + updateAddressDescription as updateRemoteAddressDescription, + deleteWallet as deleteRemoteWallet, + backupWallet as backupRemoteWallet, +} from 'services/remote' +import { wallets as walletsCache, currentWallet as currentWalletCache } from 'utils/localCache' +import initStates from 'states/initStates' +import { Routes } from 'utils/const' import { NeuronWalletActions } from '../reducer' +import { addNotification } from './app' +export const updateCurrentWallet = () => (dispatch: StateDispatch) => { + getCurrentWallet().then(res => { + if (res.status) { + const payload = res.result || initStates.wallet + dispatch({ + type: NeuronWalletActions.UpdateCurrentWallet, + payload, + }) + currentWalletCache.save(payload) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const createWalletWithMnemonic = (params: Controller.ImportMnemonicParams) => ( + dispatch: StateDispatch, + history: any +) => { + importMnemonic(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + history.push(Routes.Overview) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const importWalletWithMnemonic = (params: Controller.ImportMnemonicParams) => ( + dispatch: StateDispatch, + history: any +) => { + importMnemonic(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + history.push(Routes.Overview) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} +export const updateWalletList = () => (dispatch: StateDispatch) => { + getWalletList().then(res => { + if (res.status) { + const payload = res.result || [] + dispatch({ + type: NeuronWalletActions.UpdateWalletList, + payload, + }) + walletsCache.save(payload) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const updateWalletProperty = (params: Controller.UpdateWalletParams) => ( + dispatch: StateDispatch, + history?: any +) => { + updateWallet(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + if (history) { + history.push(Routes.SettingsWallets) + } + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} +export const setCurrentWallet = (id: string) => (dispatch: StateDispatch) => { + setRemoteCurrentWallet(id).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const sendTransaction = (params: Controller.SendTransaction) => (dispatch: StateDispatch, history: any) => { + dispatch({ + type: AppActions.UpdateLoadings, + payload: { + sending: true, + }, + }) + sendCapacity(params) + .then(res => { + if (res.status) { + history.push(Routes.History) + } else { + addNotification({ type: 'alert', content: JSON.stringify(res.message.title) })(dispatch) + } + dispatch({ + type: AppActions.DismissPasswordRequest, + payload: null, + }) + }) + .finally(() => { + dispatch({ + type: AppActions.UpdateLoadings, + payload: { + sending: false, + }, + }) + }) +} + +export const updateAddressList = (params: Controller.GetAddressesByWalletIDParams) => (dispatch: StateDispatch) => { + getAddressesByWalletID(params).then(res => { + if (res.status) { + dispatch({ + type: NeuronWalletActions.UpdateAddressList, + payload: res.result, + }) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const updateAddressDescription = (params: Controller.UpdateAddressDescriptionParams) => ( + dispatch: StateDispatch +) => { + updateRemoteAddressDescription(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const deleteWallet = (params: Controller.DeleteWalletParams) => (dispatch: StateDispatch) => { + dispatch({ + type: AppActions.DismissPasswordRequest, + payload: null, + }) + deleteRemoteWallet(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} + +export const backupWallet = (params: Controller.BackupWalletParams) => (dispatch: StateDispatch) => { + dispatch({ + type: AppActions.DismissPasswordRequest, + payload: null, + }) + backupRemoteWallet(params).then(res => { + if (res.status) { + dispatch({ + type: AppActions.Ignore, + payload: null, + }) + } else { + addNotification({ type: 'alert', content: res.message.title })(dispatch) + } + }) +} export default { - getAll: () => { - walletsCall.getAll() - return { - type: NeuronWalletActions.Wallet, - } - }, - activateWallet: (id: string) => { - walletsCall.activate(id) - return { - type: NeuronWalletActions.Wallet, - payload: id, - } - }, - updateWallet: (params: { id: string; password?: string; newPassword?: string; name?: string }) => { - walletsCall.update(params) - return { - type: AppActions.Ignore, - payload: null, - } - }, - deleteWallet: (params: { id: string; password: string }) => { - setTimeout(() => { - walletsCall.delete(params) - }, 100) - return { - type: AppActions.DismissPasswordRequest, - payload: null, - } - }, - backupWallet: (params: { id: string; password: string }) => { - setTimeout(() => { - walletsCall.backup(params) - }, 100) - return { - type: AppActions.DismissPasswordRequest, - payload: null, - } - }, + createWalletWithMnemonic, + importWalletWithMnemonic, + updateCurrentWallet, + updateWalletList, + updateWallet, + setCurrentWallet, + sendTransaction, + updateAddressList, + updateAddressDescription, + deleteWallet, + backupWallet, } diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 9797fdbe8d..48f135c47c 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -1,14 +1,24 @@ -import emptyWalletState from 'states/initStates/wallet' import initStates from 'states/initStates' export enum NeuronWalletActions { - Initiate = 'initiate', - Chain = 'chain', - Wallet = 'wallet', - Settings = 'settings', + InitiateCurrentWalletAndWalletList = 'initiateCurrentWalletAndWalletList', UpdateCodeHash = 'updateCodeHash', + // wallets + UpdateCurrentWallet = 'updateCurrentWallet', + UpdateWalletList = 'updateWalletList', + UpdateAddressList = 'updateAddressList', + // transactions + UpdateTransactionList = 'updateTransactionList', + UpdateTransaction = 'updateTransaction', + // networks + UpdateNetworkList = 'updateNetworkList', + UpdateCurrentNetworkID = 'updateCurrentNetworkID', + // Connection + UpdateConnectionStatus = 'updateConnectionStatus', + UpdateSyncedBlockNumber = 'updateSyncedBlockNumber', } export enum AppActions { + ToggleAddressBookVisibility = 'toggleAddressBookVisibility', UpdateTransactionID = 'updateTransactionID', AddSendOutput = 'addSendOutput', RemoveSendOutput = 'removeSendOutput', @@ -28,6 +38,7 @@ export enum AppActions { UpdatePassword = 'updatePassword', UpdateTipBlockNumber = 'updateTipBlockNumber', UpdateChainInfo = 'updateChainInfo', + UpdateLoadings = 'updateLoadings', Ignore = 'ignore', } @@ -41,99 +52,125 @@ export const reducer = ( { type, payload }: { type: StateActions; payload: any } ): State.AppWithNeuronWallet => { const { app, wallet, settings, chain } = state - if (process.env.NODE_ENV === 'development') { + if (process.env.NODE_ENV === 'development' && window.localStorage.getItem('log-action')) { /* eslint-disable no-console */ - console.group() - console.info('type', type) - console.info('payload', payload) + console.group(`type: ${type}`) + console.info(payload) console.groupEnd() /* eslint-enable no-console */ } switch (type) { // Actions of Neuron Wallet - case NeuronWalletActions.Initiate: { - const { networks, networkID, wallets, wallet: incomingWallet } = payload + case NeuronWalletActions.InitiateCurrentWalletAndWalletList: { + const { wallets, wallet: incomingWallet } = payload return { ...state, wallet: incomingWallet || wallet, chain: { ...state.chain, - networkID, }, settings: { ...state.settings, wallets, - networks, }, } } - case NeuronWalletActions.Wallet: { - if (!payload) { - return { - ...state, - wallet: emptyWalletState, - } - } + case AppActions.ToggleAddressBookVisibility: { return { ...state, - wallet: { - ...state.wallet, - ...payload, + settings: { + ...settings, + showAddressBook: !settings.showAddressBook, }, } } - case NeuronWalletActions.Chain: { - const newState: State.AppWithNeuronWallet = { + case NeuronWalletActions.UpdateCodeHash: { + return { ...state, chain: { ...chain, - ...payload, + codeHash: payload, }, } - const pendingTxs = newState.chain.transactions.items - .filter(item => item.status === 'pending') - .sort((item1, item2) => +(item2.timestamp || item2.createdAt) - +(item1.timestamp || item1.createdAt)) - const determinedTxs = newState.chain.transactions.items - .filter(item => item.status !== 'pending') - .sort((item1, item2) => +(item2.timestamp || item2.createdAt) - +(item1.timestamp || item1.createdAt)) - newState.chain.transactions.items = [...pendingTxs, ...determinedTxs] - return newState - } - case NeuronWalletActions.Settings: { - if (payload.toggleAddressBook) { - return { - ...state, - settings: { - ...settings, - showAddressBook: !state.settings.showAddressBook, - }, - } + } + case NeuronWalletActions.UpdateCurrentWallet: { + return { + ...state, + wallet: { + ...wallet, + ...payload, + }, } - let currentWalletName = wallet.name - if (payload.wallets) { - const currentWallet = payload.wallets.find((w: { id: string; name: string }) => w.id === wallet.id) - if (currentWallet) { - currentWalletName = currentWallet.name - } + } + case NeuronWalletActions.UpdateWalletList: { + return { + ...state, + settings: { + ...settings, + wallets: payload, + }, } + } + case NeuronWalletActions.UpdateAddressList: { return { ...state, wallet: { ...wallet, - name: currentWalletName, + addresses: payload, }, + } + } + case NeuronWalletActions.UpdateTransactionList: { + return { + ...state, + chain: { + ...chain, + transactions: payload, + }, + } + } + case NeuronWalletActions.UpdateTransaction: { + return { + ...state, + chain: { + ...chain, + transaction: payload, + }, + } + } + case NeuronWalletActions.UpdateNetworkList: { + return { + ...state, settings: { ...settings, - ...payload, + networks: payload, }, } } - case NeuronWalletActions.UpdateCodeHash: { + case NeuronWalletActions.UpdateCurrentNetworkID: { return { ...state, chain: { ...chain, - codeHash: payload, + networkID: payload, + }, + } + } + case NeuronWalletActions.UpdateConnectionStatus: { + return { + ...state, + chain: { + ...chain, + connectionStatus: payload, + }, + } + } + case NeuronWalletActions.UpdateSyncedBlockNumber: { + return { + ...state, + chain: { + ...chain, + tipBlockNumber: payload, }, } } @@ -381,6 +418,18 @@ export const reducer = ( }, } } + case AppActions.UpdateLoadings: { + return { + ...state, + app: { + ...app, + loadings: { + ...app.loadings, + ...payload, + }, + }, + } + } default: { return state } diff --git a/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx b/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx index 894b16b849..7dafffb22f 100644 --- a/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx +++ b/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx @@ -1,8 +1,11 @@ import React from 'react' +import { Route, RouteComponentProps } from 'react-router-dom' import { storiesOf } from '@storybook/react' import { action } from '@storybook/addon-actions' -import PasswordRquest from 'components/PasswordRequest' +import StoryRouter from 'storybook-react-router' +import PasswordRequest from 'components/PasswordRequest' import initStates from 'states/initStates' +import { StateWithDispatch } from 'states/stateProvider/reducer' const states: { [title: string]: State.AppWithNeuronWallet } = { 'Wallet not Found': { @@ -93,10 +96,17 @@ const states: { [title: string]: State.AppWithNeuronWallet } = { }, } -const stories = storiesOf('PasswordRequest', module) +const PasswordRequestWithRouteProps = (props: StateWithDispatch) => ( + } /> +) + +const stories = storiesOf('PasswordRequest', module).addDecorator(StoryRouter()) Object.entries(states).forEach(([title, props]) => { stories.add(title, () => ( - action('Dispatch')(JSON.stringify(reducerAction, null, 2))} /> + action('Dispatch')(JSON.stringify(reducerAction, null, 2))} + /> )) }) diff --git a/packages/neuron-ui/src/stories/TransactionList.stories.tsx b/packages/neuron-ui/src/stories/TransactionList.stories.tsx index 4ea538c4c2..ca89de6dda 100644 --- a/packages/neuron-ui/src/stories/TransactionList.stories.tsx +++ b/packages/neuron-ui/src/stories/TransactionList.stories.tsx @@ -5,13 +5,18 @@ import transactions from './data/transactions' const stories = storiesOf('TransactionList', module) Object.entries(transactions).forEach(([title, list]) => { - stories.add(title, () => {}} />) + stories.add(title, () => {}} />) }) stories.add('Wtih empty pending list', () => ( item.status !== 'pending')} dispatch={() => {}} /> )) + +stories.add('Shimmered List', () => { + return {}} /> +}) diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 691ade8b74..b903d38a0f 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -75,6 +75,11 @@ declare namespace State { [index: string]: Message | null } notifications: Message[] + loadings: { + sending: boolean + addressList: boolean + transactionList: boolean + } } interface NetworkProperty { @@ -104,7 +109,6 @@ declare namespace State { interface Wallet extends WalletIdentity { balance: string addresses: Address[] - sending: boolean } interface Chain { diff --git a/packages/neuron-ui/src/types/Channel/index.d.ts b/packages/neuron-ui/src/types/Channel/index.d.ts deleted file mode 100644 index 87af9ac4b6..0000000000 --- a/packages/neuron-ui/src/types/Channel/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface ChannelResponse { - status: number - result: T - msg?: string -} diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts new file mode 100644 index 0000000000..55e0beeb6c --- /dev/null +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -0,0 +1,57 @@ +declare namespace Controller { + interface ImportMnemonicParams { + name: string + mnemonic: string + password: string + } + interface UpdateWalletParams { + id: string + password?: string + newPassword?: string + name?: string + } + + interface DeleteWalletParams { + id: string + password: string + } + + interface BackupWalletParams { + id: string + password: string + } + + type SetCurrentWalletParams = string + interface SendTransaction { + id: string + walletID: string + items: { + address: string + capacity: string + }[] + password: string + fee: string + description: string + } + + type GetAddressesByWalletIDParams = string + interface UpdateAddressDescriptionParams { + walletID: string + address: string + description: string + } + + interface CreateNetworkParams { + name: string + remote: string + } + + interface UpdateNetworkParams { + networkID: string + options: Partial<{ name: string; remote: string }> + } + interface UpdateTransactionDescriptionParams { + hash: string + description: string + } +} diff --git a/packages/neuron-ui/src/types/Subject/index.d.ts b/packages/neuron-ui/src/types/Subject/index.d.ts index 88eed10de2..e53a33eeee 100644 --- a/packages/neuron-ui/src/types/Subject/index.d.ts +++ b/packages/neuron-ui/src/types/Subject/index.d.ts @@ -5,3 +5,8 @@ interface NeuronWalletSubject { subscribe: (onData?: (data: T) => void, onError?: (error: Error) => void, onComplete?: () => void) => Subscription unsubscribe: () => void } + +declare namespace Command { + type Type = 'nav' | 'toggleAddressBook' | 'deleteWallet' | 'backupWallet' + type payload = string | null +} diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts index e6a991e4ca..f6e983f345 100644 --- a/packages/neuron-ui/src/utils/const.ts +++ b/packages/neuron-ui/src/utils/const.ts @@ -13,16 +13,6 @@ export enum ConnectionStatus { Offline = 'offline', } -export enum Channel { - NavTo = 'navTo', - App = 'app', - Chain = 'chain', - Networks = 'networks', - Transactions = 'transactions', - Wallets = 'wallets', - Helpers = 'helpers', -} - export enum Routes { Launch = '/', Overview = '/overview', diff --git a/packages/neuron-ui/src/utils/hooks.ts b/packages/neuron-ui/src/utils/hooks.ts index 818586e535..191a5c4fc2 100644 --- a/packages/neuron-ui/src/utils/hooks.ts +++ b/packages/neuron-ui/src/utils/hooks.ts @@ -1,5 +1,6 @@ import { useEffect, useState, useCallback } from 'react' -import actionCreators from 'states/stateProvider/actionCreators' +import { updateTransactionDescription, updateAddressDescription } from 'states/stateProvider/actionCreators' +import { StateDispatch } from 'states/stateProvider/reducer' export const useGoBack = (history: any) => { return useCallback(() => { @@ -11,50 +12,64 @@ export const useLocalDescription = ( type: 'address' | 'transaction', walletID: string, owners: { key: string; description: string }[], - dispatch: any + dispatch: StateDispatch ) => { - const [localDescription, setLocalDescription] = useState([]) + const [localDescription, setLocalDescription] = useState<{ description: string; key: string }[]>([]) useEffect(() => { - setLocalDescription(owners.map(owner => owner.description)) - }, [owners]) + setLocalDescription( + owners.map(owner => { + const local = localDescription.find(localDesc => localDesc.key === owner.key) + if (local && local.description) { + return local + } + return owner + }) + ) + }, [owners, localDescription]) const submitDescription = useCallback( - (idx: number) => { - if (owners[idx].description === localDescription[idx]) { + (key: string) => { + const ownerDesc = owners.find(owner => owner.key === key) + const localDesc = localDescription.find(local => local.key === key) + if (ownerDesc && localDesc && ownerDesc.description === localDesc.description) { return } - dispatch( - actionCreators.updateDescription({ - type, + if (localDesc && type === 'transaction') { + updateTransactionDescription({ + hash: key, + description: localDesc.description, + })(dispatch) + } + if (localDesc && type === 'address') { + updateAddressDescription({ walletID, - key: owners[idx].key, - description: localDescription[idx], - }) - ) + address: key, + description: localDesc.description, + })(dispatch) + } }, - [type, walletID, dispatch, localDescription, owners] + [type, walletID, localDescription, owners, dispatch] ) const onDescriptionFieldBlur = useCallback( - (idx: number): React.FocusEventHandler => () => { - submitDescription(idx) + (key: string): React.FocusEventHandler => () => { + submitDescription(key) }, [submitDescription] ) const onDescriptionPress = useCallback( - (idx: number) => (e: React.KeyboardEvent) => { + (key: string) => (e: React.KeyboardEvent) => { if (e.key && e.key === 'Enter') { - submitDescription(idx) + submitDescription(key) } }, [submitDescription] ) const onDescriptionChange = useCallback( - (idx: number) => (_e: React.FormEvent, value?: string) => { + (key: string) => (_e: React.FormEvent, value?: string) => { if (undefined !== value) { - const newDesc = [...localDescription] - newDesc[idx] = value + const newDesc = [...localDescription].map(desc => (desc.key === key ? { key, description: value } : desc)) setLocalDescription(newDesc) } }, diff --git a/packages/neuron-ui/src/utils/i18n.ts b/packages/neuron-ui/src/utils/i18n.ts index 71a69dab9d..fd44c750c3 100644 --- a/packages/neuron-ui/src/utils/i18n.ts +++ b/packages/neuron-ui/src/utils/i18n.ts @@ -1,15 +1,19 @@ import i18n from 'i18next' import { initReactI18next } from 'react-i18next' -import { language } from 'utils/localCache' +import { getLocale } from 'services/remote' + import zh from 'locales/zh.json' import en from 'locales/en.json' +const locale = getLocale() +const lng = ['zh', 'zh-CN'].includes(locale) ? 'zh' : 'en' + i18n.use(initReactI18next).init({ resources: { en, zh, }, - fallbackLng: language.load(), + fallbackLng: lng, interpolation: { escapeValue: false, }, diff --git a/packages/neuron-ui/src/utils/initializeApp.ts b/packages/neuron-ui/src/utils/initializeApp.ts deleted file mode 100644 index 2a7281c665..0000000000 --- a/packages/neuron-ui/src/utils/initializeApp.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { NeuronWalletActions, StateDispatch } from 'states/stateProvider/reducer' -import initStates from 'states/initStates' - -import { - wallets as walletsCache, - networks as networksCache, - addresses as addressesCache, - currentNetworkID as currentNetworkIDCache, - currentWallet as currentWalletCache, - systemScript as systemScriptCache, - language as languageCache, -} from 'utils/localCache' -import { Routes, ConnectionStatus } from 'utils/const' -import { WalletWizardPath } from 'components/WalletWizard' -import addressesToBalance from 'utils/addressesToBalance' - -const intializeApp = ({ - initializedState, - i18n, - history, - dispatch, -}: { - initializedState: any - i18n: any - history: any - dispatch: StateDispatch -}) => { - const { - locale = '', - networks = [], - currentNetworkID: networkID = '', - wallets = [], - currentWallet: wallet = initStates.wallet, - addresses = [], - transactions = initStates.chain.transactions, - tipNumber = '0', - connectionStatus = false, - codeHash = '', - } = initializedState - const lng = ['zh', 'zh-CN'].includes(locale) ? 'zh' : 'en' - if (lng !== i18n.language) { - i18n.changeLanguage(lng) - languageCache.save(lng) - } - if (wallet && wallet.id) { - history.push(Routes.Overview) - } else { - history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`) - } - if (networks.length) { - dispatch({ - type: NeuronWalletActions.Initiate, - payload: { - networks, - networkID, - wallet: { ...wallet, balance: addressesToBalance(addresses), addresses }, - wallets, - }, - }) - } - dispatch({ - type: NeuronWalletActions.Chain, - payload: { - tipBlockNumber: tipNumber, - codeHash, - connectionStatus: connectionStatus ? ConnectionStatus.Online : ConnectionStatus.Offline, - transactions: { ...initStates.chain.transactions, ...transactions }, - }, - }) - - currentWalletCache.save(wallet) - currentNetworkIDCache.save(networkID) - walletsCache.save(wallets) - addressesCache.save(addresses) - networksCache.save(networks) - systemScriptCache.save({ codeHash }) -} -export default intializeApp diff --git a/packages/neuron-ui/src/utils/localCache.ts b/packages/neuron-ui/src/utils/localCache.ts index 7302aa2926..2861d7c3aa 100644 --- a/packages/neuron-ui/src/utils/localCache.ts +++ b/packages/neuron-ui/src/utils/localCache.ts @@ -6,7 +6,6 @@ export enum LocalCacheKey { CurrentWallet = 'currentWallet', CurrentNetworkID = 'currentNetworkID', SystemScript = 'systemScript', - Language = 'lng', } enum AddressBookVisibility { Invisible = '0', @@ -142,16 +141,6 @@ export const systemScript = { }, } -export const language = { - save: (lng: string) => { - window.localStorage.setItem(LocalCacheKey.Language, lng) - return true - }, - load: () => { - return window.localStorage.getItem(LocalCacheKey.Language) || 'en' - }, -} - export default { LocalCacheKey, addressBook, @@ -161,5 +150,4 @@ export default { currentWallet, currentNetworkID, systemScript, - language, } diff --git a/packages/neuron-wallet/src/controllers/app/index.ts b/packages/neuron-wallet/src/controllers/app/index.ts index 26aafda06f..97d9452863 100644 --- a/packages/neuron-wallet/src/controllers/app/index.ts +++ b/packages/neuron-wallet/src/controllers/app/index.ts @@ -1,64 +1,26 @@ import path from 'path' import { dialog, shell, Menu, MessageBoxOptions, SaveDialogOptions, BrowserWindow } from 'electron' -import { take } from 'rxjs/operators' -import systemScriptSubject from '../../models/subjects/system-script' import app from '../../app' import { URL, contextMenuTemplate } from './options' import TransactionsController from '../transactions' -import NetworksService from '../../services/networks' import WalletsService from '../../services/wallets' -import NodeService from '../../services/node' import WalletsController from '../wallets' -import SyncInfoController from '../sync-info' import { Controller as ControllerDecorator } from '../../decorators' import { Channel, ResponseCode } from '../../utils/const' import WindowManager from '../../models/window-manager' import i18n from '../../utils/i18n' import env from '../../env' - -const networksService = NetworksService.getInstance() -const nodeService = NodeService.getInstance() +import CommandSubject from '../../models/subjects/command' @ControllerDecorator(Channel.App) export default class AppController { public static getInitState = async () => { const walletsService = WalletsService.getInstance() - const [ - currentWallet = null, - wallets = [], - currentNetworkID = '', - networks = [], - tipNumber = '0', - connectionStatus = false, - codeHash = '', - ] = await Promise.all([ + const [currentWallet = null, wallets = []] = await Promise.all([ walletsService.getCurrent(), walletsService.getAll(), - networksService.getCurrentID(), - networksService.getAll(), - SyncInfoController.currentBlockNumber() - .then(res => { - if (res.status) { - return res.result.currentBlockNumber - } - return '0' - }) - .catch(() => '0'), - new Promise(resolve => { - nodeService.connectionStatusSubject.pipe(take(1)).subscribe( - status => { - resolve(status) - }, - () => { - resolve(false) - } - ) - }), - new Promise(resolve => { - systemScriptSubject.pipe(take(1)).subscribe(({ codeHash: currentCodeHash }) => resolve(currentCodeHash)) - }), ]) const addresses: Controller.Address[] = await (currentWallet ? WalletsController.getAllAddresses(currentWallet.id).then(res => res.result) @@ -79,15 +41,10 @@ export default class AppController { }, wallets: [...wallets.map(({ name, id }) => ({ id, name }))], addresses, - currentNetworkID, - networks, transactions, locale, - tipNumber, - connectionStatus, - codeHash, } - return initState + return { status: ResponseCode.Success, result: initState } } public static handleViewError = (error: string) => { @@ -108,23 +65,19 @@ export default class AppController { } public static toggleAddressBook() { - WindowManager.broadcast(Channel.App, 'toggleAddressBook', { - status: ResponseCode.Success, - }) + if (WindowManager.mainWindow) { + CommandSubject.next({ + winID: WindowManager.mainWindow.id, + type: 'toggleAddressBook', + payload: null, + }) + } } public static navTo(url: string) { - WindowManager.sendToMainWindow(Channel.App, 'navTo', { - status: ResponseCode.Success, - result: url, - }) - } - - public static setUILocale(locale: string) { - WindowManager.broadcast(Channel.App, 'setUILocale', { - status: ResponseCode.Success, - result: locale, - }) + if (WindowManager.mainWindow) { + CommandSubject.next({ winID: WindowManager.mainWindow.id, type: 'nav', payload: url }) + } } public static openExternal(url: string) { diff --git a/packages/neuron-wallet/src/controllers/app/options.ts b/packages/neuron-wallet/src/controllers/app/options.ts index 385681d84a..eec6e791f8 100644 --- a/packages/neuron-wallet/src/controllers/app/options.ts +++ b/packages/neuron-wallet/src/controllers/app/options.ts @@ -121,7 +121,7 @@ export const contextMenuTemplate: { { label: i18n.t('contextMenu.backup'), click: async () => { - walletsService.requestPassword(id, 'backup') + walletsService.requestPassword(id, 'backupWallet') }, }, { @@ -133,7 +133,7 @@ export const contextMenuTemplate: { { label: i18n.t('contextMenu.delete'), click: async () => { - walletsService.requestPassword(id, 'delete') + walletsService.requestPassword(id, 'deleteWallet') }, }, ] diff --git a/packages/neuron-wallet/src/controllers/helpers.ts b/packages/neuron-wallet/src/controllers/helpers.ts deleted file mode 100644 index e06ef5995d..0000000000 --- a/packages/neuron-wallet/src/controllers/helpers.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { generateMnemonic } from '../models/keys/key' -import { Controller as ControllerDecorator, CatchControllerError } from '../decorators' -import { Channel, ResponseCode } from '../utils/const' -import { FailToCreateMnemonic } from '../exceptions' - -/** - * @class HelpersController - * @description handle messages from helpers channel - */ -@ControllerDecorator(Channel.Helpers) -export default class HelpersController { - @CatchControllerError - public static async generateMnemonic() { - const mnemonic = generateMnemonic() - if (mnemonic) { - return { - status: ResponseCode.Success, - result: mnemonic, - } - } - throw new FailToCreateMnemonic() - } -} - -/* eslint-disable */ -declare global { - module Controller { - type HelpersMethod = Exclude - } -} -/* eslint-enable */ diff --git a/packages/neuron-wallet/src/controllers/index.ts b/packages/neuron-wallet/src/controllers/index.ts index 4ba08e834c..600f3d2981 100644 --- a/packages/neuron-wallet/src/controllers/index.ts +++ b/packages/neuron-wallet/src/controllers/index.ts @@ -2,7 +2,6 @@ import AppController from './app' import NetworksController from './networks' import WalletsController from './wallets' import TransactionsController from './transactions' -import HelpersController from './helpers' import SyncInfoController from './sync-info' export default { @@ -10,6 +9,5 @@ export default { NetworksController, WalletsController, TransactionsController, - HelpersController, SyncInfoController, } diff --git a/packages/neuron-wallet/src/controllers/wallets/index.ts b/packages/neuron-wallet/src/controllers/wallets/index.ts index 7c70947923..a3df9b6c68 100644 --- a/packages/neuron-wallet/src/controllers/wallets/index.ts +++ b/packages/neuron-wallet/src/controllers/wallets/index.ts @@ -17,7 +17,6 @@ import { IncorrectPassword, } from '../../exceptions' import i18n from '../../utils/i18n' -import windowManager from '../../models/window-manager' import AddressService from '../../services/addresses' import WalletCreatedSubject from '../../models/subjects/wallet-created-subject' @@ -342,10 +341,6 @@ export default class WalletsController { throw new IsRequired('Parameters') } try { - windowManager.broadcast(Channel.Wallets, 'sendingStatus', { - status: ResponseCode.Success, - result: true, - }) const walletsService = WalletsService.getInstance() const hash = await walletsService.sendCapacity( params.walletID, @@ -361,16 +356,8 @@ export default class WalletsController { } catch (err) { return { status: ResponseCode.Fail, - msg: { - content: `Error: "${err.message}"`, - id: params.id, - }, + msg: `Error: "${err.message}"`, } - } finally { - windowManager.broadcast(Channel.Wallets, 'sendingStatus', { - status: ResponseCode.Success, - result: false, - }) } } diff --git a/packages/neuron-wallet/src/models/subjects/command.ts b/packages/neuron-wallet/src/models/subjects/command.ts new file mode 100644 index 0000000000..861dddd616 --- /dev/null +++ b/packages/neuron-wallet/src/models/subjects/command.ts @@ -0,0 +1,9 @@ +import { Subject } from 'rxjs' + +const CommandSubject = new Subject<{ + winID: number + type: 'nav' | 'toggleAddressBook' | 'deleteWallet' | 'backupWallet' + payload: string | null +}>() + +export default CommandSubject diff --git a/packages/neuron-wallet/src/models/subjects/current-block-subject.ts b/packages/neuron-wallet/src/models/subjects/current-block-subject.ts index d2e653fc07..9dc263bc0e 100644 --- a/packages/neuron-wallet/src/models/subjects/current-block-subject.ts +++ b/packages/neuron-wallet/src/models/subjects/current-block-subject.ts @@ -1,7 +1,6 @@ import { ReplaySubject } from 'rxjs' import { sampleTime } from 'rxjs/operators' -import windowManager from '../window-manager' -import { Channel, ResponseCode } from '../../utils/const' +import { SyncedBlockNumberSubject } from './node' export interface CurrentBlockInfo { blockNumber: string @@ -17,10 +16,7 @@ export class CurrentBlockSubject { static subscribe() { CurrentBlockSubject.subject.pipe(sampleTime(500)).subscribe(({ blockNumber }) => { - windowManager.broadcast(Channel.Chain, 'tipBlockNumber', { - status: ResponseCode.Success, - result: blockNumber, - }) + SyncedBlockNumberSubject.next(blockNumber) }) } } diff --git a/packages/neuron-wallet/src/models/subjects/networks.ts b/packages/neuron-wallet/src/models/subjects/networks.ts index 56dcf54594..3e53b21f13 100644 --- a/packages/neuron-wallet/src/models/subjects/networks.ts +++ b/packages/neuron-wallet/src/models/subjects/networks.ts @@ -1,20 +1,10 @@ -import { Subject } from 'rxjs' -import { debounceTime } from 'rxjs/operators' -import DataUpdateSubject from './data-update' +import { BehaviorSubject } from 'rxjs' -const DEBOUNCE_TIME = 50 - -export const NetworkListSubject = new Subject<{ +export const NetworkListSubject = new BehaviorSubject<{ currentNetworkList: Controller.Network[] -}>() -export const CurrentNetworkIDSubject = new Subject<{ currentNetworkID: Controller.NetworkID }>() - -NetworkListSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(() => { - DataUpdateSubject.next({ dataType: 'network', actionType: 'update' }) -}) - -CurrentNetworkIDSubject.pipe(debounceTime(DEBOUNCE_TIME)).subscribe(() => { - DataUpdateSubject.next({ dataType: 'network', actionType: 'update' }) +}>({ currentNetworkList: [] }) +export const CurrentNetworkIDSubject = new BehaviorSubject<{ currentNetworkID: Controller.NetworkID }>({ + currentNetworkID: '', }) export default { diff --git a/packages/neuron-wallet/src/models/subjects/node.ts b/packages/neuron-wallet/src/models/subjects/node.ts new file mode 100644 index 0000000000..d965107205 --- /dev/null +++ b/packages/neuron-wallet/src/models/subjects/node.ts @@ -0,0 +1,6 @@ +import { BehaviorSubject } from 'rxjs' + +export const ConnectionStatusSubject = new BehaviorSubject(false) +export const SyncedBlockNumberSubject = new BehaviorSubject('0') + +export default { ConnectionStatusSubject, SyncedBlockNumberSubject } diff --git a/packages/neuron-wallet/src/models/window-manager.ts b/packages/neuron-wallet/src/models/window-manager.ts index 8c095f0938..1837fea737 100644 --- a/packages/neuron-wallet/src/models/window-manager.ts +++ b/packages/neuron-wallet/src/models/window-manager.ts @@ -1,50 +1,5 @@ import { BrowserWindow } from 'electron' -import { Channel } from '../utils/const' -import logger from '../utils/logger' - -const error = { level: 'error', message: 'Electron is not loaded' } - -interface SendMessage { - (channel: Channel.App, method: Controller.AppMethod, params: any): void - ( - channel: Channel.Wallets, - method: Controller.WalletsMethod | 'allAddresses' | 'sendingStatus' | 'requestPassword', - params: any - ): void - (channel: Channel.Transactions, method: Controller.TransactionsMethod | 'transactionUpdated', params: any): void - (channel: Channel.Networks, method: Controller.NetworksMethod, params: any): void - (channel: Channel.Helpers, method: Controller.HelpersMethod, params: any): void - (channel: Channel.Chain, method: 'status' | 'tipBlockNumber', params: any): void -} export default class WindowManager { public static mainWindow: BrowserWindow | null - public static broadcast: SendMessage = (channel: Channel, method: string, params: any): void => { - if (!BrowserWindow) { - logger.log(error) - return - } - BrowserWindow.getAllWindows().forEach(window => { - if (window && window.webContents) { - window.webContents.send(channel, method, params) - } - }) - } - - public static sendToFocusedWindow: SendMessage = (channel: Channel, method: string, params: any): void => { - if (!BrowserWindow) { - logger.log(error) - return - } - const window = BrowserWindow.getFocusedWindow() - if (window) { - window.webContents.send(channel, method, params) - } - } - - public static sendToMainWindow: SendMessage = (channel: Channel, method: string, params: any): void => { - if (WindowManager.mainWindow) { - WindowManager.mainWindow.webContents.send(channel, method, params) - } - } } diff --git a/packages/neuron-wallet/src/services/networks.ts b/packages/neuron-wallet/src/services/networks.ts index 8942747201..7796ab6555 100644 --- a/packages/neuron-wallet/src/services/networks.ts +++ b/packages/neuron-wallet/src/services/networks.ts @@ -6,7 +6,6 @@ import Store from '../models/store' import env from '../env' import { Validate, Required } from '../decorators' -import NodeService from './node' import { UsedName, NetworkNotFound, InvalidFormat } from '../exceptions' import { NetworkListSubject, CurrentNetworkIDSubject } from '../models/subjects/networks' @@ -45,6 +44,25 @@ export default class NetworksService extends Store { constructor() { super('networks', 'index.json', JSON.stringify(env.presetNetworks)) + this.getAll().then(currentNetworkList => { + if (currentNetworkList) { + NetworkListSubject.next({ + currentNetworkList, + }) + } + }) + + this.getCurrentID().then(currentNetworkID => { + if (currentNetworkID) { + CurrentNetworkIDSubject.next({ currentNetworkID }) + this.get(currentNetworkID).then(network => { + if (network) { + networkSwitchSubject.next(network) + } + }) + } + }) + this.on(NetworksKey.List, async (_, currentNetworkList: NetworkWithID[] = []) => { NetworkListSubject.next({ currentNetworkList }) @@ -66,15 +84,8 @@ export default class NetworksService extends Store { throw new NetworkNotFound(currentNetworkID) } CurrentNetworkIDSubject.next({ currentNetworkID }) - NodeService.getInstance().setNetwork(currentNetwork.remote) networkSwitchSubject.next(currentNetwork) }) - - this.getCurrentID().then(currentID => { - if (currentID) { - this.emit(NetworksKey.Current, null, currentID) - } - }) } public getAll = async () => { diff --git a/packages/neuron-wallet/src/services/node.ts b/packages/neuron-wallet/src/services/node.ts index c83ffa1e39..a003c1da62 100644 --- a/packages/neuron-wallet/src/services/node.ts +++ b/packages/neuron-wallet/src/services/node.ts @@ -2,8 +2,9 @@ import Core from '@nervosnetwork/ckb-sdk-core' import { interval, BehaviorSubject, merge } from 'rxjs' import { distinctUntilChanged, sampleTime, flatMap, delay, retry, debounceTime } from 'rxjs/operators' import { ShouldBeTypeOf } from '../exceptions' -import windowManager from '../models/window-manager' -import { Channel, ResponseCode } from '../utils/const' +import { ConnectionStatusSubject } from '../models/subjects/node' +import { CurrentNetworkIDSubject } from '../models/subjects/networks' +import NetworksService from './networks' class NodeService { private static instance: NodeService @@ -25,6 +26,12 @@ class NodeService { constructor() { this.start() this.syncConnectionStatus() + CurrentNetworkIDSubject.subscribe(async ({ currentNetworkID }) => { + const currentNetwork = await NetworksService.getInstance().get(currentNetworkID) + if (currentNetwork) { + this.setNetwork(currentNetwork.remote) + } + }) } public syncConnectionStatus = () => { @@ -33,10 +40,7 @@ class NodeService { merge(periodSync, realtimeSync) .pipe(debounceTime(500)) .subscribe(connectionStatus => { - windowManager.broadcast(Channel.Chain, 'status', { - status: ResponseCode.Success, - result: connectionStatus, - }) + ConnectionStatusSubject.next(connectionStatus) }) } diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 4ea50acedc..ad0cc84dd3 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -17,9 +17,9 @@ import Keychain from '../models/keys/keychain' import AddressDbChangedSubject from '../models/subjects/address-db-changed-subject' import AddressesUsedSubject from '../models/subjects/addresses-used-subject' import { WalletListSubject, CurrentWalletSubject } from '../models/subjects/wallets' -import { Channel, ResponseCode } from '../utils/const' -import windowManager from '../models/window-manager' import dataUpdateSubject from '../models/subjects/data-update' +import CommandSubject from '../models/subjects/command' +import WindowManager from '../models/window-manager' const { core } = NodeService.getInstance() const fileService = FileService.getInstance() @@ -412,13 +412,13 @@ export default class WalletService { })) } - public requestPassword = (walletID: string, actionType: 'delete' | 'backup') => { - windowManager.sendToMainWindow(Channel.Wallets, 'requestPassword', { - status: ResponseCode.Success, - result: { - walletID, - actionType, - }, - }) + public requestPassword = (walletID: string, actionType: 'deleteWallet' | 'backupWallet') => { + if (WindowManager.mainWindow) { + CommandSubject.next({ + winID: WindowManager.mainWindow.id, + type: actionType, + payload: walletID, + }) + } } } diff --git a/packages/neuron-wallet/src/utils/application-menu.ts b/packages/neuron-wallet/src/utils/application-menu.ts index dff3278f85..b366be9c2a 100644 --- a/packages/neuron-wallet/src/utils/application-menu.ts +++ b/packages/neuron-wallet/src/utils/application-menu.ts @@ -79,7 +79,7 @@ export const walletMenuItem: MenuItemConstructorOptions = { // TODO: show the error message return } - walletsService.requestPassword(currentWallet.id, 'backup') + walletsService.requestPassword(currentWallet.id, 'backupWallet') }, }, { @@ -92,7 +92,7 @@ export const walletMenuItem: MenuItemConstructorOptions = { // TODO: show the error message return } - walletsService.requestPassword(currentWallet.id, 'delete') + walletsService.requestPassword(currentWallet.id, 'deleteWallet') }, }, /** diff --git a/packages/neuron-wallet/src/utils/const.ts b/packages/neuron-wallet/src/utils/const.ts index 2503fe7eda..db587ce125 100644 --- a/packages/neuron-wallet/src/utils/const.ts +++ b/packages/neuron-wallet/src/utils/const.ts @@ -3,12 +3,9 @@ export const MAX_PASSWORD_LENGTH = 50 export enum Channel { App = 'app', - Chain = 'chain', Networks = 'networks', Wallets = 'wallets', Transactions = 'transactions', - Helpers = 'helpers', - DataUpdate = 'dataUpdate', } export enum ResponseCode {