mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Merge pull request #201 from MetaMask/AccountCrud
Add ability to nickname accounts
This commit is contained in:
commit
27790b38a9
@ -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
|
||||
|
||||
|
@ -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){
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
36
test/unit/actions/save_account_label_test.js
Normal file
36
test/unit/actions/save_account_label_test.js
Normal 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)
|
||||
});
|
||||
});
|
||||
|
@ -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'
|
||||
|
@ -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', {
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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,
|
||||
|
52
ui/app/components/editable-label.js
Normal file
52
ui/app/components/editable-label.js
Normal 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 })
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user