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}
{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 (
);
}
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={
}
text={t('createAccount')}
/>
{
toggleAccountMenu();
metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Clicked Import Account',
},
});
history.push(IMPORT_ACCOUNT_ROUTE);
}}
icon={
}
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={
}
text={t('connectHardwareWallet')}
/>
{
global.platform.openTab({ url: supportLink });
}}
icon={}
text={supportText}
/>
{
toggleAccountMenu();
history.push(SETTINGS_ROUTE);
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Opened Settings',
},
});
}}
icon={
}
text={t('settings')}
/>
);
}
}