diff options
author | kumavis <kumavis@users.noreply.github.com> | 2016-05-21 07:54:00 +0800 |
---|---|---|
committer | kumavis <kumavis@users.noreply.github.com> | 2016-05-21 07:54:00 +0800 |
commit | 27790b38a95e219b7663852150b82387cedb58e2 (patch) | |
tree | 4c71d9f923b5119f12bea5865c921789303699cf | |
parent | da6e965b66434f4ca0151c4e40ba88a3865814ef (diff) | |
parent | 8efd30ad7af4350108dc418571e16e74aa069375 (diff) | |
download | tangerine-wallet-browser-27790b38a95e219b7663852150b82387cedb58e2.tar tangerine-wallet-browser-27790b38a95e219b7663852150b82387cedb58e2.tar.gz tangerine-wallet-browser-27790b38a95e219b7663852150b82387cedb58e2.tar.bz2 tangerine-wallet-browser-27790b38a95e219b7663852150b82387cedb58e2.tar.lz tangerine-wallet-browser-27790b38a95e219b7663852150b82387cedb58e2.tar.xz tangerine-wallet-browser-27790b38a95e219b7663852150b82387cedb58e2.tar.zst tangerine-wallet-browser-27790b38a95e219b7663852150b82387cedb58e2.zip |
Merge pull request #201 from MetaMask/AccountCrud
Add ability to nickname accounts
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | app/scripts/background.js | 1 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 20 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 12 | ||||
-rw-r--r-- | test/unit/actions/save_account_label_test.js | 36 | ||||
-rw-r--r-- | test/unit/config-manager-test.js | 21 | ||||
-rw-r--r-- | ui/app/account-detail.js | 40 | ||||
-rw-r--r-- | ui/app/accounts.js | 13 | ||||
-rw-r--r-- | ui/app/actions.js | 18 | ||||
-rw-r--r-- | ui/app/components/editable-label.js | 52 | ||||
-rw-r--r-- | ui/app/css/index.css | 14 | ||||
-rw-r--r-- | ui/app/css/lib.css | 8 | ||||
-rw-r--r-- | ui/app/reducers/metamask.js | 8 |
13 files changed, 211 insertions, 33 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 955917cf2..6fc1f17aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Added support for capitalization-based address checksums. - Send value is no longer limited by javascript number precision, and is always in ETH. - Added ability to generate new accounts. +- Added ability to locally nickname accounts. ## 1.8.4 2016-05-13 diff --git a/app/scripts/background.js b/app/scripts/background.js index e77df1519..f79047db4 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -183,6 +183,7 @@ function setupControllerConnection(stream){ clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore), exportAccount: idStore.exportAccount.bind(idStore), revealAccount: idStore.revealAccount.bind(idStore), + saveAccountLabel: idStore.saveAccountLabel.bind(idStore), }) stream.pipe(dnode).pipe(stream) dnode.on('remote', function(remote){ diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 7b2f2f1f8..f5e1cf38d 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -230,6 +230,26 @@ ConfigManager.prototype.updateTx = function(tx) { this._saveTxList(transactions) } +// wallet nickname methods + +ConfigManager.prototype.getWalletNicknames = function() { + var data = this.getData() + let nicknames = ('walletNicknames' in data) ? data.walletNicknames : {} + return nicknames +} + +ConfigManager.prototype.nicknameForWallet = function(account) { + let nicknames = this.getWalletNicknames() + return nicknames[account] +} + +ConfigManager.prototype.setNicknameForWallet = function(account, nickname) { + let nicknames = this.getWalletNicknames() + nicknames[account] = nickname + var data = this.getData() + data.walletNicknames = nicknames + this.setData(data) +} // observable diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 0604c4bca..9d2552e8b 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -325,9 +325,10 @@ IdentityStore.prototype._loadIdentities = function(){ // // add to ethStore this._ethStore.addAccount(address) // add to identities + const defaultLabel = 'Wallet ' + (i+1) + const nickname = configManager.nicknameForWallet(address) var identity = { - name: 'Wallet ' + (i+1), - img: 'QmW6hcwYzXrNkuHrpvo58YeZvbZxUddv69ATSHY3BHpPdd', + name: nickname || defaultLabel, address: address, mayBeFauceting: this._mayBeFauceting(i), } @@ -336,6 +337,13 @@ IdentityStore.prototype._loadIdentities = function(){ this._didUpdate() } +IdentityStore.prototype.saveAccountLabel = function(account, label, cb) { + configManager.setNicknameForWallet(account, label) + this._loadIdentities() + cb(null, label) + this._didUpdate() +} + // mayBeFauceting // If on testnet, index 0 may be fauceting. // The UI will have to check the balance to know. diff --git a/test/unit/actions/save_account_label_test.js b/test/unit/actions/save_account_label_test.js new file mode 100644 index 000000000..1df428b1d --- /dev/null +++ b/test/unit/actions/save_account_label_test.js @@ -0,0 +1,36 @@ +var jsdom = require('mocha-jsdom') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') + +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('SAVE_ACCOUNT_LABEL', function() { + + it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function() { + var initialState = { + metamask: { + identities: { + foo: { + name: 'bar' + } + }, + } + } + freeze(initialState) + + const action = { + type: actions.SAVE_ACCOUNT_LABEL, + value: { + account: 'foo', + label: 'baz' + }, + } + freeze(action) + + var resultingState = reducers(initialState, action) + assert.equal(resultingState.metamask.identities.foo.name, action.value.label) + }); +}); + diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index e414ecb9e..aa94dc385 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -54,6 +54,27 @@ describe('config-manager', function() { }) }) + describe('wallet nicknames', function() { + it('should return null when no nicknames are saved', function() { + var nick = configManager.nicknameForWallet('0x0') + assert.equal(nick, null, 'no nickname returned') + }) + + it('should persist nicknames', function() { + var account = '0x0' + var nick1 = 'foo' + var nick2 = 'bar' + configManager.setNicknameForWallet(account, nick1) + + var result1 = configManager.nicknameForWallet(account) + assert.equal(result1, nick1) + + configManager.setNicknameForWallet(account, nick2) + var result2 = configManager.nicknameForWallet(account) + assert.equal(result2, nick2) + }) + }) + describe('rpc manipulations', function() { it('changing rpc should return a different rpc', function() { var firstRpc = 'first' diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index c708580c4..bae44ec85 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -8,12 +8,12 @@ const actions = require('./actions') const addressSummary = require('./util').addressSummary const ReactCSSTransitionGroup = require('react-addons-css-transition-group') -const AccountPanel = require('./components/account-panel') const Identicon = require('./components/identicon') const EtherBalance = require('./components/eth-balance') const transactionList = require('./components/transaction-list') const ExportAccountView = require('./components/account-export') const ethUtil = require('ethereumjs-util') +const EditableLabel = require('./components/editable-label') module.exports = connect(mapStateToProps)(AccountDetailScreen) @@ -34,12 +34,12 @@ function AccountDetailScreen() { } AccountDetailScreen.prototype.render = function() { - var state = this.props - var selected = state.address || Object.keys(state.accounts)[0] - var identity = state.identities[selected] - var account = state.accounts[selected] - var accountDetail = state.accountDetail - var transactions = state.transactions + var props = this.props + var selected = props.address || Object.keys(props.accounts)[0] + var identity = props.identities[selected] + var account = props.accounts[selected] + var accountDetail = props.accountDetail + var transactions = props.transactions return ( @@ -78,16 +78,28 @@ AccountDetailScreen.prototype.render = function() { h('i.fa.fa-users.fa-lg.cursor-pointer.color-orange', { onClick: this.navigateToAccounts.bind(this), }), - ]), - // account label - h('h2.font-medium.color-forest.flex-center', { + h('.flex-center', { style: { - paddingTop: 8, - marginBottom: 32, - }, - }, identity && identity.name), + height: '62px', + paddingTop: '8px', + } + }, [ + h(EditableLabel, { + textValue: identity ? identity.name : '', + state: { + isEditingLabel: false, + }, + saveText: (text) => { + props.dispatch(actions.saveAccountLabel(selected, text)) + }, + }, [ + + // What is shown when not editing: + h('h2.font-medium.color-forest', identity && identity.name) + ]), + ]), // address and getter actions h('.flex-row.flex-space-between', { diff --git a/ui/app/accounts.js b/ui/app/accounts.js index 45594f3c0..dbf4ee0fa 100644 --- a/ui/app/accounts.js +++ b/ui/app/accounts.js @@ -39,6 +39,7 @@ AccountsScreen.prototype.render = function() { onSelect: this.onSelect.bind(this), onShowDetail: this.onShowDetail.bind(this), revealAccount: this.onRevealAccount.bind(this), + goHome: this.goHome.bind(this), } return ( @@ -47,9 +48,7 @@ AccountsScreen.prototype.render = function() { // subtitle and nav h('.section-title.flex-center', [ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: (event) => { - state.dispatch(actions.goHome()) - } + onClick: actions.goHome, }), h('h2.page-subtitle', 'Select Account'), ]), @@ -112,13 +111,13 @@ AccountsScreen.prototype.render = function() { isSelected: false, isFauceting: isFauceting, }) + const selectedClass = isSelected ? '.selected' : '' return ( - h('.accounts-list-option.flex-row.flex-space-between.pointer.hover-white', { + h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, { key: `account-panel-${identity.address}`, style: { flex: '1 0 auto', - background: isSelected ? 'white' : 'none', }, onClick: (event) => actions.onShowDetail(identity.address, event), }, [ @@ -177,3 +176,7 @@ AccountsScreen.prototype.onShowDetail = function(address, event){ AccountsScreen.prototype.onRevealAccount = function() { this.props.dispatch(actions.revealAccount()) } + +AccountsScreen.prototype.goHome = function() { + this.props.dispatch(actions.goHome()) +} diff --git a/ui/app/actions.js b/ui/app/actions.js index 5d6f503e2..9ff05c460 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -59,6 +59,8 @@ var actions = { exportAccount: exportAccount, SHOW_PRIVATE_KEY: 'SHOW_PRIVATE_KEY', showPrivateKey: showPrivateKey, + SAVE_ACCOUNT_LABEL: 'SAVE_ACCOUNT_LABEL', + saveAccountLabel: saveAccountLabel, // tx conf screen COMPLETED_TX: 'COMPLETED_TX', TRANSACTION_ERROR: 'TRANSACTION_ERROR', @@ -481,6 +483,22 @@ function showPrivateKey(key) { } } +function saveAccountLabel(account, label) { + return (dispatch) => { + dispatch(this.showLoadingIndication()) + _accountManager.saveAccountLabel(account, label, (err) => { + dispatch(this.hideLoadingIndication()) + if (err) { + return dispatch(this.showWarning(err.message)) + } + dispatch({ + type: this.SAVE_ACCOUNT_LABEL, + value: { account, label }, + }) + }) + } +} + function showSendPage() { return { type: this.SHOW_SEND_PAGE, diff --git a/ui/app/components/editable-label.js b/ui/app/components/editable-label.js new file mode 100644 index 000000000..20e24a9c7 --- /dev/null +++ b/ui/app/components/editable-label.js @@ -0,0 +1,52 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const findDOMNode = require('react-dom').findDOMNode + +module.exports = EditableLabel + + +inherits(EditableLabel, Component) +function EditableLabel() { + Component.call(this) +} + +EditableLabel.prototype.render = function() { + const props = this.props + let state = this.state + + if (state && state.isEditingLabel) { + + return h('div.editable-label', [ + h('input', { + defaultValue: props.textValue, + onKeyPress:(event) => { + this.saveIfEnter(event) + }, + }), + h('button', { + onClick:() => this.saveText(), + }, 'Save') + ]) + + } else { + return h('div', { + onClick:(event) => { + this.setState({ isEditingLabel: true }) + }, + }, this.props.children) + } +} + +EditableLabel.prototype.saveIfEnter = function(event) { + if (event.key === 'Enter') { + this.saveText() + } +} + +EditableLabel.prototype.saveText = function() { + var container = findDOMNode(this) + var text = container.querySelector('.editable-label input').value + this.props.saveText(text) + this.setState({ isEditingLabel: false, textLabel: text }) +} diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 060ddce91..d6d1f91ac 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -274,10 +274,6 @@ input.large-input { height: 120px; } -.accounts-list-option:hover { - transform: scale(1.1); -} - .accounts-list-option .identicon-wrapper { width: 100px; } @@ -334,9 +330,6 @@ input.large-input { border-bottom: 1px solid #B1B1B1; cursor: pointer; } -.identity-section .identity-panel:hover { - background: #F9F9F9; -} .identity-section .identity-panel.selected { background: white; @@ -347,6 +340,11 @@ input.large-input { border-color: orange; } +.identity-section .accounts-list-option:hover, +.identity-section .accounts-list-option.selected { + background:white; +} + /* account detail screen */ .account-detail-section { @@ -393,4 +391,4 @@ input.large-input { .ether-balance-label { color: #ABA9AA; -}
\ No newline at end of file +} diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 97ff02c46..d9719b1e3 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -107,10 +107,6 @@ user-select: none; } -.hover-white:hover { - background: white; -} - .pointer { cursor: pointer; } @@ -166,3 +162,7 @@ hr.horizontal-line { margin: 1em 0; padding: 0; } + +.hover-white:hover { + background: white; +} diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 8628e84d2..a45327189 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -95,6 +95,14 @@ function reduceMetamask(state, action) { delete newState.seedWords return newState + case actions.SAVE_ACCOUNT_LABEL: + const account = action.value.account + const name = action.value.label + var id = {} + id[account] = extend(metamaskState.identities[account], { name }) + var identities = extend(metamaskState.identities, id) + return extend(metamaskState, { identities }) + default: return metamaskState |