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 classnames from 'classnames'; import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import Identicon from '../../ui/identicon'; import SiteIcon from '../../ui/site-icon'; import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'; import { PRIMARY } from '../../../helpers/constants/common'; import { KEYRING_TYPES } from '../../../../shared/constants/hardware-wallets'; import { SETTINGS_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'; import Button from '../../ui/button'; import { isBeta } from '../../../helpers/utils/build-types'; export function AccountMenuItem(props) { const { icon, children, text, subText, className, onClick } = props; const itemClassName = classnames('account-menu__item', className, { 'account-menu__item--clickable': Boolean(onClick), }); return children ? (
{children}
) : (
{icon ?
{icon}
: null} {text ?
{text}
: null} {subText ? (
{subText}
) : null}
); } AccountMenuItem.propTypes = { icon: PropTypes.node, children: PropTypes.node, text: PropTypes.node, subText: PropTypes.node, onClick: PropTypes.func, className: PropTypes.string, }; 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([], { 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 = ( ); return [ this.setSearchQuery(e.target.value)} startAdornment={inputAdornment} fullWidth theme="material-white-padded" />,
, ]; } 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 (

{this.context.t('noAccountsFound')}

); } 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 (
{ this.context.metricsEvent({ eventOpts: { category: 'Navigation', action: 'Main Menu', name: 'Switched Account', }, }); showAccountDetail(identity.address); }} key={identity.address} >
{isSelected ? (
) : null}
{identity.name || ''}
{this.renderKeyringType(keyring)} {iconAndNameForOpenDomain ? (
) : null}
); }); } renderKeyringType(keyring) { const { t } = this.context; // Sometimes keyrings aren't loaded yet if (!keyring) { return null; } const { type } = keyring; let label; switch (type) { case KEYRING_TYPES.TREZOR: case KEYRING_TYPES.LEDGER: label = t('hardware'); break; case 'Simple Key Pair': label = t('imported'); break; default: return null; } return
{label}
; } resetSearchQuery() { this.setSearchQuery(''); } setSearchQuery(searchQuery) { this.setState({ searchQuery }); } setShouldShowScrollButton = () => { if (!this.accountsRef) { return; } 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() { if (!this.state.shouldShowScrollButton) { return null; } return (
{this.context.t('scrollDown')}
); } render() { const { t, metricsEvent } = this.context; const { shouldShowAccountsSearch, isAccountMenuOpen, toggleAccountMenu, lockMetamask, history, } = this.props; if (!isAccountMenuOpen) { return null; } let supportText = t('support'); let supportLink = 'https://support.metamask.io'; if (isBeta()) { supportText = t('needHelpSubmitTicket'); supportLink = 'https://metamask.zendesk.com/hc/en-us/requests/new'; } return (
{t('myAccounts')}
{shouldShowAccountsSearch ? this.renderAccountsSearch() : null}
{ this.accountsRef = ref; }} > {this.renderAccounts()}
{this.renderScrollButton()}
{ toggleAccountMenu(); metricsEvent({ eventOpts: { category: 'Navigation', action: 'Main Menu', name: 'Clicked Create Account', }, }); history.push(NEW_ACCOUNT_ROUTE); }} icon={ {t('createAccount')} } text={t('createAccount')} /> { toggleAccountMenu(); metricsEvent({ eventOpts: { category: 'Navigation', action: 'Main Menu', name: 'Clicked Import Account', }, }); history.push(IMPORT_ACCOUNT_ROUTE); }} icon={ {t('importAccount')} } text={t('importAccount')} /> { 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={ {t('connectHardwareWallet')} } text={t('connectHardwareWallet')} />
{ global.platform.openTab({ url: supportLink }); }} icon={{supportText}} text={supportText} /> { toggleAccountMenu(); history.push(SETTINGS_ROUTE); this.context.metricsEvent({ eventOpts: { category: 'Navigation', action: 'Main Menu', name: 'Opened Settings', }, }); }} icon={ } text={t('settings')} />
); } }