From 2dd7bd6bd0d026da339c1e55d52270674be13f3d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Apr 2016 12:20:33 -0700 Subject: [PATCH 1/6] Make account detail view the primary view - When unlocking, the first account is now selected by default and displayed as the main view. - There is now a "CHANGE ACCT" button on the detail view to show the accounts list. - Clicking an account from the accounts list now navigates to the detail view and selects that account. - Config/Info screen "back" buttons now fire a new action, `GO_HOME`, which is configured to navigate to the accountDetail view, putting that logic in one place. - When locking and unlocking again, the first account is always displayed, eventually we should persist the selection. --- app/scripts/lib/idStore.js | 10 +++-- test/unit/actions/restore_vault_test.js | 2 +- ui/app/account-detail.js | 56 +++++++++++++------------ ui/app/actions.js | 25 ++++++----- ui/app/components/account-panel.js | 2 +- ui/app/config.js | 2 +- ui/app/info.js | 2 +- ui/app/loading.js | 2 - ui/app/reducers/app.js | 27 +++++++++++- 9 files changed, 80 insertions(+), 48 deletions(-) diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 7763d33d8..525fdae30 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -72,7 +72,8 @@ IdentityStore.prototype.setStore = function(store){ IdentityStore.prototype.clearSeedWordCache = function(cb) { configManager.setShowSeedWords(false) - cb() + var accounts = this._loadIdentities() + cb(null, accounts) } IdentityStore.prototype.getState = function(){ @@ -119,8 +120,8 @@ IdentityStore.prototype.submitPassword = function(password, cb){ this._tryPassword(password, (err) => { if (err) return cb(err) // load identities before returning... - this._loadIdentities() - cb() + var accounts = this._loadIdentities() + cb(null, accounts) }) } @@ -212,6 +213,7 @@ IdentityStore.prototype._loadIdentities = function(){ if (!this._isUnlocked()) throw new Error('not unlocked') var addresses = this._getAddresses() + var accountArray = [] addresses.forEach((address, i) => { // // add to ethStore this._ethStore.addAccount(address) @@ -223,8 +225,10 @@ IdentityStore.prototype._loadIdentities = function(){ mayBeFauceting: this._mayBeFauceting(i), } this._currentState.identities[address] = identity + accountArray.push(identity) }) this._didUpdate() + return accountArray } // mayBeFauceting diff --git a/test/unit/actions/restore_vault_test.js b/test/unit/actions/restore_vault_test.js index 5873a0181..5675028b1 100644 --- a/test/unit/actions/restore_vault_test.js +++ b/test/unit/actions/restore_vault_test.js @@ -21,7 +21,7 @@ describe('#recoverFromSeed(password, seed)', function() { // stub out account manager actions._setAccountManager({ - recoverFromSeed(pw, seed, cb) { cb() }, + recoverFromSeed(pw, seed, cb) { cb(null, [{}, {}]) }, }) it('sets metamask.isUnlocked to true', function() { diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 025644efe..57f932a2b 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -10,12 +10,11 @@ const transactionList = require('./components/transaction-list') module.exports = connect(mapStateToProps)(AccountDetailScreen) function mapStateToProps(state) { - var accountDetail = state.appState.accountDetail return { identities: state.metamask.identities, accounts: state.metamask.accounts, address: state.appState.currentView.context, - accountDetail: accountDetail, + accountDetail: state.appState.accountDetail, transactions: state.metamask.transactions, networkVersion: state.networkVersion, } @@ -26,7 +25,6 @@ function AccountDetailScreen() { Component.call(this) } - AccountDetailScreen.prototype.render = function() { var state = this.props var identity = state.identities[state.address] @@ -40,9 +38,6 @@ AccountDetailScreen.prototype.render = function() { // subtitle and nav h('.section-title.flex-row.flex-center', [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: this.navigateToAccounts.bind(this), - }), h('h2.page-subtitle', 'Account Detail'), ]), @@ -51,28 +46,35 @@ AccountDetailScreen.prototype.render = function() { showFullAddress: true, identity: identity, account: account, + }, []), + + h('div', { + style: { + display: 'flex', + } }, [ - h('.flex-row.flex-space-around', [ - // h('button', 'GET ETH'), DISABLED UNTIL WORKING - - h('button', { - onClick: () => { - copyToClipboard(identity.address) - }, - }, 'COPY ADDR'), - - h('button', { - onClick: () => { - this.props.dispatch(actions.showSendPage()) - }, - }, 'SEND'), - - h('button', { - onClick: () => { - this.requestAccountExport(identity.address) - }, - }, 'EXPORT'), - ]), + + h('button', { + onClick: this.navigateToAccounts.bind(this), + }, 'CHANGE ACCT'), + + h('button', { + onClick: () => { + copyToClipboard(identity.address) + }, + }, 'COPY ADDR'), + + h('button', { + onClick: () => { + this.props.dispatch(actions.showSendPage()) + }, + }, 'SEND'), + + h('button', { + onClick: () => { + this.requestAccountExport(identity.address) + }, + }, 'EXPORT'), ]), transactionList(transactions diff --git a/ui/app/actions.js b/ui/app/actions.js index 339c28be3..e2d81883f 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1,4 +1,6 @@ var actions = { + GO_HOME: 'GO_HOME', + goHome: goHome, // remote state UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE', updateMetamaskState: updateMetamaskState, @@ -87,24 +89,29 @@ var actions = { module.exports = actions - var _accountManager = null function _setAccountManager(accountManager){ _accountManager = accountManager } +function goHome() { + return { + type: this.GO_HOME, + } +} + // async actions function tryUnlockMetamask(password) { return (dispatch) => { dispatch(this.unlockInProgress()) - _accountManager.submitPassword(password, (err) => { + _accountManager.submitPassword(password, (err, accounts) => { dispatch(this.hideLoadingIndication()) if (err) { dispatch(this.unlockFailed()) } else { dispatch(this.unlockMetamask()) - dispatch(this.setSelectedAddress()) + dispatch(this.showAccountDetail(accounts[0].address)) } }) } @@ -123,7 +130,7 @@ function recoverFromSeed(password, seed) { return (dispatch) => { // dispatch(this.createNewVaultInProgress()) dispatch(this.showLoadingIndication()) - _accountManager.recoverFromSeed(password, seed, (err, result) => { + _accountManager.recoverFromSeed(password, seed, (err, accounts) => { if (err) { dispatch(this.hideLoadingIndication()) var message = err.message @@ -131,11 +138,9 @@ function recoverFromSeed(password, seed) { } dispatch(this.unlockMetamask()) - dispatch(this.setSelectedAddress()) - dispatch(this.updateMetamaskState(result)) + dispatch(this.showAccountDetail(accounts[0].address)) dispatch(this.hideLoadingIndication()) - dispatch(this.showAccountsPage()) - }) + }) } } @@ -297,10 +302,10 @@ function clearSeedWordCache() { function confirmSeedWords() { return (dispatch) => { dispatch(this.showLoadingIndication()) - _accountManager.clearSeedWordCache((err) => { + _accountManager.clearSeedWordCache((err, accounts) => { dispatch(this.clearSeedWordCache()) console.log('Seed word cache cleared.') - dispatch(this.setSelectedAddress()) + dispatch(this.showAccountDetail(accounts[0].address)) }) } } diff --git a/ui/app/components/account-panel.js b/ui/app/components/account-panel.js index 4e433b87d..9de29cd91 100644 --- a/ui/app/components/account-panel.js +++ b/ui/app/components/account-panel.js @@ -25,7 +25,7 @@ AccountPanel.prototype.render = function() { style: { flex: '1 0 auto', }, - onClick: state.onSelect && state.onSelect.bind(null, identity.address), + onClick: state.onShowDetail && state.onShowDetail.bind(null, identity.address), }, [ // account identicon diff --git a/ui/app/config.js b/ui/app/config.js index f4eecf7f8..ded065bf8 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -31,7 +31,7 @@ ConfigScreen.prototype.render = function() { h('.section-title.flex-row.flex-center', [ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { onClick: (event) => { - state.dispatch(actions.showAccountsPage()) + state.dispatch(actions.goHome()) } }), h('h2.page-subtitle', 'Configuration'), diff --git a/ui/app/info.js b/ui/app/info.js index ae8c6efc5..f6311b4cb 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -26,7 +26,7 @@ InfoScreen.prototype.render = function() { h('.section-title.flex-row.flex-center', [ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { onClick: (event) => { - state.dispatch(actions.showAccountsPage()) + state.dispatch(actions.goHome()) } }), h('h2.page-subtitle', 'Info'), diff --git a/ui/app/loading.js b/ui/app/loading.js index 47b758cb6..9288256de 100644 --- a/ui/app/loading.js +++ b/ui/app/loading.js @@ -19,7 +19,6 @@ function LoadingIndicator() { } LoadingIndicator.prototype.render = function() { - console.dir(this.props) var isLoading = this.props.isLoading return ( @@ -44,7 +43,6 @@ LoadingIndicator.prototype.render = function() { src: 'images/loading.svg', }), ]) : null, - ]) ) } diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 582583185..d3d5ad638 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -1,14 +1,18 @@ const extend = require('xtend') const actions = require('../actions') +const valuesFor = require('../util').valuesFor module.exports = reduceApp function reduceApp(state, action) { // clone and defaults + var accounts = valuesFor(state.metamask.accounts) + var account = accounts.length ? valuesFor(state.metamask.accounts)[0].address : null var defaultView = { - name: 'accounts', + name: 'accountDetail', detailView: null, + context: account, } // confirm seed words @@ -56,6 +60,7 @@ function reduceApp(state, action) { return extend(appState, { currentView: { name: 'config', + context: appState.currentView.context, }, transForward: true, }) @@ -64,6 +69,7 @@ function reduceApp(state, action) { return extend(appState, { currentView: { name: 'info', + context: appState.currentView.context, }, transForward: true, }) @@ -120,11 +126,28 @@ function reduceApp(state, action) { activeAddress: action.value, }) + case actions.GO_HOME: + return extend(appState, { + currentView: { + name: 'accountDetail', + context: appState.currentView.context, + }, + accountDetail: { + accountExport: 'none', + privateKey: '', + }, + transForward: false, + }) + + case actions.SHOW_ACCOUNT_DETAIL: + var account = action.value || valuesFor(state.metamask.accounts)[0].address + return extend(appState, { + isLoading: account ? false : true, currentView: { name: 'accountDetail', - context: action.value, + context: account, }, accountDetail: { accountExport: 'none', From 44c68eb23c8fe510438fa44c26a5acd99bbe19e8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Apr 2016 13:41:06 -0700 Subject: [PATCH 2/6] Fix test --- ui/app/reducers/app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index d3d5ad638..46bc3c36d 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -141,8 +141,6 @@ function reduceApp(state, action) { case actions.SHOW_ACCOUNT_DETAIL: - var account = action.value || valuesFor(state.metamask.accounts)[0].address - return extend(appState, { isLoading: account ? false : true, currentView: { From 4c46cbc99cbed0de560b3c2ba8fe502c1c5343a8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Apr 2016 13:49:46 -0700 Subject: [PATCH 3/6] Fixed some loading bugs --- ui/app/reducers/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 46bc3c36d..131b434e9 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -139,13 +139,11 @@ function reduceApp(state, action) { transForward: false, }) - case actions.SHOW_ACCOUNT_DETAIL: return extend(appState, { - isLoading: account ? false : true, currentView: { name: 'accountDetail', - context: account, + context: action.value || account, }, accountDetail: { accountExport: 'none', From 88ed546a9ae9a04f9ba09fe1d24910fe6c88292f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Apr 2016 13:49:53 -0700 Subject: [PATCH 4/6] Bump changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 103dc45d7..7b699b10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Master +- Account detail view is now the primary view. +- The account detail view now has a "Change acct" button which shows the account list. +- Clicking accounts in the account list now both selects that account and displays that account's detail view. + # 1.6.0 2016-04-22 - Pending transactions are now persisted to localStorage and resume even after browser is closed. From 1025eb3b4f90c2b909fe9d238cebba878c8ce2db Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Apr 2016 14:14:34 -0700 Subject: [PATCH 5/6] Persist selected account When selecting an account, we now persist the selection to the `configManager`, so the selection can be restored when re-unlocking Metamask. Also found the bug where `rawtestrpc` was still being used as a default, and fixed it! --- app/scripts/lib/config-manager.js | 11 +++++++++++ app/scripts/lib/idStore.js | 17 +++++++---------- ui/app/actions.js | 18 +++++++++++------- ui/app/components/account-panel.js | 6 ++---- ui/app/reducers/app.js | 4 +--- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index c79dc7a8f..102327c2d 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -102,6 +102,17 @@ ConfigManager.prototype.setWallet = function(wallet) { this.setData(data) } +ConfigManager.prototype.getSelectedAccount = function() { + var config = this.getConfig() + return config.selectedAccount +} + +ConfigManager.prototype.setSelectedAccount = function(address) { + var config = this.getConfig() + config.selectedAccount = address + this.setConfig(config) +} + ConfigManager.prototype.getWallet = function() { return this.migrator.getData().wallet } diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 525fdae30..92d0f9668 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -9,7 +9,7 @@ const extend = require('xtend') const createId = require('web3-provider-engine/util/random-id') const autoFaucet = require('./auto-faucet') const configManager = require('./config-manager-singleton') -const DEFAULT_RPC = 'https://rawtestrpc.metamask.io/' +const DEFAULT_RPC = 'https://testrpc.metamask.io/' module.exports = IdentityStore @@ -72,8 +72,7 @@ IdentityStore.prototype.setStore = function(store){ IdentityStore.prototype.clearSeedWordCache = function(cb) { configManager.setShowSeedWords(false) - var accounts = this._loadIdentities() - cb(null, accounts) + cb(null, configManager.getSelectedAccount()) } IdentityStore.prototype.getState = function(){ @@ -85,6 +84,7 @@ IdentityStore.prototype.getState = function(){ seedWords: seedWords, unconfTxs: configManager.unconfirmedTxs(), transactions: configManager.getTxList(), + selectedAddress: configManager.getSelectedAccount(), })) } @@ -97,7 +97,7 @@ IdentityStore.prototype.getSeedIfUnlocked = function() { } IdentityStore.prototype.getSelectedAddress = function(){ - return this._currentState.selectedAddress + return configManager.getSelectedAccount() } IdentityStore.prototype.setSelectedAddress = function(address){ @@ -106,7 +106,7 @@ IdentityStore.prototype.setSelectedAddress = function(address){ address = addresses[0] } - this._currentState.selectedAddress = address + configManager.setSelectedAccount(address) this._didUpdate() } @@ -120,8 +120,8 @@ IdentityStore.prototype.submitPassword = function(password, cb){ this._tryPassword(password, (err) => { if (err) return cb(err) // load identities before returning... - var accounts = this._loadIdentities() - cb(null, accounts) + this._loadIdentities() + cb(null, configManager.getSelectedAccount()) }) } @@ -213,7 +213,6 @@ IdentityStore.prototype._loadIdentities = function(){ if (!this._isUnlocked()) throw new Error('not unlocked') var addresses = this._getAddresses() - var accountArray = [] addresses.forEach((address, i) => { // // add to ethStore this._ethStore.addAccount(address) @@ -225,10 +224,8 @@ IdentityStore.prototype._loadIdentities = function(){ mayBeFauceting: this._mayBeFauceting(i), } this._currentState.identities[address] = identity - accountArray.push(identity) }) this._didUpdate() - return accountArray } // mayBeFauceting diff --git a/ui/app/actions.js b/ui/app/actions.js index e2d81883f..a2106ea85 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -105,13 +105,13 @@ function goHome() { function tryUnlockMetamask(password) { return (dispatch) => { dispatch(this.unlockInProgress()) - _accountManager.submitPassword(password, (err, accounts) => { + _accountManager.submitPassword(password, (err, selectedAccount) => { dispatch(this.hideLoadingIndication()) if (err) { dispatch(this.unlockFailed()) } else { dispatch(this.unlockMetamask()) - dispatch(this.showAccountDetail(accounts[0].address)) + dispatch(this.showAccountDetail(selectedAccount)) } }) } @@ -130,7 +130,7 @@ function recoverFromSeed(password, seed) { return (dispatch) => { // dispatch(this.createNewVaultInProgress()) dispatch(this.showLoadingIndication()) - _accountManager.recoverFromSeed(password, seed, (err, accounts) => { + _accountManager.recoverFromSeed(password, seed, (err, selectedAccount) => { if (err) { dispatch(this.hideLoadingIndication()) var message = err.message @@ -138,7 +138,7 @@ function recoverFromSeed(password, seed) { } dispatch(this.unlockMetamask()) - dispatch(this.showAccountDetail(accounts[0].address)) + dispatch(this.showAccountDetail(selectedAccount)) dispatch(this.hideLoadingIndication()) }) } @@ -281,9 +281,13 @@ function lockMetamask() { } function showAccountDetail(address) { - return { - type: this.SHOW_ACCOUNT_DETAIL, - value: address, + return (dispatch) => { + _accountManager.setSelectedAddress(address) + + dispatch({ + type: this.SHOW_ACCOUNT_DETAIL, + value: address, + }) } } diff --git a/ui/app/components/account-panel.js b/ui/app/components/account-panel.js index 9de29cd91..9fda2ebfc 100644 --- a/ui/app/components/account-panel.js +++ b/ui/app/components/account-panel.js @@ -25,7 +25,7 @@ AccountPanel.prototype.render = function() { style: { flex: '1 0 auto', }, - onClick: state.onShowDetail && state.onShowDetail.bind(null, identity.address), + onClick: (event) => state.onShowDetail(identity.address, event), }, [ // account identicon @@ -53,9 +53,7 @@ AccountPanel.prototype.render = function() { // navigate to account detail !state.onShowDetail ? null : - h('.arrow-right.cursor-pointer', { - onClick: state.onShowDetail && state.onShowDetail.bind(null, identity.address), - }, [ + h('.arrow-right.cursor-pointer', [ h('i.fa.fa-chevron-right.fa-lg'), ]), ]) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 131b434e9..f522e6042 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -7,12 +7,10 @@ module.exports = reduceApp function reduceApp(state, action) { // clone and defaults - var accounts = valuesFor(state.metamask.accounts) - var account = accounts.length ? valuesFor(state.metamask.accounts)[0].address : null var defaultView = { name: 'accountDetail', detailView: null, - context: account, + context: state.metamask.selectedAccount, } // confirm seed words From 82ac8bde5d246b58e90a7e428ee685cf1ee7c351 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 25 Apr 2016 14:16:07 -0700 Subject: [PATCH 6/6] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b699b10e..047865f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Account detail view is now the primary view. - The account detail view now has a "Change acct" button which shows the account list. - Clicking accounts in the account list now both selects that account and displays that account's detail view. +- Selected account is now persisted between sessions, so the current account stays selected. # 1.6.0 2016-04-22