Added ability to nickname wallets locally

The changes are persisted to localstorage, so they cannot be restored on a new computer, but for right now it's a nice organizational feature.
This commit is contained in:
Dan Finlay 2016-05-20 16:18:54 -07:00
parent 24fc5f9ea3
commit 95a3cfe3fc
9 changed files with 192 additions and 16 deletions

View File

@ -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){

View File

@ -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

View File

@ -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.

View File

@ -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)
});
});

View File

@ -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'

View File

@ -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', {

View File

@ -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,

View File

@ -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 })
}

View File

@ -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