1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00
metamask-extension/ui/app/components/app/account-menu/account-menu.component.js
Mark Stacey f5ec16c65a
Fix account menu entry for imported accounts (#8747)
The entry for imported accounts in the account menu looked wrong with
the new connected site icon - there was no padding between the site
icon and the 'imported' label. The entry was pretty crowded with these
three symbols as well (the third being the 'x' used to remove the
account).

The site icon has been made the right-most icon, so that it lines up
with the site icons shown for other accounts, and spacing has been
added between the site icon and the 'imported' label.

The 'x' used to remove accounts has been removed. Accounts can still be
removed from the 'Account Options' menu on the Home screen. This seemed
like the wrong place for this button to exist, as it's the only action
that can be taken from that menu aside from navigation.
2020-06-05 17:24:51 -03:00

412 lines
11 KiB
JavaScript

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { debounce } from 'lodash'
import Fuse from 'fuse.js'
import InputAdornment from '@material-ui/core/InputAdornment'
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 Identicon from '../../ui/identicon'
import IconWithFallBack from '../../ui/icon-with-fallback'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { PRIMARY } from '../../../helpers/constants/common'
import {
SETTINGS_ROUTE,
ABOUT_US_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
CONNECT_HARDWARE_ROUTE,
DEFAULT_ROUTE,
} from '../../../helpers/constants/routes'
import TextField from '../../ui/text-field'
import SearchIcon from '../../ui/search-icon'
export default class AccountMenu extends Component {
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
static propTypes = {
shouldShowAccountsSearch: PropTypes.bool,
accounts: PropTypes.array,
history: PropTypes.object,
isAccountMenuOpen: PropTypes.bool,
keyrings: PropTypes.array,
lockMetamask: PropTypes.func,
selectedAddress: PropTypes.string,
showAccountDetail: PropTypes.func,
toggleAccountMenu: PropTypes.func,
addressConnectedDomainMap: PropTypes.object,
originOfCurrentTab: PropTypes.string,
}
accountsRef
state = {
shouldShowScrollButton: false,
searchQuery: '',
}
addressFuse = new Fuse([], {
shouldSort: false,
threshold: 0.45,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
{ name: 'name', weight: 0.5 },
{ name: 'address', weight: 0.5 },
],
})
componentDidUpdate (prevProps, prevState) {
const { isAccountMenuOpen: prevIsAccountMenuOpen } = prevProps
const { searchQuery: prevSearchQuery } = prevState
const { isAccountMenuOpen } = this.props
const { searchQuery } = this.state
if (!prevIsAccountMenuOpen && isAccountMenuOpen) {
this.setShouldShowScrollButton()
this.resetSearchQuery()
}
// recalculate on each search query change
// whether we can show scroll down button
if (isAccountMenuOpen && prevSearchQuery !== searchQuery) {
this.setShouldShowScrollButton()
}
}
renderAccountsSearch () {
const inputAdornment = (
<InputAdornment
position="start"
style={{
maxHeight: 'none',
marginRight: 0,
marginLeft: '8px',
}}
>
<SearchIcon />
</InputAdornment>
)
return [
<TextField
key="search-text-field"
id="search-accounts"
placeholder={this.context.t('searchAccounts')}
type="text"
value={this.state.searchQuery}
onChange={(e) => this.setSearchQuery(e.target.value)}
startAdornment={inputAdornment}
fullWidth
theme="material-white-padded"
/>,
<Divider key="search-divider" />,
]
}
renderAccounts () {
const {
accounts,
selectedAddress,
keyrings,
showAccountDetail,
addressConnectedDomainMap,
originOfCurrentTab,
} = this.props
const { searchQuery } = this.state
let filteredIdentities = accounts
if (searchQuery) {
this.addressFuse.setCollection(accounts)
filteredIdentities = this.addressFuse.search(searchQuery)
}
if (filteredIdentities.length === 0) {
return <p className="account-menu__no-accounts">{this.context.t('noAccountsFound')}</p>
}
return filteredIdentities.map((identity) => {
const isSelected = identity.address === selectedAddress
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address)
})
const addressDomains = addressConnectedDomainMap[identity.address] || {}
const iconAndNameForOpenDomain = addressDomains[originOfCurrentTab]
return (
<div
className="account-menu__account menu__item--clickable"
onClick={() => {
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Switched Account',
},
})
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={identity.balance}
type={PRIMARY}
/>
</div>
{ this.renderKeyringType(keyring) }
{ iconAndNameForOpenDomain
? (
<div className="account-menu__icon-list">
<IconWithFallBack icon={iconAndNameForOpenDomain.icon} name={iconAndNameForOpenDomain.name} />
</div>
)
: null
}
</div>
)
})
}
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
default:
return null
}
return (
<div className="keyring-label allcaps">
{ label }
</div>
)
}
resetSearchQuery () {
this.setSearchQuery('')
}
setSearchQuery (searchQuery) {
this.setState({ searchQuery })
}
setShouldShowScrollButton = () => {
const { scrollTop, offsetHeight, scrollHeight } = this.accountsRef
const canScroll = scrollHeight > offsetHeight
const atAccountListBottom = scrollTop + offsetHeight >= scrollHeight
const shouldShowScrollButton = canScroll && !atAccountListBottom
this.setState({ shouldShowScrollButton })
}
onScroll = debounce(this.setShouldShowScrollButton, 25)
handleScrollDown = (e) => {
e.stopPropagation()
const { scrollHeight } = this.accountsRef
this.accountsRef.scroll({ left: 0, top: scrollHeight, behavior: 'smooth' })
this.setShouldShowScrollButton()
}
renderScrollButton () {
const { shouldShowScrollButton } = this.state
return shouldShowScrollButton && (
<div
className="account-menu__scroll-button"
onClick={this.handleScrollDown}
>
<img
src="./images/icons/down-arrow.svg"
width={28}
height={28}
alt="scroll down"
/>
</div>
)
}
render () {
const { t, metricsEvent } = this.context
const {
shouldShowAccountsSearch,
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__lock-button"
onClick={() => {
lockMetamask()
history.push(DEFAULT_ROUTE)
}}
>
{ t('lock') }
</button>
</Item>
<Divider />
<div className="account-menu__accounts-container">
{shouldShowAccountsSearch ? this.renderAccountsSearch() : null}
<div
className="account-menu__accounts"
onScroll={this.onScroll}
ref={(ref) => {
this.accountsRef = ref
}}
>
{ this.renderAccounts() }
</div>
{ this.renderScrollButton() }
</div>
<Divider />
<Item
onClick={() => {
toggleAccountMenu()
metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Clicked Create Account',
},
})
history.push(NEW_ACCOUNT_ROUTE)
}}
icon={(
<img
className="account-menu__item-icon"
src="images/plus-btn-white.svg"
/>
)}
text={t('createAccount')}
/>
<Item
onClick={() => {
toggleAccountMenu()
metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Clicked Import Account',
},
})
history.push(IMPORT_ACCOUNT_ROUTE)
}}
icon={(
<img
className="account-menu__item-icon"
src="images/import-account.svg"
/>
)}
text={t('importAccount')}
/>
<Item
onClick={() => {
toggleAccountMenu()
metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Clicked Connect Hardware',
},
})
if (getEnvironmentType() === 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(ABOUT_US_ROUTE)
}}
icon={
<img src="images/mm-info-icon.svg" />
}
text={t('infoHelp')}
/>
<Item
onClick={() => {
toggleAccountMenu()
history.push(SETTINGS_ROUTE)
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Opened Settings',
},
})
}}
icon={(
<img
className="account-menu__item-icon"
src="images/settings.svg"
/>
)}
text={t('settings')}
/>
</Menu>
)
}
}