diff --git a/ui/responsive/app/accounts/account-list-item.js b/ui/responsive/app/accounts/account-list-item.js deleted file mode 100644 index 10a0b6cc7..000000000 --- a/ui/responsive/app/accounts/account-list-item.js +++ /dev/null @@ -1,91 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const ethUtil = require('ethereumjs-util') - -const EthBalance = require('../components/eth-balance') -const CopyButton = require('../components/copyButton') -const Identicon = require('../components/identicon') - -module.exports = AccountListItem - -inherits(AccountListItem, Component) -function AccountListItem () { - Component.call(this) -} - -AccountListItem.prototype.render = function () { - const { identity, selectedAddress, accounts, onShowDetail, - conversionRate, currentCurrency } = this.props - - const checksumAddress = identity && identity.address && ethUtil.toChecksumAddress(identity.address) - const isSelected = selectedAddress === identity.address - const account = accounts[identity.address] - const selectedClass = isSelected ? '.selected' : '' - - return ( - h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, { - key: `account-panel-${identity.address}`, - onClick: (event) => onShowDetail(identity.address, event), - }, [ - - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - this.pendingOrNot(), - this.indicateIfLoose(), - h(Identicon, { - address: identity.address, - imageify: true, - }), - ]), - - // account address, balance - h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', { - style: { - width: '200px', - }, - }, [ - h('span', identity.name), - h('span.font-small', { - style: { - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - }, checksumAddress), - h(EthBalance, { - value: account && account.balance, - currentCurrency, - conversionRate, - style: { - lineHeight: '7px', - marginTop: '10px', - }, - }), - ]), - - // copy button - h('.identity-copy.flex-column', { - style: { - margin: '0 20px', - }, - }, [ - h(CopyButton, { - value: checksumAddress, - }), - ]), - ]) - ) -} - -AccountListItem.prototype.indicateIfLoose = function () { - try { // Sometimes keyrings aren't loaded yet: - const type = this.props.keyring.type - const isLoose = type !== 'HD Key Tree' - return isLoose ? h('.keyring-label', 'LOOSE') : null - } catch (e) { return } -} - -AccountListItem.prototype.pendingOrNot = function () { - const pending = this.props.pending - if (pending.length === 0) return null - return h('.pending-dot', pending.length) -} diff --git a/ui/responsive/app/accounts/index.js b/ui/responsive/app/accounts/index.js deleted file mode 100644 index 3e0830b63..000000000 --- a/ui/responsive/app/accounts/index.js +++ /dev/null @@ -1,163 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../actions') -const valuesFor = require('../util').valuesFor -const findDOMNode = require('react-dom').findDOMNode -const AccountListItem = require('./account-list-item') - -module.exports = connect(mapStateToProps)(AccountsScreen) - -function mapStateToProps (state) { - const pendingTxs = valuesFor(state.metamask.unapprovedTxs) - .filter(txMeta => txMeta.metamaskNetworkId === state.metamask.network) - const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs) - const pending = pendingTxs.concat(pendingMsgs) - - return { - accounts: state.metamask.accounts, - identities: state.metamask.identities, - unapprovedTxs: state.metamask.unapprovedTxs, - selectedAddress: state.metamask.selectedAddress, - scrollToBottom: state.appState.scrollToBottom, - pending, - keyrings: state.metamask.keyrings, - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - } -} - -inherits(AccountsScreen, Component) -function AccountsScreen () { - Component.call(this) -} - -AccountsScreen.prototype.render = function () { - const props = this.props - const { keyrings, conversionRate, currentCurrency } = props - const identityList = valuesFor(props.identities) - const unapprovedTxList = valuesFor(props.unapprovedTxs) - - return ( - - h('.accounts-section.flex-grow', [ - - // subtitle and nav - h('.section-title.flex-center', [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: this.goHome.bind(this), - }), - h('h2.page-subtitle', 'Select Account'), - ]), - - h('hr.horizontal-line'), - - // identity selection - h('section.identity-section', { - style: { - overflowY: 'auto', - overflowX: 'hidden', - }, - }, - [ - identityList.map((identity) => { - const pending = this.props.pending.filter((txOrMsg) => { - if ('txParams' in txOrMsg) { - return txOrMsg.txParams.from === identity.address - } else if ('msgParams' in txOrMsg) { - return txOrMsg.msgParams.from === identity.address - } else { - return false - } - }) - - const simpleAddress = identity.address.substring(2).toLowerCase() - const keyring = keyrings.find((kr) => { - return kr.accounts.includes(simpleAddress) || - kr.accounts.includes(identity.address) - }) - - return h(AccountListItem, { - key: `acct-panel-${identity.address}`, - identity, - selectedAddress: this.props.selectedAddress, - conversionRate, - currentCurrency, - accounts: this.props.accounts, - onShowDetail: this.onShowDetail.bind(this), - pending, - keyring, - }) - }), - - h('hr.horizontal-line'), - h('div.footer.hover-white.pointer', { - key: 'reveal-account-bar', - onClick: () => { - this.addNewAccount() - }, - style: { - display: 'flex', - height: '40px', - padding: '10px', - justifyContent: 'center', - alignItems: 'center', - }, - }, [ - h('i.fa.fa-plus.fa-lg', {key: ''}), - ]), - h('hr.horizontal-line'), - ]), - - unapprovedTxList.length ? ( - - h('.unconftx-link.flex-row.flex-center', { - onClick: this.navigateToConfTx.bind(this), - }, [ - h('span', 'Unconfirmed Txs'), - h('i.fa.fa-arrow-right.fa-lg'), - ]) - - ) : ( - null - ), - ]) - ) -} - -// If a new account was revealed, scroll to the bottom -AccountsScreen.prototype.componentDidUpdate = function () { - const scrollToBottom = this.props.scrollToBottom - - if (scrollToBottom) { - var container = findDOMNode(this) - var scrollable = container.querySelector('.identity-section') - scrollable.scrollTop = scrollable.scrollHeight - } -} - -AccountsScreen.prototype.navigateToConfTx = function () { - event.stopPropagation() - this.props.dispatch(actions.showConfTxPage()) -} - -AccountsScreen.prototype.onShowDetail = function (address, event) { - event.stopPropagation() - this.props.dispatch(actions.showAccountDetail(address)) -} - -AccountsScreen.prototype.addNewAccount = function () { - this.props.dispatch(actions.addNewAccount(0)) -} - -/* An optional view proposed in this design: - * https://consensys.quip.com/zZVrAysM5znY -AccountsScreen.prototype.addNewAccount = function () { - this.props.dispatch(actions.navigateToNewAccountScreen()) -} -*/ - -AccountsScreen.prototype.goHome = function () { - this.props.dispatch(actions.goHome()) -} diff --git a/ui/responsive/app/components/account-dropdowns.js b/ui/responsive/app/components/account-dropdowns.js new file mode 100644 index 000000000..cbb97b2cb --- /dev/null +++ b/ui/responsive/app/components/account-dropdowns.js @@ -0,0 +1,217 @@ +const Component = require('react').Component +const PropTypes = require('react').PropTypes +const h = require('react-hyperscript') +const actions = require('../actions') +const genAccountLink = require('../../lib/account-link.js') +const connect = require('react-redux').connect +const Dropdown = require('./dropdown').Dropdown +const DropdownMenuItem = require('./dropdown').DropdownMenuItem +const Identicon = require('./identicon') +const ethUtil = require('ethereumjs-util') +const copyToClipboard = require('copy-to-clipboard') + +class AccountDropdowns extends Component { + constructor (props) { + super(props) + this.state = { + overflowMenuActive: false, + switchingMenuActive: false, + } + } + + getAccounts () { + const { identities, selected } = this.props + + return Object.keys(identities).map((key) => { + const identity = identities[key] + const isSelected = identity.address === selected + + return h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + this.props.actions.showAccountDetail(identity.address) + }, + }, + [ + h( + Identicon, + { + address: identity.address, + diameter: 16, + }, + ), + h('span', { style: { marginLeft: '10px' } }, identity.name || ''), + h('span', { style: { marginLeft: '10px' } }, isSelected ? h('.check', '✓') : null), + ] + ) + }) + } + + render () { + const { style, actions } = this.props + const { switchingMenuActive, overflowMenuActive } = this.state + + return h( + 'span', + { + style: style, + }, + [ + h( + 'i.fa.fa-angle-down', + { + style: {}, + onClick: (event) => { + event.stopPropagation() + this.setState({ + switchingMenuActive: !switchingMenuActive, + overflowMenuActive: false, + }) + }, + }, + [ + h( + Dropdown, + { + style: { + marginLeft: '-140px', + minWidth: '180px', + }, + isOpen: switchingMenuActive, + onClickOutside: () => { this.setState({ switchingMenuActive: false}) }, + }, + [ + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => actions.showConfigPage(), + }, + 'Account Settings', + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + const { selected, network } = this.props + const url = genAccountLink(selected, network) + global.platform.openWindow({ url }) + }, + }, + 'View account on Etherscan', + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + const { selected } = this.props + const checkSumAddress = selected && ethUtil.toChecksumAddress(selected) + copyToClipboard(checkSumAddress) + }, + }, + 'Copy Address to clipboard', + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + actions.requestAccountExport() + }, + }, + 'Export Private Key', + ), + ] + ), + ], + ), + h( + 'i.fa.fa-ellipsis-h', + { + style: { 'marginLeft': '10px'}, + onClick: (event) => { + event.stopPropagation() + this.setState({ + overflowMenuActive: !overflowMenuActive, + switchingMenuActive: false, + }) + }, + }, + [ + h( + Dropdown, + { + style: { + marginLeft: '-155px', + minWidth: '180px', + }, + isOpen: overflowMenuActive, + onClickOutside: () => { this.setState({ overflowMenuActive: false}) }, + }, + [ + ...this.getAccounts(), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => actions.addNewAccount(), + }, + [ + h( + Identicon, + { + diameter: 16, + }, + ), + h('span', { style: { marginLeft: '10px' } }, 'Create Account'), + ], + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => actions.showImportPage(), + }, + [ + h( + Identicon, + { + diameter: 16, + }, + ), + h('span', { style: { marginLeft: '10px' } }, 'Import Account'), + ] + ), + ] + ), + ] + ), + ] + ) + } +} + +AccountDropdowns.propTypes = { + identities: PropTypes.objectOf(PropTypes.object), + selected: PropTypes.string, +} + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + actions: { + showConfigPage: () => dispatch(actions.showConfigPage()), + requestAccountExport: () => dispatch(actions.requestExportAccount()), + showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)), + addNewAccount: () => dispatch(actions.addNewAccount()), + showImportPage: () => dispatch(actions.showImportPage()), + }, + } +} + +module.exports = { + AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns), +} diff --git a/ui/responsive/app/components/dropdown.js b/ui/responsive/app/components/dropdown.js index 6e09cd133..e77b4c40c 100644 --- a/ui/responsive/app/components/dropdown.js +++ b/ui/responsive/app/components/dropdown.js @@ -1,11 +1,13 @@ -const Component = require('react').Component; -const PropTypes = require('react').PropTypes; -const h = require('react-hyperscript'); -const MenuDroppo = require('menu-droppo'); +const Component = require('react').Component +const PropTypes = require('react').PropTypes +const h = require('react-hyperscript') +const MenuDroppo = require('menu-droppo') + +const noop = () => {} class Dropdown extends Component { - render() { - const { isOpen, onClickOutside, style, children } = this.props; + render () { + const { isOpen, onClickOutside, style, children } = this.props return h( MenuDroppo, @@ -30,27 +32,34 @@ class Dropdown extends Component { ` ), ...children, - ], - ); + ] + ) } } +Dropdown.defaultProps = { + isOpen: false, + onClick: noop, +} + Dropdown.propTypes = { - isOpen: PropTypes.func.isRequired, + isOpen: PropTypes.bool.isRequired, onClick: PropTypes.func.isRequired, children: PropTypes.node, - style: PropTypes.object.isRequired, + style: PropTypes.object.isRequired, } class DropdownMenuItem extends Component { - render() { - const { onClick, closeMenu, children } = this.props; + render () { + const { onClick, closeMenu, children } = this.props return h( 'li.dropdown-menu-item', { - onClick, - closeMenu, + onClick: () => { + onClick() + closeMenu() + }, style: { listStyle: 'none', padding: '8px 0px 8px 0px', @@ -60,10 +69,11 @@ class DropdownMenuItem extends Component { cursor: 'pointer', display: 'flex', justifyContent: 'flex-start', + alignItems: 'center', }, }, children - ); + ) } } @@ -71,9 +81,9 @@ DropdownMenuItem.propTypes = { closeMenu: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, children: PropTypes.node, -}; +} module.exports = { Dropdown, DropdownMenuItem, -}; \ No newline at end of file +}