Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scrolling button to account list #5992

Merged
merged 1 commit into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/images/icons/down-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ui/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading-screen')
const LoadingNetwork = require('./components/loading-network-screen').default
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu')
import AccountMenu from './components/account-menu'

// Global Modals
const Modal = require('./components/modals/index').Modal
Expand Down
301 changes: 301 additions & 0 deletions ui/app/components/account-menu/account-menu.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import debounce from 'lodash.debounce'
import { Menu, Item, Divider, CloseArea } from '../dropdowns/components/menu'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import Tooltip from '../tooltip'
import Identicon from '../identicon'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { PRIMARY } from '../../constants/common'
import {
SETTINGS_ROUTE,
INFO_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
CONNECT_HARDWARE_ROUTE,
DEFAULT_ROUTE,
} from '../../routes'

export default class AccountMenu extends PureComponent {
static contextTypes = {
t: PropTypes.func,
}

static propTypes = {
accounts: PropTypes.object,
history: PropTypes.object,
identities: PropTypes.object,
isAccountMenuOpen: PropTypes.bool,
keyrings: PropTypes.array,
lockMetamask: PropTypes.func,
selectedAddress: PropTypes.string,
showAccountDetail: PropTypes.func,
showRemoveAccountConfirmationModal: PropTypes.func,
toggleAccountMenu: PropTypes.func,
}

state = {
atAccountListBottom: false,
}

componentDidUpdate (prevProps) {
const { prevIsAccountMenuOpen } = prevProps
const { isAccountMenuOpen } = this.props

if (!prevIsAccountMenuOpen && isAccountMenuOpen) {
this.setAtAccountListBottom()
}
}

renderAccounts () {
const {
identities,
accounts,
selectedAddress,
keyrings,
showAccountDetail,
} = this.props

const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])

return accountOrder.filter(address => !!identities[address]).map(address => {
const identity = identities[address]
const isSelected = identity.address === selectedAddress

const balanceValue = accounts[address] ? accounts[address].balance : ''
const simpleAddress = identity.address.substring(2).toLowerCase()

const keyring = keyrings.find(kr => {
return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address)
})

return (
<div
className="account-menu__account menu__item--clickable"
onClick={() => showAccountDetail(identity.address)}
key={identity.address}
>
<div className="account-menu__check-mark">
{ isSelected && <div className="account-menu__check-mark-icon" /> }
</div>
<Identicon
address={identity.address}
diameter={24}
/>
<div className="account-menu__account-info">
<div className="account-menu__name">
{ identity.name || '' }
</div>
<UserPreferencedCurrencyDisplay
className="account-menu__balance"
value={balanceValue}
type={PRIMARY}
/>
</div>
{ this.renderKeyringType(keyring) }
{ this.renderRemoveAccount(keyring, identity) }
</div>
)
})
}

renderRemoveAccount (keyring, identity) {
const { t } = this.context
// Any account that's not from the HD wallet Keyring can be removed
const { type } = keyring
const isRemovable = type !== 'HD Key Tree'

return isRemovable && (
<Tooltip
title={t('removeAccount')}
position="bottom"
>
<a
className="remove-account-icon"
onClick={e => this.removeAccount(e, identity)}
/>
</Tooltip>
)
}

removeAccount (e, identity) {
e.preventDefault()
e.stopPropagation()
const { showRemoveAccountConfirmationModal } = this.props
showRemoveAccountConfirmationModal(identity)
}

renderKeyringType (keyring) {
const { t } = this.context

// Sometimes keyrings aren't loaded yet
if (!keyring) {
return null
}

const { type } = keyring
let label

switch (type) {
case 'Trezor Hardware':
case 'Ledger Hardware':
label = t('hardware')
break
case 'Simple Key Pair':
label = t('imported')
break
}

return label && (
<div className="keyring-label allcaps">
{ label }
</div>
)
}

setAtAccountListBottom = () => {
const target = document.querySelector('.account-menu__accounts')
const { scrollTop, offsetHeight, scrollHeight } = target
const atAccountListBottom = scrollTop + offsetHeight >= scrollHeight
this.setState({ atAccountListBottom })
}

onScroll = debounce(this.setAtAccountListBottom, 25)

handleScrollDown = e => {
e.stopPropagation()
const target = document.querySelector('.account-menu__accounts')
const { scrollHeight } = target
target.scroll({ left: 0, top: scrollHeight, behavior: 'smooth' })
this.setAtAccountListBottom()
}

renderScrollButton () {
const { accounts } = this.props
const { atAccountListBottom } = this.state

return !atAccountListBottom && Object.keys(accounts).length > 3 && (
<div
className="account-menu__scroll-button"
onClick={this.handleScrollDown}
>
<img
src="./images/icons/down-arrow.svg"
width={28}
height={28}
/>
</div>
)
}

render () {
const { t } = this.context
const {
isAccountMenuOpen,
toggleAccountMenu,
lockMetamask,
history,
} = this.props

return (
<Menu
className="account-menu"
isShowing={isAccountMenuOpen}
>
<CloseArea onClick={toggleAccountMenu} />
<Item className="account-menu__header">
{ t('myAccounts') }
<button
className="account-menu__logout-button"
onClick={() => {
lockMetamask()
history.push(DEFAULT_ROUTE)
}}
>
{ t('logout') }
</button>
</Item>
<Divider />
<div className="account-menu__accounts-container">
<div
className="account-menu__accounts"
onScroll={this.onScroll}
>
{ this.renderAccounts() }
</div>
{ this.renderScrollButton() }
</div>
<Divider />
<Item
onClick={() => {
toggleAccountMenu()
history.push(NEW_ACCOUNT_ROUTE)
}}
icon={
<img
className="account-menu__item-icon"
src="images/plus-btn-white.svg"
/>
}
text={t('createAccount')}
/>
<Item
onClick={() => {
toggleAccountMenu()
history.push(IMPORT_ACCOUNT_ROUTE)
}}
icon={
<img
className="account-menu__item-icon"
src="images/import-account.svg"
/>
}
text={t('importAccount')}
/>
<Item
onClick={() => {
toggleAccountMenu()

if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE)
} else {
history.push(CONNECT_HARDWARE_ROUTE)
}
}}
icon={
<img
className="account-menu__item-icon"
src="images/connect-icon.svg"
/>
}
text={t('connectHardwareWallet')}
/>
<Divider />
<Item
onClick={() => {
toggleAccountMenu()
history.push(INFO_ROUTE)
}}
icon={
<img src="images/mm-info-icon.svg" />
}
text={t('infoHelp')}
/>
<Item
onClick={() => {
toggleAccountMenu()
history.push(SETTINGS_ROUTE)
}}
icon={
<img
className="account-menu__item-icon"
src="images/settings.svg"
/>
}
text={t('settings')}
/>
</Menu>
)
}
}
62 changes: 62 additions & 0 deletions ui/app/components/account-menu/account-menu.container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withRouter } from 'react-router-dom'
import {
toggleAccountMenu,
showAccountDetail,
hideSidebar,
lockMetamask,
hideWarning,
showConfigPage,
showInfoPage,
showModal,
} from '../../actions'
import { getMetaMaskAccounts } from '../../selectors'
import AccountMenu from './account-menu.component'

function mapStateToProps (state) {
const { metamask: { selectedAddress, isAccountMenuOpen, keyrings, identities } } = state

return {
selectedAddress,
isAccountMenuOpen,
keyrings,
identities,
accounts: getMetaMaskAccounts(state),
}
}

function mapDispatchToProps (dispatch) {
return {
toggleAccountMenu: () => dispatch(toggleAccountMenu()),
showAccountDetail: address => {
dispatch(showAccountDetail(address))
dispatch(hideSidebar())
dispatch(toggleAccountMenu())
},
lockMetamask: () => {
dispatch(lockMetamask())
dispatch(hideWarning())
dispatch(hideSidebar())
dispatch(toggleAccountMenu())
},
showConfigPage: () => {
dispatch(showConfigPage())
dispatch(hideSidebar())
dispatch(toggleAccountMenu())
},
showInfoPage: () => {
dispatch(showInfoPage())
dispatch(hideSidebar())
dispatch(toggleAccountMenu())
},
showRemoveAccountConfirmationModal: identity => {
return dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
},
}
}

export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(AccountMenu)
Loading