From 7d5aaaa5bd8a0f34694eb3e8ce5ba6bbecf03d71 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 20 May 2016 12:40:44 -0700 Subject: [PATCH] Add ability to add account to vault Scrolling to the bottom of the accounts page now reveals a downward-facing chevron button. Pressing this button shows loading indication, adds a new account to the identity vault, displays it in the list, and scrolls the list to the bottom of the page. Any number of accounts can be generated in this way, and the UX feels intuitive without having to overly explain how HD paths work. --- app/scripts/background.js | 1 + app/scripts/lib/idStore.js | 15 +++++++++++ ui/app/accounts.js | 55 +++++++++++++++++++++++++++++++++----- ui/app/actions.js | 15 +++++++++++ ui/app/css/lib.css | 4 +++ ui/app/reducers/app.js | 6 +++++ 6 files changed, 89 insertions(+), 7 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index a52eab2d3..e77df1519 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -182,6 +182,7 @@ function setupControllerConnection(stream){ setLocked: idStore.setLocked.bind(idStore), clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore), exportAccount: idStore.exportAccount.bind(idStore), + revealAccount: idStore.revealAccount.bind(idStore), }) stream.pipe(dnode).pipe(stream) dnode.on('remote', function(remote){ diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 6d3d0c0aa..0604c4bca 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -115,6 +115,21 @@ IdentityStore.prototype.setSelectedAddress = function(address, cb){ if (cb) return cb(null, address) } +IdentityStore.prototype.revealAccount = function(cb) { + let addresses = this._getAddresses() + const derivedKey = this._idmgmt.derivedKey + const keyStore = this._keyStore + + keyStore.setDefaultHdDerivationPath(this.hdPathString) + keyStore.generateNewAddress(derivedKey, 1) + configManager.setWallet(keyStore.serialize()) + + addresses = this._getAddresses() + this._loadIdentities() + this._didUpdate() + cb(null) +} + IdentityStore.prototype.getNetwork = function(tries) { if (tries === 0) return this.web3.version.getNetwork((err, network) => { diff --git a/ui/app/accounts.js b/ui/app/accounts.js index e609e7424..0f3030829 100644 --- a/ui/app/accounts.js +++ b/ui/app/accounts.js @@ -9,6 +9,7 @@ const EtherBalance = require('./components/eth-balance') const valuesFor = require('./util').valuesFor const addressSummary = require('./util').addressSummary const formatBalance = require('./util').formatBalance +const findDOMNode = require('react-dom').findDOMNode module.exports = connect(mapStateToProps)(AccountsScreen) @@ -20,6 +21,7 @@ function mapStateToProps(state) { unconfTxs: state.metamask.unconfTxs, selectedAddress: state.metamask.selectedAddress, currentDomain: state.appState.currentDomain, + scrollToBottom: state.appState.scrollToBottom, } } @@ -36,13 +38,19 @@ AccountsScreen.prototype.render = function() { var actions = { onSelect: this.onSelect.bind(this), onShowDetail: this.onShowDetail.bind(this), + revealAccount: this.onRevealAccount.bind(this), } return ( - h('.accounts-section.flex-column.flex-grow', [ + h('.accounts-section.flex-grow', [ // subtitle and nav - h('.section-title.flex-column.flex-center', [ + h('.section-title.flex-center', [ + h('i.fa.fa-arrow-left.fa-lg.pointer.hover-white', { + onClick: (event) => { + state.dispatch(actions.goHome()) + } + }), h('h2.page-subtitle', 'Select Account'), ]), @@ -51,12 +59,32 @@ AccountsScreen.prototype.render = function() { // identity selection h('section.identity-section.flex-column', { style: { + height: '418px', overflowY: 'auto', overflowX: 'hidden', } }, - identityList.map(renderAccountPanel) - ), + [ + identityList.map(renderAccountPanel), + + h('hr.horizontal-line', {key: 'horizontal-line1'}), + h('div.footer.hover-white.pointer', { + key: 'reveal-account-bar', + onClick:() => { + actions.revealAccount() + }, + style: { + display: 'flex', + flex: '1 0 auto', + height: '40px', + paddint: '10px', + justifyContent: 'center', + alignItems: 'center', + } + }, [ + h('i.fa.fa-chevron-down.fa-lg', {key: ''}), + ]), + ]), unconfTxList.length ? ( @@ -70,10 +98,7 @@ AccountsScreen.prototype.render = function() { ) : ( null ), - - ]) - ) function renderAccountPanel(identity){ @@ -90,6 +115,7 @@ AccountsScreen.prototype.render = function() { return ( h('.accounts-list-option.flex-row.flex-space-between.cursor-pointer', { + key: `account-panel-${identity.address}`, style: { flex: '1 0 auto', background: isSelected ? 'white' : 'none', @@ -120,6 +146,17 @@ AccountsScreen.prototype.render = function() { } } +// If a new account was revealed, scroll to the bottom +AccountsScreen.prototype.componentDidUpdate = function(){ + const scrollToBottom = this.props.scrollToBottom + + if (scrollToBottom) { + var container = findDOMNode(this) + var scrollable = container.querySelector('.identity-section') + scrollable.scrollTop = scrollable.scrollHeight + } +} + AccountsScreen.prototype.navigateToConfTx = function(){ event.stopPropagation() this.props.dispatch(actions.showConfTxPage()) @@ -136,3 +173,7 @@ AccountsScreen.prototype.onShowDetail = function(address, event){ event.stopPropagation() this.props.dispatch(actions.showAccountDetail(address)) } + +AccountsScreen.prototype.onRevealAccount = function() { + this.props.dispatch(actions.revealAccount()) +} diff --git a/ui/app/actions.js b/ui/app/actions.js index ee5e417d4..5d6f503e2 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -48,6 +48,8 @@ var actions = { SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE', SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE', SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE', + REVEAL_ACCOUNT: 'REVEAL_ACCOUNT', + revealAccount: revealAccount, // account detail screen SHOW_SEND_PAGE: 'SHOW_SEND_PAGE', showSendPage: showSendPage, @@ -175,6 +177,19 @@ function setSelectedAddress(address) { } } +function revealAccount() { + return (dispatch) => { + dispatch(this.showLoadingIndication()) + _accountManager.revealAccount((err) => { + dispatch(this.hideLoadingIndication()) + if (err) return dispatch(this.displayWarning(err.message)) + dispatch({ + type: this.REVEAL_ACCOUNT, + }) + }) + } +} + function signMsg(msgData) { return (dispatch) => { dispatch(this.showLoadingIndication()) diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 1eba7465b..97ff02c46 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -107,6 +107,10 @@ user-select: none; } +.hover-white:hover { + background: white; +} + .pointer { cursor: pointer; } diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 1c0154cd5..a29a8f79c 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -191,6 +191,12 @@ function reduceApp(state, action) { transForward: true, isLoading: false, warning: null, + scrollToBottom: false, + }) + + case actions.REVEAL_ACCOUNT: + return extend(appState, { + scrollToBottom: true, }) case actions.SHOW_CONF_TX_PAGE: