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 { EVENT, EVENT_NAMES, CONTEXT_PROPS, } from '../../../../shared/constants/metametrics'; 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, ///: BEGIN:ONLY_INCLUDE_IN(beta,flask) SUPPORT_REQUEST_LINK, ///: END:ONLY_INCLUDE_IN } from '../../../helpers/constants/common'; import { SETTINGS_ROUTE, NEW_ACCOUNT_ROUTE, IMPORT_ACCOUNT_ROUTE, CONNECT_HARDWARE_ROUTE, DEFAULT_ROUTE, ///: BEGIN:ONLY_INCLUDE_IN(flask) NOTIFICATIONS_ROUTE, ///: END:ONLY_INCLUDE_IN } from '../../../helpers/constants/routes'; import TextField from '../../ui/text-field'; import IconCheck from '../../ui/icon/icon-check'; import IconSpeechBubbles from '../../ui/icon/icon-speech-bubbles'; import IconConnect from '../../ui/icon/icon-connect'; import IconCog from '../../ui/icon/icon-cog'; import IconPlus from '../../ui/icon/icon-plus'; import IconImport from '../../ui/icon/icon-import'; import Button from '../../ui/button'; import SearchIcon from '../../ui/icon/search-icon'; import { SUPPORT_LINK } from '../../../../shared/lib/ui-utils'; import KeyRingLabel from './keyring-label'; 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}
) : ( ); } 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, trackEvent: 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, addressConnectedSubjectMap: PropTypes.object, originOfCurrentTab: PropTypes.string, ///: BEGIN:ONLY_INCLUDE_IN(flask) unreadNotificationsCount: PropTypes.number, ///: END:ONLY_INCLUDE_IN }; accountsRef; state = { shouldShowScrollButton: false, searchQuery: '', }; addressFuse = new Fuse([], { threshold: 0.55, location: 0, distance: 100, maxPatternLength: 32, minMatchCharLength: 1, ignoreFieldNorm: true, 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 handleChange = (e) => { const val = e.target.value.length > 1 ? e.target.value : ''; this.setSearchQuery(val); }; const inputAdornment = ( ); return [ ,
, ]; } renderAccounts() { const { accounts, selectedAddress, keyrings, showAccountDetail, addressConnectedSubjectMap, 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 addressSubjects = addressConnectedSubjectMap[identity.address] || {}; const iconAndNameForOpenSubject = addressSubjects[originOfCurrentTab]; return ( ); }); } 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 (
); } render() { const { t, trackEvent } = this.context; const { shouldShowAccountsSearch, isAccountMenuOpen, toggleAccountMenu, lockMetamask, history, ///: BEGIN:ONLY_INCLUDE_IN(flask) unreadNotificationsCount, ///: END:ONLY_INCLUDE_IN } = this.props; if (!isAccountMenuOpen) { return null; } let supportText = t('support'); let supportLink = SUPPORT_LINK; ///: BEGIN:ONLY_INCLUDE_IN(beta,flask) supportText = t('needHelpSubmitTicket'); supportLink = SUPPORT_REQUEST_LINK; ///: END:ONLY_INCLUDE_IN return (
{t('myAccounts')}
{shouldShowAccountsSearch ? this.renderAccountsSearch() : null}
{ this.accountsRef = ref; }} > {this.renderAccounts()}
{this.renderScrollButton()}
{ toggleAccountMenu(); trackEvent({ category: EVENT.CATEGORIES.NAVIGATION, event: EVENT_NAMES.ACCOUNT_ADD_SELECTED, properties: { account_type: EVENT.ACCOUNT_TYPES.DEFAULT, location: 'Main Menu', }, }); history.push(NEW_ACCOUNT_ROUTE); }} icon={} text={t('createAccount')} /> { toggleAccountMenu(); trackEvent({ category: EVENT.CATEGORIES.NAVIGATION, event: EVENT_NAMES.ACCOUNT_ADD_SELECTED, properties: { account_type: EVENT.ACCOUNT_TYPES.IMPORTED, location: 'Main Menu', }, }); history.push(IMPORT_ACCOUNT_ROUTE); }} icon={ } text={t('importAccount')} /> { toggleAccountMenu(); trackEvent({ category: EVENT.CATEGORIES.NAVIGATION, event: EVENT_NAMES.ACCOUNT_ADD_SELECTED, properties: { account_type: EVENT.ACCOUNT_TYPES.HARDWARE, location: 'Main Menu', }, }); if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) { global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE); } else { history.push(CONNECT_HARDWARE_ROUTE); } }} icon={ } text={t('connectHardwareWallet')} />
{ ///: BEGIN:ONLY_INCLUDE_IN(flask) <> { toggleAccountMenu(); history.push(NOTIFICATIONS_ROUTE); }} icon={
{unreadNotificationsCount > 0 && (
{unreadNotificationsCount}
)}
} text={t('notifications')} />
///: END:ONLY_INCLUDE_IN } { trackEvent( { category: EVENT.CATEGORIES.NAVIGATION, event: EVENT_NAMES.SUPPORT_LINK_CLICKED, properties: { url: supportLink, }, }, { contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], }, ); global.platform.openTab({ url: supportLink }); }} icon={ } text={supportText} /> { toggleAccountMenu(); history.push(SETTINGS_ROUTE); this.context.trackEvent({ category: EVENT.CATEGORIES.NAVIGATION, event: EVENT_NAMES.NAV_SETTINGS_OPENED, properties: { location: 'Main Menu', }, }); }} icon={ } text={t('settings')} />
); } }