diff --git a/ui/app/actions.js b/ui/app/actions.js index 9744bf67f..7ac0acf05 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -201,6 +201,9 @@ var actions = { callBackgroundThenUpdate, forceUpdateMetamaskState, + + TOGGLE_ACCOUNT_MENU: 'TOGGLE_ACCOUNT_MENU', + toggleAccountMenu, } module.exports = actions @@ -1303,3 +1306,9 @@ function forceUpdateMetamaskState (dispatch) { dispatch(actions.updateMetamaskState(newState)) }) } + +function toggleAccountMenu () { + return { + type: actions.TOGGLE_ACCOUNT_MENU, + } +} diff --git a/ui/app/app.js b/ui/app/app.js index 08d24d86c..3f27b36c7 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -91,6 +91,7 @@ function mapDispatchToProps (dispatch, ownProps) { showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), + toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), } } @@ -256,10 +257,12 @@ App.prototype.renderAppBar = function () { ]), - h(Identicon, { - address: this.props.selectedAddress, - diameter: 32, - }), + h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [ + h(Identicon, { + address: this.props.selectedAddress, + diameter: 32, + }), + ]), ]), ]), ]), diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 7dc3d10a5..3b1118271 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -3,7 +3,9 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('../../actions') -const { Menu, Item, Divider } = require('../dropdowns/components/menu') +const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu') +const Identicon = require('../identicon') +const { formatBalance } = require('../../util') module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountMenu) @@ -13,21 +15,33 @@ function AccountMenu () { Component.call(this) } function mapStateToProps (state) { return { selectedAddress: state.metamask.selectedAddress, + isAccountMenuOpen: state.metamask.isAccountMenuOpen, + keyrings: state.metamask.keyrings, + identities: state.metamask.identities, + accounts: state.metamask.accounts, + } } -function mapDispatchToProps () { - return {} +// identities, accounts, selected, menuItemStyles, actions, keyrings + +function mapDispatchToProps (dispatch) { + return { + toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), + } } AccountMenu.prototype.render = function () { - return h(Menu, { className: 'account-menu' }, [ + const { isAccountMenuOpen, toggleAccountMenu } = this.props + + return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [ + h(CloseArea, { onClick: toggleAccountMenu }), h(Item, { className: 'account-menu__header' }, [ 'My Accounts', h('button.account-menu__logout-button', 'Log out'), ]), h(Divider), - h(Item, { text: 'hi' }), + h('div.account-menu__accounts', this.renderAccounts()), h(Divider), h(Item, { onClick: true, @@ -53,4 +67,90 @@ AccountMenu.prototype.render = function () { ]) } +AccountMenu.prototype.renderAccounts = function () { + const { identities, accounts, selected, actions, keyrings } = this.props + + return Object.keys(identities).map((key, index) => { + const identity = identities[key] + const isSelected = identity.address === selected + + const balanceValue = accounts[key].balance + const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' + const simpleAddress = identity.address.substring(2).toLowerCase() + + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) + }) + + return h( + 'div.account-menu__account', + { + onClick: () => { + this.props.actions.showAccountDetail(identity.address) + }, + }, + [ + h('div.account-menu__check-mark', [ + isSelected ? h('i.fa.fa-check') : null, + ]), + h( + Identicon, + { + address: identity.address, + diameter: 24, + }, + ), + + h('div.account-menu__account-info', [ + + this.indicateIfLoose(keyring), + + h('div.account-menu__name', { + style: { + fontSize: '18px', + maxWidth: '145px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + }, identity.name || ''), + + h('div.account-menu__balance', formattedBalance), + ]), + + h('div.account-menu__action', { + onClick: () => { + actions.showEditAccountModal(identity) + }, + }, 'Edit'), + +// ======= +// }, +// ), +// this.indicateIfLoose(keyring), +// h('span', { +// style: { +// marginLeft: '20px', +// fontSize: '24px', +// maxWidth: '145px', +// whiteSpace: 'nowrap', +// overflow: 'hidden', +// textOverflow: 'ellipsis', +// }, +// }, identity.name || ''), +// h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), +// >>>>>>> master:ui/app/components/account-dropdowns.js + ], + ) + }) +} + +AccountMenu.prototype.indicateIfLoose = function (keyring) { + try { // Sometimes keyrings aren't loaded yet: + const type = keyring.type + const isLoose = type !== 'HD Key Tree' + return isLoose ? h('.keyring-label', 'LOOSE') : null + } catch (e) { return } +} diff --git a/ui/app/components/dropdowns/components/menu.js b/ui/app/components/dropdowns/components/menu.js index 0cbe0f342..323103f0b 100644 --- a/ui/app/components/dropdowns/components/menu.js +++ b/ui/app/components/dropdowns/components/menu.js @@ -41,4 +41,11 @@ Divider.prototype.render = function () { return h('div.menu__divider') } -module.exports = { Menu, Item, Divider } +inherits(CloseArea, Component) +function CloseArea () { Component.call(this) } + +CloseArea.prototype.render = function () { + return h('div.menu__close-area', { onClick: this.props.onClick }) +} + +module.exports = { Menu, Item, Divider, CloseArea } diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 54d90b7ac..f06c4d512 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -136,7 +136,6 @@ WalletView.prototype.render = function () { selected: selectedAddress, network, identities, - enableAccountsSelector: true, }, []), ]), diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index 8b08c02fd..5ed42f627 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -20,6 +20,10 @@ right: calc((100vw - 65vw) / 2); } + &__icon { + cursor: pointer; + } + &__header { display: flex; flex-flow: row nowrap; @@ -41,4 +45,13 @@ width: 16px; height: 16px; } + + &__accounts { + display: flex; + flex-flow: column nowrap; + overflow-y: auto; + height: 272px; + position: relative; + z-index: 200; + } } diff --git a/ui/app/css/itcss/components/menu.scss b/ui/app/css/itcss/components/menu.scss index d01c24a70..0f83146a8 100644 --- a/ui/app/css/itcss/components/menu.scss +++ b/ui/app/css/itcss/components/menu.scss @@ -10,6 +10,8 @@ display: flex; flex-flow: row nowrap; align-items: center; + position: relative; + z-index: 200; &--clickable { cursor: pointer; @@ -40,4 +42,13 @@ width: 100%; height: 1px; } + + &__close-area { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 100; + } } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index cc24a6729..a9a54e91e 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -10,6 +10,7 @@ function reduceMetamask (state, action) { var metamaskState = extend({ isInitialized: false, isUnlocked: false, + isAccountMenuOpen: false, rpcTarget: 'https://rawtestrpc.metamask.io/', identities: {}, unapprovedTxs: {}, @@ -172,6 +173,11 @@ function reduceMetamask (state, action) { }, }) + case actions.TOGGLE_ACCOUNT_MENU: + return extend(metamaskState, { + isAccountMenuOpen: !metamaskState.isAccountMenuOpen, + }) + default: return metamaskState