mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Feat/settings search (#13214)
* fix error with color variable - fix rebase * clean list search & fuse threshold decreased * update search-icon , fix tests * nice to have highlighting text & cleaning * unit test on settings & search input ui up on expanded view * fix color variable in alert scss * setting search input padding right up * fix dom warning * util/search test added & Dom element warning fix * renaming files * fix color text in settings search * settings search highlight text refacto & fix ui * fix settings-search test & renaming * Fix styling on search field for edge cases, update components and e2e E2E tests update for search feature Update components from class to functional component # Fix storybook for search box Fix styling Fix unit tests fix: remove z-index Fix unit tests Co-authored-by: amerkadicE <amer.kadic@endava.com>
This commit is contained in:
parent
96b82349a0
commit
f5e86d0351
12
app/_locales/en/messages.json
generated
12
app/_locales/en/messages.json
generated
@ -1817,6 +1817,12 @@
|
||||
"missingNFT": {
|
||||
"message": "Don't see your NFT?"
|
||||
},
|
||||
"missingSetting": {
|
||||
"message": "Can't find a setting?"
|
||||
},
|
||||
"missingSettingRequest": {
|
||||
"message": "Request here"
|
||||
},
|
||||
"missingToken": {
|
||||
"message": "Don't see your token?"
|
||||
},
|
||||
@ -2533,6 +2539,9 @@
|
||||
"searchResults": {
|
||||
"message": "Search Results"
|
||||
},
|
||||
"searchSettings": {
|
||||
"message": "Search in settings"
|
||||
},
|
||||
"searchTokens": {
|
||||
"message": "Search Tokens"
|
||||
},
|
||||
@ -2682,6 +2691,9 @@
|
||||
"settings": {
|
||||
"message": "Settings"
|
||||
},
|
||||
"settingsSearchMatchingNotFound": {
|
||||
"message": "No matching results found"
|
||||
},
|
||||
"show": {
|
||||
"message": "Show"
|
||||
},
|
||||
|
@ -36,10 +36,10 @@ describe('Stores custom RPC history', function () {
|
||||
await driver.findElement('.networks-tab__sub-header-text');
|
||||
|
||||
const customRpcInputs = await driver.findElements('input[type="text"]');
|
||||
const networkNameInput = customRpcInputs[0];
|
||||
const rpcUrlInput = customRpcInputs[1];
|
||||
const chainIdInput = customRpcInputs[2];
|
||||
const symbolInput = customRpcInputs[3];
|
||||
const networkNameInput = customRpcInputs[1];
|
||||
const rpcUrlInput = customRpcInputs[2];
|
||||
const chainIdInput = customRpcInputs[3];
|
||||
const symbolInput = customRpcInputs[4];
|
||||
|
||||
await networkNameInput.clear();
|
||||
await networkNameInput.sendKeys(networkName);
|
||||
@ -84,7 +84,7 @@ describe('Stores custom RPC history', function () {
|
||||
await driver.findElement('.networks-tab__sub-header-text');
|
||||
|
||||
const customRpcInputs = await driver.findElements('input[type="text"]');
|
||||
const rpcUrlInput = customRpcInputs[1];
|
||||
const rpcUrlInput = customRpcInputs[2];
|
||||
|
||||
await rpcUrlInput.clear();
|
||||
await rpcUrlInput.sendKeys(duplicateRpcUrl);
|
||||
@ -120,8 +120,8 @@ describe('Stores custom RPC history', function () {
|
||||
await driver.findElement('.networks-tab__sub-header-text');
|
||||
|
||||
const customRpcInputs = await driver.findElements('input[type="text"]');
|
||||
const rpcUrlInput = customRpcInputs[1];
|
||||
const chainIdInput = customRpcInputs[2];
|
||||
const rpcUrlInput = customRpcInputs[2];
|
||||
const chainIdInput = customRpcInputs[3];
|
||||
|
||||
await chainIdInput.clear();
|
||||
await chainIdInput.sendKeys(duplicateChainId);
|
||||
|
@ -125,7 +125,7 @@ export default class AccountMenu extends Component {
|
||||
marginLeft: '8px',
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
<SearchIcon color="currentColor" />
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
|
@ -41,7 +41,6 @@
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 201;
|
||||
|
||||
&__title {
|
||||
@include H4;
|
||||
|
@ -1,13 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function SearchIcon() {
|
||||
export default function SearchIcon({ color }) {
|
||||
return (
|
||||
<svg
|
||||
height="20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<svg height="20" width="20" xmlns="http://www.w3.org/2000/svg" fill={color}>
|
||||
<g clipRule="evenodd" fillRule="evenodd">
|
||||
<path d="M9.167 3.333a5.833 5.833 0 100 11.667 5.833 5.833 0 000-11.667zm-7.5 5.834a7.5 7.5 0 1115 0 7.5 7.5 0 01-15 0z" />
|
||||
<path d="M13.286 13.286a.833.833 0 011.178 0l3.625 3.625a.833.833 0 11-1.178 1.178l-3.625-3.625a.833.833 0 010-1.178z" />
|
||||
@ -15,3 +11,10 @@ export default function SearchIcon() {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
SearchIcon.propTypes = {
|
||||
/**
|
||||
* Color of the icon should be a valid design system color and is required
|
||||
*/
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
@ -88,6 +88,7 @@ const getMaterialThemeInputProps = ({
|
||||
dir,
|
||||
classes: { materialLabel, materialFocused, materialError, materialUnderline },
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
min,
|
||||
max,
|
||||
autoComplete,
|
||||
@ -101,6 +102,7 @@ const getMaterialThemeInputProps = ({
|
||||
},
|
||||
InputProps: {
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
classes: {
|
||||
underline: materialUnderline,
|
||||
},
|
||||
@ -122,12 +124,14 @@ const getMaterialWhitePaddedThemeInputProps = ({
|
||||
materialWhitePaddedUnderline,
|
||||
},
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
min,
|
||||
max,
|
||||
autoComplete,
|
||||
}) => ({
|
||||
InputProps: {
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
classes: {
|
||||
root: materialWhitePaddedRoot,
|
||||
focused: materialWhitePaddedFocused,
|
||||
@ -157,6 +161,7 @@ const getBorderedThemeInputProps = ({
|
||||
},
|
||||
largeLabel,
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
min,
|
||||
max,
|
||||
autoComplete,
|
||||
@ -172,6 +177,7 @@ const getBorderedThemeInputProps = ({
|
||||
},
|
||||
InputProps: {
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
disableUnderline: true,
|
||||
classes: {
|
||||
root: inputRoot,
|
||||
@ -198,6 +204,7 @@ const TextField = ({
|
||||
classes,
|
||||
theme,
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
largeLabel,
|
||||
dir,
|
||||
min,
|
||||
@ -209,6 +216,7 @@ const TextField = ({
|
||||
const inputProps = themeToInputProps[theme]({
|
||||
classes,
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
largeLabel,
|
||||
dir,
|
||||
min,
|
||||
@ -257,6 +265,7 @@ TextField.propTypes = {
|
||||
*/
|
||||
theme: PropTypes.oneOf(['bordered', 'material', 'material-white-padded']),
|
||||
startAdornment: PropTypes.element,
|
||||
endAdornment: PropTypes.element,
|
||||
/**
|
||||
* Show large label
|
||||
*/
|
||||
|
408
ui/helpers/utils/settings-search.js
Normal file
408
ui/helpers/utils/settings-search.js
Normal file
@ -0,0 +1,408 @@
|
||||
/* eslint-disable require-unicode-regexp */
|
||||
import {
|
||||
ALERTS_ROUTE,
|
||||
ADVANCED_ROUTE,
|
||||
SECURITY_ROUTE,
|
||||
GENERAL_ROUTE,
|
||||
ABOUT_US_ROUTE,
|
||||
NETWORKS_ROUTE,
|
||||
CONTACT_LIST_ROUTE,
|
||||
EXPERIMENTAL_ROUTE,
|
||||
} from '../constants/routes';
|
||||
|
||||
const MENU_TAB = 'menu-tab';
|
||||
const MENU_SECTION = 'menu-section';
|
||||
|
||||
function showHideSettings(t, settings) {
|
||||
if (!process.env.COLLECTIBLES_V1) {
|
||||
return settings.filter(
|
||||
(e) =>
|
||||
e.section !== t('enableOpenSeaAPI') &&
|
||||
e.section !== t('useCollectibleDetection'),
|
||||
);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
export function getSettingsRoutes(t) {
|
||||
const settingsRoutesList = [
|
||||
{
|
||||
tab: t('general'),
|
||||
section: t('currencyConversion'),
|
||||
description: '',
|
||||
route: `${GENERAL_ROUTE}#currency-conversion`,
|
||||
image: 'general-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('general'),
|
||||
section: t('primaryCurrencySetting'),
|
||||
description: t('primaryCurrencySettingDescription'),
|
||||
route: `${GENERAL_ROUTE}#primary-currency`,
|
||||
image: 'general-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('general'),
|
||||
section: t('currentLanguage'),
|
||||
description: '',
|
||||
route: `${GENERAL_ROUTE}#current-language`,
|
||||
image: 'general-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('general'),
|
||||
section: t('accountIdenticon'),
|
||||
description: '',
|
||||
route: `${GENERAL_ROUTE}#account-identicon`,
|
||||
image: 'general-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('general'),
|
||||
section: t('hideZeroBalanceTokens'),
|
||||
description: '',
|
||||
route: `${GENERAL_ROUTE}#zero-balancetokens`,
|
||||
image: 'general-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('stateLogs'),
|
||||
description: t('stateLogsDescription'),
|
||||
route: `${ADVANCED_ROUTE}#state-logs`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('syncWithMobile'),
|
||||
description: '',
|
||||
route: `${ADVANCED_ROUTE}#sync-withmobile`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('resetAccount'),
|
||||
description: t('resetAccountDescription'),
|
||||
route: `${ADVANCED_ROUTE}#reset-account`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('showAdvancedGasInline'),
|
||||
description: t('showAdvancedGasInlineDescription'),
|
||||
route: `${ADVANCED_ROUTE}#advanced-gascontrols`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('showHexData'),
|
||||
description: t('showHexDataDescription'),
|
||||
route: `${ADVANCED_ROUTE}#show-hexdata`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('showFiatConversionInTestnets'),
|
||||
description: t('showFiatConversionInTestnetsDescription'),
|
||||
route: `${ADVANCED_ROUTE}#conversion-testnetworks`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('showTestnetNetworks'),
|
||||
description: t('showTestnetNetworksDescription'),
|
||||
route: `${ADVANCED_ROUTE}#show-testnets`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('nonceField'),
|
||||
description: t('nonceFieldDescription'),
|
||||
route: `${ADVANCED_ROUTE}#customize-nonce`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('autoLockTimeLimit'),
|
||||
description: t('autoLockTimeLimitDescription'),
|
||||
route: `${ADVANCED_ROUTE}#autolock-timer`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('syncWithThreeBox'),
|
||||
description: t('syncWithThreeBoxDescription'),
|
||||
route: `${ADVANCED_ROUTE}#sync-with3box`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('ipfsGateway'),
|
||||
description: t('ipfsGatewayDescription'),
|
||||
route: `${ADVANCED_ROUTE}#ipfs-gateway`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('preferredLedgerConnectionType'),
|
||||
description: t('preferredLedgerConnectionType'),
|
||||
route: `${ADVANCED_ROUTE}#ledger-connection`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('advanced'),
|
||||
section: t('dismissReminderField'),
|
||||
description: t('dismissReminderDescriptionField'),
|
||||
route: `${ADVANCED_ROUTE}#dimiss-secretrecovery`,
|
||||
image: 'advanced-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('contacts'),
|
||||
section: t('contacts'),
|
||||
description: t('contacts'),
|
||||
route: CONTACT_LIST_ROUTE,
|
||||
image: 'contacts-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('securityAndPrivacy'),
|
||||
section: t('revealSeedWords'),
|
||||
description: t('revealSeedWords'),
|
||||
route: `${SECURITY_ROUTE}#reveal-secretrecovery`,
|
||||
image: 'security-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('securityAndPrivacy'),
|
||||
section: t('showIncomingTransactions'),
|
||||
description: t('showIncomingTransactionsDescription'),
|
||||
route: `${SECURITY_ROUTE}#incoming-transaction`,
|
||||
image: 'security-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('securityAndPrivacy'),
|
||||
section: t('usePhishingDetection'),
|
||||
description: t('usePhishingDetectionDescription'),
|
||||
route: `${SECURITY_ROUTE}#phishing-detection`,
|
||||
image: 'security-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('securityAndPrivacy'),
|
||||
section: t('participateInMetaMetrics'),
|
||||
description: t('participateInMetaMetricsDescription'),
|
||||
route: `${SECURITY_ROUTE}#metrametrics`,
|
||||
image: 'security-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('alerts'),
|
||||
section: t('alertSettingsUnconnectedAccount'),
|
||||
description: t('alertSettingsUnconnectedAccount'),
|
||||
route: `${ALERTS_ROUTE}#unconnected-account`,
|
||||
image: 'alerts-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('alerts'),
|
||||
section: t('alertSettingsWeb3ShimUsage'),
|
||||
description: t('alertSettingsWeb3ShimUsage'),
|
||||
route: `${ALERTS_ROUTE}#web3-shimusage`,
|
||||
image: 'alerts-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('networks'),
|
||||
section: t('mainnet'),
|
||||
description: t('mainnet'),
|
||||
route: `${NETWORKS_ROUTE}#networks-mainnet`,
|
||||
image: 'network-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('networks'),
|
||||
section: t('ropsten'),
|
||||
description: t('ropsten'),
|
||||
route: `${NETWORKS_ROUTE}#networks-ropsten`,
|
||||
image: 'network-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('networks'),
|
||||
section: t('rinkeby'),
|
||||
description: t('rinkeby'),
|
||||
route: `${NETWORKS_ROUTE}#networks-rinkeby`,
|
||||
image: 'network-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('networks'),
|
||||
section: t('goerli'),
|
||||
description: t('goerli'),
|
||||
route: `${NETWORKS_ROUTE}#networks-goerli`,
|
||||
image: 'network-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('networks'),
|
||||
section: t('kovan'),
|
||||
description: t('kovan'),
|
||||
route: `${NETWORKS_ROUTE}#networtks-kovan`,
|
||||
image: 'network-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('networks'),
|
||||
section: t('localhost'),
|
||||
description: t('localhost'),
|
||||
route: `${NETWORKS_ROUTE}#network-localhost`,
|
||||
image: 'network-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('experimental'),
|
||||
section: t('useTokenDetection'),
|
||||
description: t('useTokenDetectionDescription'),
|
||||
route: `${EXPERIMENTAL_ROUTE}#token-description`,
|
||||
image: 'experimental-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('experimental'),
|
||||
section: t('enableOpenSeaAPI'),
|
||||
description: t('enableOpenSeaAPIDescription'),
|
||||
route: `${EXPERIMENTAL_ROUTE}#opensea-api`,
|
||||
image: 'experimental-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('experimental'),
|
||||
section: t('useCollectibleDetection'),
|
||||
description: t('useCollectibleDetectionDescription'),
|
||||
route: `${EXPERIMENTAL_ROUTE}#autodetect-nfts`,
|
||||
image: 'experimental-icon.svg',
|
||||
},
|
||||
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('metamaskVersion'),
|
||||
description: t('builtAroundTheWorld'),
|
||||
route: `${ABOUT_US_ROUTE}#version`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('links'),
|
||||
description: '',
|
||||
route: `${ABOUT_US_ROUTE}#links`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('privacyMsg'),
|
||||
description: t('privacyMsg'),
|
||||
route: `${ABOUT_US_ROUTE}#privacy-policy`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('terms'),
|
||||
description: t('terms'),
|
||||
route: `${ABOUT_US_ROUTE}#terms`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('attributions'),
|
||||
description: t('attributions'),
|
||||
route: `${ABOUT_US_ROUTE}#attributions`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('supportCenter'),
|
||||
description: t('supportCenter'),
|
||||
route: `${ABOUT_US_ROUTE}#supportcenter`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('visitWebSite'),
|
||||
description: t('visitWebSite'),
|
||||
route: `${ABOUT_US_ROUTE}#visitwebsite`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
|
||||
{
|
||||
tab: t('about'),
|
||||
section: t('contactUs'),
|
||||
description: t('contactUs'),
|
||||
route: `${ABOUT_US_ROUTE}#contactus`,
|
||||
image: 'info-icon.svg',
|
||||
},
|
||||
];
|
||||
|
||||
// TODO: write to json file?
|
||||
return showHideSettings(t, settingsRoutesList);
|
||||
}
|
||||
|
||||
function getFilteredSettingsRoutes(t, tabName) {
|
||||
return getSettingsRoutes(t).filter((s) => s.tab === tabName);
|
||||
}
|
||||
|
||||
export function getSettingsSectionNumber(t, tabName) {
|
||||
return getSettingsRoutes(t).filter((s) => s.tab === tabName).length;
|
||||
}
|
||||
|
||||
export function handleSettingsRefs(t, tabName, settingsRefs) {
|
||||
const settingsSearchJsonFiltered = getFilteredSettingsRoutes(t, tabName);
|
||||
const settingsRefsIndex = settingsSearchJsonFiltered.findIndex(
|
||||
(s) => s.route.substring(1) === window.location.hash.substring(1),
|
||||
);
|
||||
|
||||
if (
|
||||
settingsRefsIndex !== -1 &&
|
||||
settingsRefs[settingsRefsIndex].current !== null
|
||||
) {
|
||||
settingsRefs[settingsRefsIndex].current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
settingsRefs[settingsRefsIndex].current.focus();
|
||||
const historySettingsUrl = window.location.hash.split('#')[1];
|
||||
window.location.hash = historySettingsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleHooksSettingsRefs(t, tabName, settingsRefs, itemIndex) {
|
||||
const settingsSearchJsonFiltered = getFilteredSettingsRoutes(t, tabName);
|
||||
const settingsRefsIndex = settingsSearchJsonFiltered.findIndex(
|
||||
(s) => s.route.substring(1) === window.location.hash.substring(1),
|
||||
);
|
||||
|
||||
if (
|
||||
settingsRefsIndex !== -1 &&
|
||||
settingsRefs !== null &&
|
||||
itemIndex === settingsRefsIndex
|
||||
) {
|
||||
settingsRefs.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
settingsRefs.current.focus();
|
||||
const historySettingsUrl = window.location.hash.split('#')[1];
|
||||
window.location.hash = historySettingsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function colorText(menuElement, regex) {
|
||||
if (menuElement !== null) {
|
||||
let elemText = menuElement?.innerHTML;
|
||||
elemText = elemText.replace('&', '&');
|
||||
elemText = elemText.replace(
|
||||
/(<span style="background:#ffd33d">|<\/span>)/gim,
|
||||
'',
|
||||
);
|
||||
menuElement.innerHTML = elemText.replace(
|
||||
regex,
|
||||
'<span style="background:#ffd33d">$&</span>',
|
||||
);
|
||||
}
|
||||
}
|
||||
export function highlightSearchedText(menuIndex) {
|
||||
const menuTabElement = document.getElementById(`${MENU_TAB}_${menuIndex}`);
|
||||
const menuSectionElement = document.getElementById(
|
||||
`${MENU_SECTION}_${menuIndex}`,
|
||||
);
|
||||
|
||||
const searchElem = document.getElementById('search-settings');
|
||||
searchElem.addEventListener('input', (event) => {
|
||||
const searchQuery = event.target.value;
|
||||
const searchRegex = new RegExp(searchQuery, 'gi');
|
||||
colorText(menuTabElement, searchRegex);
|
||||
colorText(menuSectionElement, searchRegex);
|
||||
});
|
||||
}
|
524
ui/helpers/utils/settings-search.test.js
Normal file
524
ui/helpers/utils/settings-search.test.js
Normal file
@ -0,0 +1,524 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
getSettingsRoutes,
|
||||
getSettingsSectionNumber,
|
||||
handleSettingsRefs,
|
||||
} from './settings-search';
|
||||
|
||||
const t = (key) => {
|
||||
switch (key) {
|
||||
case 'general':
|
||||
return 'General';
|
||||
case 'currencyConversion':
|
||||
return 'Currency Conversion';
|
||||
case 'primaryCurrencySetting':
|
||||
return 'Primary Currenc';
|
||||
case 'primaryCurrencySettingDescription':
|
||||
return 'Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency.';
|
||||
case 'currentLanguage':
|
||||
return 'Current Language';
|
||||
case 'accountIdenticon':
|
||||
return 'Current Language"';
|
||||
case 'hideZeroBalanceTokens':
|
||||
return 'Hide Tokens Without Balance';
|
||||
case 'advanced':
|
||||
return 'Advanced';
|
||||
case 'stateLogs':
|
||||
return 'State Logs';
|
||||
case 'stateLogsDescription':
|
||||
return 'State logs contain your public account addresses and sent transactions.';
|
||||
case 'syncWithMobile':
|
||||
return 'Sync with mobile';
|
||||
case 'resetAccount':
|
||||
return 'Reset Account';
|
||||
case 'resetAccountDescription':
|
||||
return 'Resetting your account will clear your transaction history. This will not change the balances in your accounts or require you to re-enter your Secret Recovery Phrase.';
|
||||
case 'showAdvancedGasInline':
|
||||
return 'Advanced gas controls';
|
||||
case 'showAdvancedGasInlineDescription':
|
||||
return 'Select this to show gas price and limit controls directly on the send and confirm screens.';
|
||||
case 'showHexData':
|
||||
return 'Show Hex Data';
|
||||
case 'showHexDataDescription':
|
||||
return 'Select this to show the hex data field on the send screen';
|
||||
case 'showFiatConversionInTestnets':
|
||||
return 'Show Conversion on test networks';
|
||||
case 'showFiatConversionInTestnetsDescription':
|
||||
return 'Select this to show fiat conversion on test network';
|
||||
case 'showTestnetNetworks':
|
||||
return 'Show test networks';
|
||||
case 'showTestnetNetworksDescription':
|
||||
return 'Select this to show test networks in network list';
|
||||
case 'nonceField':
|
||||
return 'Customize transaction nonce';
|
||||
case 'nonceFieldDescription':
|
||||
return 'Turn this on to change the nonce (transaction number) on confirmation screens. This is an advanced feature, use cautiously.';
|
||||
case 'autoLockTimeLimit':
|
||||
return 'Auto-Lock Timer (minutes)';
|
||||
case 'autoLockTimeLimitDescription':
|
||||
return 'Set the idle time in minutes before MetaMask will become locked.';
|
||||
case 'syncWithThreeBox':
|
||||
return 'Sync data with 3Box (experimental)';
|
||||
case 'syncWithThreeBoxDescription':
|
||||
return 'Turn on to have your settings backed up with 3Box. This feature is currently experimental; use at your own risk.';
|
||||
case 'ipfsGateway':
|
||||
return 'IPFS Gateway';
|
||||
case 'ipfsGatewayDescription':
|
||||
return 'Enter the URL of the IPFS CID gateway to use for ENS content resolution.';
|
||||
case 'preferredLedgerConnectionType':
|
||||
return 'Preferred Ledger Connection Type';
|
||||
case 'dismissReminderField':
|
||||
return 'Dismiss Secret Recovery Phrase backup reminder';
|
||||
case 'dismissReminderDescriptionField':
|
||||
return 'Turn this on to dismiss the Secret Recovery Phrase backup reminder message. We highly recommend that you back up your Secret Recovery Phrase to avoid loss of funds';
|
||||
case 'Contacts':
|
||||
return 'Contacts';
|
||||
case 'securityAndPrivacy':
|
||||
return 'Security & Privacy';
|
||||
case 'revealSeedWords':
|
||||
return 'Reveal Secret Recovery Phrase';
|
||||
case 'showIncomingTransactions':
|
||||
return 'Show Incoming Transactions';
|
||||
case 'showIncomingTransactionsDescription':
|
||||
return 'Select this to use Etherscan to show incoming transactions in the transactions list';
|
||||
case 'usePhishingDetection':
|
||||
return 'Use Phishing Detection';
|
||||
case 'usePhishingDetectionDescription':
|
||||
return 'Display a warning for phishing domains targeting Ethereum users';
|
||||
case 'participateInMetaMetrics':
|
||||
return 'Participate in MetaMetrics';
|
||||
case 'participateInMetaMetricsDescription':
|
||||
return 'Participate in MetaMetrics to help us make MetaMask better';
|
||||
case 'alerts':
|
||||
return 'Alerts';
|
||||
case 'alertSettingsUnconnectedAccount':
|
||||
return 'Browsing a website with an unconnected account selected';
|
||||
case 'alertSettingsWeb3ShimUsage':
|
||||
return 'When a website tries to use the removed window.web3 API';
|
||||
case 'networks':
|
||||
return 'Networks';
|
||||
case 'mainnet':
|
||||
return 'Ethereum Mainnet';
|
||||
case 'ropsten':
|
||||
return 'Ropsten Test Network';
|
||||
case 'rinkeby':
|
||||
return 'Rinkeby Test Network';
|
||||
case 'goerli':
|
||||
return 'Goerli Test Network';
|
||||
case 'kovan':
|
||||
return 'Kovan Test Network';
|
||||
case 'localhost':
|
||||
return 'Localhost 8545';
|
||||
case 'experimental':
|
||||
return 'Experimental';
|
||||
case 'useTokenDetection':
|
||||
return 'Use Token Detection';
|
||||
case 'useTokenDetectionDescription':
|
||||
return 'We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you don’t want MetaMask to pull data from those services.';
|
||||
case 'enableOpenSeaAPI':
|
||||
return 'Enable OpenSea API';
|
||||
case 'enableOpenSeaAPIDescription':
|
||||
return 'Use OpenSea API to fetch NFT data.NFT auto - detection relies on OpenSea API, and will not be available when this is turned off.';
|
||||
case 'useCollectibleDetection':
|
||||
return 'Autodetect NFTs';
|
||||
case 'useCollectibleDetectionDescription':
|
||||
return 'Displaying NFTs media & data may expose your IP address to centralized servers. Third-party APIs (like OpenSea) are used to detect NFTs in your wallet. This exposes your account address with those services. Leave this disabled if you don’t want the app to pull data from those those services.';
|
||||
case 'about':
|
||||
return 'About';
|
||||
case 'metamaskVersion':
|
||||
return 'MetaMask Version';
|
||||
case 'builtAroundTheWorld':
|
||||
return 'MetaMask is designed and built around the world.';
|
||||
case 'links':
|
||||
return 'Links';
|
||||
case 'privacyMsg':
|
||||
return 'Privacy Policy';
|
||||
case 'terms':
|
||||
return 'Terms of Use';
|
||||
case 'attributions':
|
||||
return 'Attributions';
|
||||
case 'supportCenter':
|
||||
return 'Visit our Support Center';
|
||||
case 'visitWebSite':
|
||||
return 'Visit our web site';
|
||||
case 'contactUs':
|
||||
return 'Contact us';
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
describe('Settings Search Utils', () => {
|
||||
describe('getSettingsRoutes', () => {
|
||||
it('should get all settings', () => {
|
||||
const settingsListExcepted = [
|
||||
{
|
||||
description: '',
|
||||
image: 'general-icon.svg',
|
||||
route: '/settings/general#currency-conversion',
|
||||
section: 'Currency Conversion',
|
||||
tab: 'General',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency.',
|
||||
image: 'general-icon.svg',
|
||||
route: '/settings/general#primary-currency',
|
||||
section: 'Primary Currenc',
|
||||
tab: 'General',
|
||||
},
|
||||
{
|
||||
description: '',
|
||||
image: 'general-icon.svg',
|
||||
route: '/settings/general#current-language',
|
||||
section: 'Current Language',
|
||||
tab: 'General',
|
||||
},
|
||||
{
|
||||
description: '',
|
||||
image: 'general-icon.svg',
|
||||
route: '/settings/general#account-identicon',
|
||||
section: 'Current Language"',
|
||||
tab: 'General',
|
||||
},
|
||||
{
|
||||
description: '',
|
||||
image: 'general-icon.svg',
|
||||
route: '/settings/general#zero-balancetokens',
|
||||
section: 'Hide Tokens Without Balance',
|
||||
tab: 'General',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'State logs contain your public account addresses and sent transactions.',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#state-logs',
|
||||
section: 'State Logs',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description: '',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#sync-withmobile',
|
||||
section: 'Sync with mobile',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Resetting your account will clear your transaction history. This will not change the balances in your accounts or require you to re-enter your Secret Recovery Phrase.',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#reset-account',
|
||||
section: 'Reset Account',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Select this to show gas price and limit controls directly on the send and confirm screens.',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#advanced-gascontrols',
|
||||
section: 'Advanced gas controls',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Select this to show the hex data field on the send screen',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#show-hexdata',
|
||||
section: 'Show Hex Data',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description: 'Select this to show fiat conversion on test network',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#conversion-testnetworks',
|
||||
section: 'Show Conversion on test networks',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description: 'Select this to show test networks in network list',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#show-testnets',
|
||||
section: 'Show test networks',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Turn this on to change the nonce (transaction number) on confirmation screens. This is an advanced feature, use cautiously.',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#customize-nonce',
|
||||
section: 'Customize transaction nonce',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Set the idle time in minutes before MetaMask will become locked.',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#autolock-timer',
|
||||
section: 'Auto-Lock Timer (minutes)',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Turn on to have your settings backed up with 3Box. This feature is currently experimental; use at your own risk.',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#sync-with3box',
|
||||
section: 'Sync data with 3Box (experimental)',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Enter the URL of the IPFS CID gateway to use for ENS content resolution.',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#ipfs-gateway',
|
||||
section: 'IPFS Gateway',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description: 'Preferred Ledger Connection Type',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#ledger-connection',
|
||||
section: 'Preferred Ledger Connection Type',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Turn this on to dismiss the Secret Recovery Phrase backup reminder message. We highly recommend that you back up your Secret Recovery Phrase to avoid loss of funds',
|
||||
image: 'advanced-icon.svg',
|
||||
route: '/settings/advanced#dimiss-secretrecovery',
|
||||
section: 'Dismiss Secret Recovery Phrase backup reminder',
|
||||
tab: 'Advanced',
|
||||
},
|
||||
{
|
||||
description: '',
|
||||
image: 'contacts-icon.svg',
|
||||
route: '/settings/contact-list',
|
||||
section: '',
|
||||
tab: '',
|
||||
},
|
||||
{
|
||||
description: 'Reveal Secret Recovery Phrase',
|
||||
image: 'security-icon.svg',
|
||||
route: '/settings/security#reveal-secretrecovery',
|
||||
section: 'Reveal Secret Recovery Phrase',
|
||||
tab: 'Security & Privacy',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Select this to use Etherscan to show incoming transactions in the transactions list',
|
||||
image: 'security-icon.svg',
|
||||
route: '/settings/security#incoming-transaction',
|
||||
section: 'Show Incoming Transactions',
|
||||
tab: 'Security & Privacy',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Display a warning for phishing domains targeting Ethereum users',
|
||||
image: 'security-icon.svg',
|
||||
route: '/settings/security#phishing-detection',
|
||||
section: 'Use Phishing Detection',
|
||||
tab: 'Security & Privacy',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Participate in MetaMetrics to help us make MetaMask better',
|
||||
image: 'security-icon.svg',
|
||||
route: '/settings/security#metrametrics',
|
||||
section: 'Participate in MetaMetrics',
|
||||
tab: 'Security & Privacy',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Browsing a website with an unconnected account selected',
|
||||
image: 'alerts-icon.svg',
|
||||
route: '/settings/alerts#unconnected-account',
|
||||
section: 'Browsing a website with an unconnected account selected',
|
||||
tab: 'Alerts',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'When a website tries to use the removed window.web3 API',
|
||||
image: 'alerts-icon.svg',
|
||||
route: '/settings/alerts#web3-shimusage',
|
||||
section: 'When a website tries to use the removed window.web3 API',
|
||||
tab: 'Alerts',
|
||||
},
|
||||
{
|
||||
description: 'Ethereum Mainnet',
|
||||
image: 'network-icon.svg',
|
||||
route: '/settings/networks#networks-mainnet',
|
||||
section: 'Ethereum Mainnet',
|
||||
tab: 'Networks',
|
||||
},
|
||||
{
|
||||
description: 'Ropsten Test Network',
|
||||
image: 'network-icon.svg',
|
||||
route: '/settings/networks#networks-ropsten',
|
||||
section: 'Ropsten Test Network',
|
||||
tab: 'Networks',
|
||||
},
|
||||
{
|
||||
description: 'Rinkeby Test Network',
|
||||
image: 'network-icon.svg',
|
||||
route: '/settings/networks#networks-rinkeby',
|
||||
section: 'Rinkeby Test Network',
|
||||
tab: 'Networks',
|
||||
},
|
||||
{
|
||||
description: 'Goerli Test Network',
|
||||
image: 'network-icon.svg',
|
||||
route: '/settings/networks#networks-goerli',
|
||||
section: 'Goerli Test Network',
|
||||
tab: 'Networks',
|
||||
},
|
||||
{
|
||||
description: 'Kovan Test Network',
|
||||
image: 'network-icon.svg',
|
||||
route: '/settings/networks#networtks-kovan',
|
||||
section: 'Kovan Test Network',
|
||||
tab: 'Networks',
|
||||
},
|
||||
{
|
||||
description: 'Localhost 8545',
|
||||
image: 'network-icon.svg',
|
||||
route: '/settings/networks#network-localhost',
|
||||
section: 'Localhost 8545',
|
||||
tab: 'Networks',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'We use third-party APIs to detect and display new tokens sent to your wallet. Turn off if you don’t want MetaMask to pull data from those services.',
|
||||
image: 'experimental-icon.svg',
|
||||
route: '/settings/experimental#token-description',
|
||||
section: 'Use Token Detection',
|
||||
tab: 'Experimental',
|
||||
},
|
||||
{
|
||||
description: 'MetaMask is designed and built around the world.',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#version',
|
||||
section: 'MetaMask Version',
|
||||
tab: 'About',
|
||||
},
|
||||
{
|
||||
description: '',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#links',
|
||||
section: 'Links',
|
||||
tab: 'About',
|
||||
},
|
||||
{
|
||||
description: 'Privacy Policy',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#privacy-policy',
|
||||
section: 'Privacy Policy',
|
||||
tab: 'About',
|
||||
},
|
||||
{
|
||||
description: 'Terms of Use',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#terms',
|
||||
section: 'Terms of Use',
|
||||
tab: 'About',
|
||||
},
|
||||
{
|
||||
description: 'Attributions',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#attributions',
|
||||
section: 'Attributions',
|
||||
tab: 'About',
|
||||
},
|
||||
{
|
||||
description: 'Visit our Support Center',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#supportcenter',
|
||||
section: 'Visit our Support Center',
|
||||
tab: 'About',
|
||||
},
|
||||
{
|
||||
description: 'Visit our web site',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#visitwebsite',
|
||||
section: 'Visit our web site',
|
||||
tab: 'About',
|
||||
},
|
||||
{
|
||||
description: 'Contact us',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#contactus',
|
||||
section: 'Contact us',
|
||||
tab: 'About',
|
||||
},
|
||||
];
|
||||
expect(getSettingsRoutes(t)).toStrictEqual(settingsListExcepted);
|
||||
});
|
||||
|
||||
it('should not get all settings', () => {
|
||||
const settingsListExcepted = [
|
||||
{
|
||||
description: '',
|
||||
image: 'general-icon.svg',
|
||||
route: '/settings/general#currency-conversion',
|
||||
section: 'Currency Conversion',
|
||||
tab: 'General',
|
||||
},
|
||||
{
|
||||
description: 'Contact us',
|
||||
image: 'info-icon.svg',
|
||||
route: '/settings/about-us#contactus',
|
||||
section: 'Contact us',
|
||||
tab: 'About',
|
||||
},
|
||||
];
|
||||
expect(getSettingsRoutes(t)).not.toStrictEqual(settingsListExcepted);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSettingsSectionNumber', () => {
|
||||
it('should get good general section number', () => {
|
||||
expect(getSettingsSectionNumber(t, t('general'))).toStrictEqual(5);
|
||||
});
|
||||
|
||||
it('should get good advanced section number', () => {
|
||||
expect(getSettingsSectionNumber(t, t('advanced'))).toStrictEqual(13);
|
||||
});
|
||||
|
||||
it('should get good contact section number', () => {
|
||||
expect(getSettingsSectionNumber(t, t('contacts'))).toStrictEqual(1);
|
||||
});
|
||||
|
||||
it('should get good security & privacy section number', () => {
|
||||
expect(
|
||||
getSettingsSectionNumber(t, t('securityAndPrivacy')),
|
||||
).toStrictEqual(4);
|
||||
});
|
||||
|
||||
it('should get good alerts section number', () => {
|
||||
expect(getSettingsSectionNumber(t, t('alerts'))).toStrictEqual(2);
|
||||
});
|
||||
|
||||
it('should get good network section number', () => {
|
||||
expect(getSettingsSectionNumber(t, t('networks'))).toStrictEqual(6);
|
||||
});
|
||||
|
||||
it('should get good experimental section number', () => {
|
||||
expect(getSettingsSectionNumber(t, t('experimental'))).toStrictEqual(1);
|
||||
});
|
||||
|
||||
it('should get good about section number', () => {
|
||||
expect(getSettingsSectionNumber(t, t('about'))).toStrictEqual(8);
|
||||
});
|
||||
});
|
||||
|
||||
// Can't be tested without DOM element
|
||||
describe('handleSettingsRefs', () => {
|
||||
it('should handle general refs', () => {
|
||||
const settingsRefs = Array(getSettingsSectionNumber(t, t('general')))
|
||||
.fill(undefined)
|
||||
.map(() => {
|
||||
return React.createRef();
|
||||
});
|
||||
expect(handleSettingsRefs(t, t('general'), settingsRefs)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
@ -12,6 +12,10 @@ import Dialog from '../../../components/ui/dialog';
|
||||
import { getPlatform } from '../../../../app/scripts/lib/util';
|
||||
|
||||
import { PLATFORM_FIREFOX } from '../../../../shared/constants/app';
|
||||
import {
|
||||
getSettingsSectionNumber,
|
||||
handleSettingsRefs,
|
||||
} from '../../../helpers/utils/settings-search';
|
||||
|
||||
import {
|
||||
LEDGER_TRANSPORT_TYPES,
|
||||
@ -61,13 +65,22 @@ export default class AdvancedTab extends PureComponent {
|
||||
showLedgerTransportWarning: false,
|
||||
};
|
||||
|
||||
showTestNetworksRef = React.createRef();
|
||||
settingsRefs = Array(
|
||||
getSettingsSectionNumber(this.context.t, this.context.t('advanced')),
|
||||
)
|
||||
.fill(undefined)
|
||||
.map(() => {
|
||||
return React.createRef();
|
||||
});
|
||||
|
||||
componentDidUpdate() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('advanced'), this.settingsRefs);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (window.location.hash.match(/show-testnets/u)) {
|
||||
this.showTestNetworksRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
this.showTestNetworksRef.current.focus();
|
||||
}
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('advanced'), this.settingsRefs);
|
||||
}
|
||||
|
||||
renderMobileSync() {
|
||||
@ -76,6 +89,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[1]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-mobile-sync"
|
||||
>
|
||||
@ -106,6 +120,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[0]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-state-logs"
|
||||
>
|
||||
@ -144,6 +159,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[2]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-reset-account"
|
||||
>
|
||||
@ -185,6 +201,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[4]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-hex-data"
|
||||
>
|
||||
@ -214,6 +231,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[3]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-advanced-gas-inline"
|
||||
>
|
||||
@ -243,7 +261,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.showTestNetworksRef}
|
||||
ref={this.settingsRefs[6]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-show-testnet-conversion"
|
||||
>
|
||||
@ -276,6 +294,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[5]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-show-testnet-conversion"
|
||||
>
|
||||
@ -307,6 +326,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[7]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-custom-nonce"
|
||||
>
|
||||
@ -355,6 +375,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[8]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-auto-lock"
|
||||
>
|
||||
@ -411,6 +432,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[9]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-3box"
|
||||
>
|
||||
@ -479,7 +501,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
: LEDGER_TRANSPORT_NAMES.U2F;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[11]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('preferredLedgerConnectionType')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
@ -578,6 +600,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[10]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-ipfs-gateway"
|
||||
>
|
||||
@ -622,6 +645,7 @@ export default class AdvancedTab extends PureComponent {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.settingsRefs[12]}
|
||||
className="settings-page__content-row"
|
||||
data-testid="advanced-setting-dismiss-reminder"
|
||||
>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
@ -8,14 +8,23 @@ import ToggleButton from '../../../components/ui/toggle-button';
|
||||
import { setAlertEnabledness } from '../../../store/actions';
|
||||
import { getAlertEnabledness } from '../../../ducks/metamask/metamask';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { handleHooksSettingsRefs } from '../../../helpers/utils/settings-search';
|
||||
|
||||
const AlertSettingsEntry = ({ alertId, description, title }) => {
|
||||
const AlertSettingsEntry = ({ alertId, description, title, alertIndex }) => {
|
||||
const t = useI18nContext();
|
||||
const settingsRefs = useRef(alertIndex);
|
||||
|
||||
useEffect(() => {
|
||||
handleHooksSettingsRefs(t, t('alerts'), settingsRefs, alertIndex);
|
||||
}, [settingsRefs, t, alertIndex]);
|
||||
|
||||
const isEnabled = useSelector((state) => getAlertEnabledness(state)[alertId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={settingsRefs} className="alerts-tab__item">
|
||||
<span>{title}</span>
|
||||
<div className="alerts-tab__description-container">
|
||||
<Tooltip
|
||||
position="top"
|
||||
title={description}
|
||||
@ -29,6 +38,8 @@ const AlertSettingsEntry = ({ alertId, description, title }) => {
|
||||
onToggle={() => setAlertEnabledness(alertId, !isEnabled)}
|
||||
value={isEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -37,6 +48,7 @@ AlertSettingsEntry.propTypes = {
|
||||
alertId: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
alertIndex: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const AlertsTab = () => {
|
||||
@ -55,14 +67,17 @@ const AlertsTab = () => {
|
||||
|
||||
return (
|
||||
<div className="alerts-tab__body">
|
||||
{Object.entries(alertConfig).map(([alertId, { title, description }]) => (
|
||||
{Object.entries(alertConfig).map(
|
||||
([alertId, { title, description }], index) => (
|
||||
<AlertSettingsEntry
|
||||
alertId={alertId}
|
||||
description={description}
|
||||
key={alertId}
|
||||
title={title}
|
||||
alertIndex={index}
|
||||
/>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -6,28 +6,26 @@
|
||||
grid-template-columns: 8fr 30px max-content;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
align-items: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__body > * {
|
||||
border-bottom: 1px solid var(--Grey-100);
|
||||
padding: 16px 8px;
|
||||
height: 100%;
|
||||
&__description-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__body > :first-child {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
&__body > :nth-child(3n+4) {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
&__body > :nth-child(3n) {
|
||||
padding-right: 32px;
|
||||
&__description-container > * {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__item {
|
||||
border-bottom: 1px solid var(--Grey-100);
|
||||
padding: 16px 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ import {
|
||||
CONTACT_VIEW_ROUTE,
|
||||
} from '../../../helpers/constants/routes';
|
||||
import Button from '../../../components/ui/button';
|
||||
import {
|
||||
getSettingsSectionNumber,
|
||||
handleSettingsRefs,
|
||||
} from '../../../helpers/utils/settings-search';
|
||||
import EditContact from './edit-contact';
|
||||
import AddContact from './add-contact';
|
||||
import ViewContact from './view-contact';
|
||||
@ -27,6 +31,24 @@ export default class ContactListTab extends Component {
|
||||
hideAddressBook: PropTypes.bool,
|
||||
};
|
||||
|
||||
settingsRefs = Array(
|
||||
getSettingsSectionNumber(this.context.t, this.context.t('contacts')),
|
||||
)
|
||||
.fill(undefined)
|
||||
.map(() => {
|
||||
return React.createRef();
|
||||
});
|
||||
|
||||
componentDidUpdate() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('contacts'), this.settingsRefs);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('contacts'), this.settingsRefs);
|
||||
}
|
||||
|
||||
renderAddresses() {
|
||||
const { addressBook, history, selectedAddress } = this.props;
|
||||
const contacts = addressBook.filter(({ name }) => Boolean(name));
|
||||
@ -124,7 +146,11 @@ export default class ContactListTab extends Component {
|
||||
const { hideAddressBook } = this.props;
|
||||
|
||||
if (!hideAddressBook) {
|
||||
return <div className="address-book">{this.renderAddresses()}</div>;
|
||||
return (
|
||||
<div ref={this.settingsRefs[0]} className="address-book">
|
||||
{this.renderAddresses()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ToggleButton from '../../../components/ui/toggle-button';
|
||||
import {
|
||||
getSettingsSectionNumber,
|
||||
handleSettingsRefs,
|
||||
} from '../../../helpers/utils/settings-search';
|
||||
|
||||
export default class ExperimentalTab extends PureComponent {
|
||||
static contextTypes = {
|
||||
@ -19,12 +23,30 @@ export default class ExperimentalTab extends PureComponent {
|
||||
setEIP1559V2Enabled: PropTypes.func,
|
||||
};
|
||||
|
||||
settingsRefs = Array(
|
||||
getSettingsSectionNumber(this.context.t, this.context.t('experimental')),
|
||||
)
|
||||
.fill(undefined)
|
||||
.map(() => {
|
||||
return React.createRef();
|
||||
});
|
||||
|
||||
componentDidUpdate() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('experimental'), this.settingsRefs);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('experimental'), this.settingsRefs);
|
||||
}
|
||||
|
||||
renderTokenDetectionToggle() {
|
||||
const { t } = this.context;
|
||||
const { useTokenDetection, setUseTokenDetection } = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[0]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('useTokenDetection')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
@ -68,7 +90,10 @@ export default class ExperimentalTab extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row--dependent">
|
||||
<div
|
||||
ref={this.settingsRefs[2]}
|
||||
className="settings-page__content-row--dependent"
|
||||
>
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('useCollectibleDetection')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
@ -114,7 +139,10 @@ export default class ExperimentalTab extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row--parent">
|
||||
<div
|
||||
ref={this.settingsRefs[1]}
|
||||
className="settings-page__content-row--parent"
|
||||
>
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('enableOpenSeaAPI')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
|
@ -13,12 +13,30 @@
|
||||
flex-flow: column nowrap;
|
||||
|
||||
&__header {
|
||||
padding: 8px 24px 8px 24px;
|
||||
position: relative;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
background: var(--ui-1);
|
||||
}
|
||||
|
||||
&__title-container {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 12px 24px;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&__close-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__close-button::after {
|
||||
content: '\00D7';
|
||||
font-size: 40px;
|
||||
color: var(--ui-4);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include H3;
|
||||
|
||||
@ -33,6 +51,102 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__search {
|
||||
@media screen and (max-width: $break-small) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
position: absolute;
|
||||
right: 57px;
|
||||
top: 10px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-midpoint) {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
background: var(--ui-white);
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 14px rgba(0, 0, 0, 0.18);
|
||||
border-radius: 6px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
|
||||
> div {
|
||||
&:hover {
|
||||
background: var(--ui-1);
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
transition: 200ms ease-in-out;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-top: 1px solid var(--ui-2);
|
||||
cursor: pointer;
|
||||
grid-template-columns: 16px minmax(20px, max-content) 8px auto;
|
||||
gap: 8px;
|
||||
|
||||
&__icon {
|
||||
background: --ui-2;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&__request,
|
||||
&__tab,
|
||||
&__section,
|
||||
&__no-matching {
|
||||
@include H6;
|
||||
|
||||
color: var(--ui-4);
|
||||
}
|
||||
|
||||
&__tab-multiple-lines {
|
||||
@media screen and (max-width: $break-small) {
|
||||
width: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
&__section-multiple-lines {
|
||||
@media screen and (max-width: $break-small) {
|
||||
width: 170px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__caret {
|
||||
background-image: url('/images/caret-right.svg');
|
||||
width: 8px;
|
||||
height: 10px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin: 0 4px;
|
||||
|
||||
[dir='rtl'] & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
@include H6;
|
||||
|
||||
display: inline;
|
||||
color: var(--primary-blue);
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__subheader,
|
||||
&__subheader--link {
|
||||
@include H4;
|
||||
@ -110,17 +224,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__close-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__close-button::after {
|
||||
content: '\00D7';
|
||||
font-size: 40px;
|
||||
color: var(--ui-4);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
|
@ -6,6 +6,10 @@ import {
|
||||
SUPPORT_REQUEST_LINK,
|
||||
} from '../../../helpers/constants/common';
|
||||
import { isBeta } from '../../../helpers/utils/build-types';
|
||||
import {
|
||||
getSettingsSectionNumber,
|
||||
handleSettingsRefs,
|
||||
} from '../../../helpers/utils/settings-search';
|
||||
|
||||
export default class InfoTab extends PureComponent {
|
||||
state = {
|
||||
@ -16,13 +20,33 @@ export default class InfoTab extends PureComponent {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
settingsRefs = Array(
|
||||
getSettingsSectionNumber(this.context.t, this.context.t('about')),
|
||||
)
|
||||
.fill(undefined)
|
||||
.map(() => {
|
||||
return React.createRef();
|
||||
});
|
||||
|
||||
componentDidUpdate() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('about'), this.settingsRefs);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('about'), this.settingsRefs);
|
||||
}
|
||||
|
||||
renderInfoLinks() {
|
||||
const { t } = this.context;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-item settings-page__content-item--without-height">
|
||||
<div className="info-tab__link-header">{t('links')}</div>
|
||||
<div className="info-tab__link-item">
|
||||
<div ref={this.settingsRefs[1]} className="info-tab__link-header">
|
||||
{t('links')}
|
||||
</div>
|
||||
<div ref={this.settingsRefs[2]} className="info-tab__link-item">
|
||||
<Button
|
||||
type="link"
|
||||
href="https://metamask.io/privacy.html"
|
||||
@ -33,7 +57,7 @@ export default class InfoTab extends PureComponent {
|
||||
{t('privacyMsg')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="info-tab__link-item">
|
||||
<div ref={this.settingsRefs[3]} className="info-tab__link-item">
|
||||
<Button
|
||||
type="link"
|
||||
href="https://metamask.io/terms.html"
|
||||
@ -44,7 +68,7 @@ export default class InfoTab extends PureComponent {
|
||||
{t('terms')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="info-tab__link-item">
|
||||
<div ref={this.settingsRefs[4]} className="info-tab__link-item">
|
||||
<Button
|
||||
type="link"
|
||||
href="https://metamask.io/attributions.html"
|
||||
@ -56,7 +80,7 @@ export default class InfoTab extends PureComponent {
|
||||
</Button>
|
||||
</div>
|
||||
<hr className="info-tab__separator" />
|
||||
<div className="info-tab__link-item">
|
||||
<div ref={this.settingsRefs[5]} className="info-tab__link-item">
|
||||
<Button
|
||||
type="link"
|
||||
href={SUPPORT_LINK}
|
||||
@ -67,7 +91,7 @@ export default class InfoTab extends PureComponent {
|
||||
{t('supportCenter')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="info-tab__link-item">
|
||||
<div ref={this.settingsRefs[6]} className="info-tab__link-item">
|
||||
<Button
|
||||
type="link"
|
||||
href="https://metamask.io/"
|
||||
@ -78,7 +102,7 @@ export default class InfoTab extends PureComponent {
|
||||
{t('visitWebSite')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="info-tab__link-item">
|
||||
<div ref={this.settingsRefs[7]} className="info-tab__link-item">
|
||||
<Button
|
||||
type="link"
|
||||
href={SUPPORT_REQUEST_LINK}
|
||||
@ -101,7 +125,10 @@ export default class InfoTab extends PureComponent {
|
||||
<div className="settings-page__content-row">
|
||||
<div className="settings-page__content-item settings-page__content-item--without-height">
|
||||
<div className="info-tab__item">
|
||||
<div className="info-tab__version-header">
|
||||
<div
|
||||
ref={this.settingsRefs[0]}
|
||||
className="info-tab__version-header"
|
||||
>
|
||||
{isBeta() ? t('betaMetamaskVersion') : t('metamaskVersion')}
|
||||
</div>
|
||||
<div className="info-tab__version-number">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@ -14,7 +14,14 @@ import { getEnvironmentType } from '../../../../../app/scripts/lib/util';
|
||||
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../shared/constants/app';
|
||||
import { getProvider } from '../../../../selectors';
|
||||
|
||||
const NetworksListItem = ({ network, networkIsSelected, selectedRpcUrl }) => {
|
||||
import { handleHooksSettingsRefs } from '../../../../helpers/utils/settings-search';
|
||||
|
||||
const NetworksListItem = ({
|
||||
network,
|
||||
networkIsSelected,
|
||||
selectedRpcUrl,
|
||||
networkIndex,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
@ -37,9 +44,15 @@ const NetworksListItem = ({ network, networkIsSelected, selectedRpcUrl }) => {
|
||||
(listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType);
|
||||
const displayNetworkListItemAsSelected =
|
||||
listItemNetworkIsSelected || listItemNetworkIsCurrentProvider;
|
||||
const settingsRefs = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
handleHooksSettingsRefs(t, t('networks'), settingsRefs, networkIndex);
|
||||
}, [networkIndex, settingsRefs, t]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={settingsRefs}
|
||||
key={`settings-network-list-item:${rpcUrl}`}
|
||||
className="networks-tab__networks-list-item"
|
||||
onClick={() => {
|
||||
@ -76,6 +89,7 @@ NetworksListItem.propTypes = {
|
||||
network: PropTypes.object.isRequired,
|
||||
networkIsSelected: PropTypes.bool,
|
||||
selectedRpcUrl: PropTypes.string,
|
||||
networkIndex: PropTypes.number,
|
||||
};
|
||||
|
||||
export default NetworksListItem;
|
||||
|
@ -16,12 +16,13 @@ const NetworksList = ({
|
||||
networkIsSelected && !networkDefaultedToProvider,
|
||||
})}
|
||||
>
|
||||
{networksToRender.map((network) => (
|
||||
{networksToRender.map((network, index) => (
|
||||
<NetworksListItem
|
||||
key={`settings-network-list:${network.rpcUrl}`}
|
||||
network={network}
|
||||
networkIsSelected={networkIsSelected}
|
||||
selectedRpcUrl={selectedRpcUrl}
|
||||
networkIndex={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -3,6 +3,10 @@ import PropTypes from 'prop-types';
|
||||
import ToggleButton from '../../../components/ui/toggle-button';
|
||||
import { REVEAL_SEED_ROUTE } from '../../../helpers/constants/routes';
|
||||
import Button from '../../../components/ui/button';
|
||||
import {
|
||||
getSettingsSectionNumber,
|
||||
handleSettingsRefs,
|
||||
} from '../../../helpers/utils/settings-search';
|
||||
|
||||
export default class SecurityTab extends PureComponent {
|
||||
static contextTypes = {
|
||||
@ -21,12 +25,33 @@ export default class SecurityTab extends PureComponent {
|
||||
usePhishDetect: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
settingsRefs = Array(
|
||||
getSettingsSectionNumber(
|
||||
this.context.t,
|
||||
this.context.t('securityAndPrivacy'),
|
||||
),
|
||||
)
|
||||
.fill(undefined)
|
||||
.map(() => {
|
||||
return React.createRef();
|
||||
});
|
||||
|
||||
componentDidUpdate() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('securityAndPrivacy'), this.settingsRefs);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('securityAndPrivacy'), this.settingsRefs);
|
||||
}
|
||||
|
||||
renderSeedWords() {
|
||||
const { t } = this.context;
|
||||
const { history } = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[0]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('revealSeedWords')}</span>
|
||||
</div>
|
||||
@ -63,7 +88,7 @@ export default class SecurityTab extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[3]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('participateInMetaMetrics')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
@ -92,7 +117,7 @@ export default class SecurityTab extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[1]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('showIncomingTransactions')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
@ -120,7 +145,7 @@ export default class SecurityTab extends PureComponent {
|
||||
const { usePhishDetect, setUsePhishDetect } = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[2]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('usePhishingDetection')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
|
3
ui/pages/settings/settings-search-list/index.js
Normal file
3
ui/pages/settings/settings-search-list/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import SettingsSearchList from './settings-search-list';
|
||||
|
||||
export default SettingsSearchList;
|
101
ui/pages/settings/settings-search-list/settings-search-list.js
Normal file
101
ui/pages/settings/settings-search-list/settings-search-list.js
Normal file
@ -0,0 +1,101 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { highlightSearchedText } from '../../../helpers/utils/settings-search';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
|
||||
export default function SettingsSearchList({ results, onClickSetting }) {
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
useEffect(() => {
|
||||
results.forEach((_, i) => {
|
||||
highlightSearchedText(i);
|
||||
});
|
||||
}, [results]);
|
||||
|
||||
return (
|
||||
<div className="settings-page__header__search__list">
|
||||
{Array(5)
|
||||
.fill(undefined)
|
||||
.map((_, i) => {
|
||||
const { image, tab, section } = results[i] || {};
|
||||
|
||||
return (
|
||||
Boolean(image || tab || section) && (
|
||||
<div key={`settings_${i}`}>
|
||||
<div
|
||||
key={`res_${i}`}
|
||||
className="settings-page__header__search__list__item"
|
||||
onClick={() => onClickSetting(results[i])}
|
||||
>
|
||||
<img
|
||||
className="settings-page__header__search__list__item__icon"
|
||||
src={`./images/${image}`}
|
||||
/>
|
||||
|
||||
<span
|
||||
id={`menu-tab_${i}`}
|
||||
className={classnames(
|
||||
'settings-page__header__search__list__item__tab',
|
||||
{
|
||||
'settings-page__header__search__list__item__tab-multiple-lines':
|
||||
tab === t('securityAndPrivacy'),
|
||||
},
|
||||
)}
|
||||
>
|
||||
{tab}
|
||||
</span>
|
||||
<div className="settings-page__header__search__list__item__caret" />
|
||||
<span
|
||||
id={`menu-section_${i}`}
|
||||
className={classnames(
|
||||
'settings-page__header__search__list__item__section',
|
||||
{
|
||||
'settings-page__header__search__list__item__section-multiple-lines':
|
||||
tab === t('securityAndPrivacy') ||
|
||||
tab === t('alerts'),
|
||||
},
|
||||
)}
|
||||
>
|
||||
{section}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
{results.length === 0 && (
|
||||
<div
|
||||
className="settings-page__header__search__list__item"
|
||||
style={{ cursor: 'auto', display: 'flex' }}
|
||||
>
|
||||
<span className="settings-page__header__search__list__item__no-matching">
|
||||
{t('settingsSearchMatchingNotFound')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="settings-page__header__search__list__item"
|
||||
style={{ cursor: 'auto', display: 'flex' }}
|
||||
>
|
||||
<span className="settings-page__header__search__list__item__request">
|
||||
{t('missingSetting')}
|
||||
</span>
|
||||
<a
|
||||
href="https://community.metamask.io/c/feature-requests-ideas/13"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="need-help-link"
|
||||
className="settings-page__header__search__list__item__link"
|
||||
>
|
||||
{t('missingSettingRequest')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsSearchList.propTypes = {
|
||||
results: PropTypes.array,
|
||||
onClickSetting: PropTypes.func,
|
||||
};
|
3
ui/pages/settings/settings-search/index.js
Normal file
3
ui/pages/settings/settings-search/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import SettingsSearch from './settings-search';
|
||||
|
||||
export default SettingsSearch;
|
105
ui/pages/settings/settings-search/settings-search.js
Normal file
105
ui/pages/settings/settings-search/settings-search.js
Normal file
@ -0,0 +1,105 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Fuse from 'fuse.js';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import TextField from '../../../components/ui/text-field';
|
||||
import { isEqualCaseInsensitive } from '../../../helpers/utils/util';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import SearchIcon from '../../../components/ui/search-icon';
|
||||
|
||||
export default function SettingsSearch({
|
||||
onSearch,
|
||||
error,
|
||||
settingsRoutesList,
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchIconColor, setSearchIconColor] = useState('#9b9b9b');
|
||||
|
||||
const settingsRoutesListArray = Object.values(settingsRoutesList);
|
||||
const settingsSearchFuse = new Fuse(settingsRoutesListArray, {
|
||||
shouldSort: true,
|
||||
threshold: 0.2,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: ['tab', 'section', 'description'],
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
const handleSearch = (searchQuery) => {
|
||||
setSearchQuery(searchQuery);
|
||||
if (searchQuery === '') {
|
||||
setSearchIconColor('#9b9b9b');
|
||||
} else {
|
||||
setSearchIconColor('#24292E');
|
||||
}
|
||||
const fuseSearchResult = settingsSearchFuse.search(searchQuery);
|
||||
const addressSearchResult = settingsRoutesListArray.filter((routes) => {
|
||||
return (
|
||||
routes.tab &&
|
||||
searchQuery &&
|
||||
isEqualCaseInsensitive(routes.tab, searchQuery)
|
||||
);
|
||||
});
|
||||
|
||||
const results = [...addressSearchResult, ...fuseSearchResult];
|
||||
onSearch({ searchQuery, results });
|
||||
};
|
||||
|
||||
const renderStartAdornment = () => {
|
||||
return (
|
||||
<InputAdornment position="start" style={{ marginRight: '12px' }}>
|
||||
<SearchIcon color={searchIconColor} />
|
||||
</InputAdornment>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEndAdornment = () => {
|
||||
return (
|
||||
<>
|
||||
{searchQuery && (
|
||||
<InputAdornment
|
||||
className="imageclosectn"
|
||||
position="end"
|
||||
onClick={() => handleSearch('')}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<img
|
||||
className="imageclose"
|
||||
src="images/close-gray.svg"
|
||||
width="17"
|
||||
height="17"
|
||||
alt=""
|
||||
/>
|
||||
</InputAdornment>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
id="search-settings"
|
||||
placeholder={t('searchSettings')}
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
error={error}
|
||||
fullWidth
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
style={{ backgroundColor: '#fff' }}
|
||||
startAdornment={renderStartAdornment()}
|
||||
endAdornment={renderEndAdornment()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsSearch.propTypes = {
|
||||
onSearch: PropTypes.func,
|
||||
error: PropTypes.string,
|
||||
settingsRoutesList: PropTypes.array,
|
||||
};
|
11
ui/pages/settings/settings-search/settings-search.stories.js
Normal file
11
ui/pages/settings/settings-search/settings-search.stories.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import SettingsSearch from './settings-search';
|
||||
|
||||
export default {
|
||||
title: 'Pages/Settings/SettingsSearch',
|
||||
id: __filename,
|
||||
};
|
||||
|
||||
export const SettingsSearchComponent = () => {
|
||||
return <SettingsSearch onSearch settingsRoutesList />;
|
||||
};
|
@ -10,6 +10,11 @@ import Jazzicon from '../../../components/ui/jazzicon';
|
||||
import BlockieIdenticon from '../../../components/ui/identicon/blockieIdenticon';
|
||||
import Typography from '../../../components/ui/typography';
|
||||
|
||||
import {
|
||||
getSettingsSectionNumber,
|
||||
handleSettingsRefs,
|
||||
} from '../../../helpers/utils/settings-search';
|
||||
|
||||
const sortedCurrencies = availableCurrencies.sort((a, b) => {
|
||||
return a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase());
|
||||
});
|
||||
@ -53,6 +58,24 @@ export default class SettingsTab extends PureComponent {
|
||||
tokenList: PropTypes.object,
|
||||
};
|
||||
|
||||
settingsRefs = Array(
|
||||
getSettingsSectionNumber(this.context.t, this.context.t('general')),
|
||||
)
|
||||
.fill(undefined)
|
||||
.map(() => {
|
||||
return React.createRef();
|
||||
});
|
||||
|
||||
componentDidUpdate() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('general'), this.settingsRefs);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { t } = this.context;
|
||||
handleSettingsRefs(t, t('general'), this.settingsRefs);
|
||||
}
|
||||
|
||||
renderCurrentConversion() {
|
||||
const { t } = this.context;
|
||||
const {
|
||||
@ -62,7 +85,7 @@ export default class SettingsTab extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[0]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('currencyConversion')}</span>
|
||||
<span className="settings-page__content-description">
|
||||
@ -96,7 +119,7 @@ export default class SettingsTab extends PureComponent {
|
||||
const currentLocaleName = currentLocaleMeta ? currentLocaleMeta.name : '';
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[2]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span className="settings-page__content-label">
|
||||
{t('currentLanguage')}
|
||||
@ -124,7 +147,11 @@ export default class SettingsTab extends PureComponent {
|
||||
const { hideZeroBalanceTokens, setHideZeroBalanceTokens } = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row" id="toggle-zero-balance">
|
||||
<div
|
||||
ref={this.settingsRefs[4]}
|
||||
className="settings-page__content-row"
|
||||
id="toggle-zero-balance"
|
||||
>
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('hideZeroBalanceTokens')}</span>
|
||||
</div>
|
||||
@ -160,7 +187,11 @@ export default class SettingsTab extends PureComponent {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row" id="blockie-optin">
|
||||
<div
|
||||
ref={this.settingsRefs[3]}
|
||||
className="settings-page__content-row"
|
||||
id="blockie-optin"
|
||||
>
|
||||
<div className="settings-page__content-item">
|
||||
<Typography variant={TYPOGRAPHY.H5} color={COLORS.BLACK}>
|
||||
{t('accountIdenticon')}
|
||||
@ -238,7 +269,7 @@ export default class SettingsTab extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div ref={this.settingsRefs[1]} className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{t('primaryCurrencySetting')}</span>
|
||||
<div className="settings-page__content-description">
|
||||
|
@ -22,6 +22,8 @@ import {
|
||||
EXPERIMENTAL_ROUTE,
|
||||
ADD_NETWORK_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
import { getSettingsRoutes } from '../../helpers/utils/settings-search';
|
||||
|
||||
import SettingsTab from './settings-tab';
|
||||
import AlertsTab from './alerts-tab';
|
||||
import NetworksTab from './networks-tab';
|
||||
@ -34,6 +36,8 @@ import ExperimentalTab from './experimental-tab';
|
||||
import SnapListTab from './flask/snaps-list-tab';
|
||||
import ViewSnap from './flask/view-snap';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import SettingsSearch from './settings-search';
|
||||
import SettingsSearchList from './settings-search-list';
|
||||
|
||||
class SettingsPage extends PureComponent {
|
||||
static propTypes = {
|
||||
@ -59,6 +63,9 @@ class SettingsPage extends PureComponent {
|
||||
|
||||
state = {
|
||||
lastFetchedConversionDate: null,
|
||||
searchResults: [],
|
||||
isSearchList: false,
|
||||
searchText: '',
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -76,6 +83,15 @@ class SettingsPage extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleClickSetting(setting) {
|
||||
const { history } = this.props;
|
||||
history.push(setting.route);
|
||||
this.setState({
|
||||
searchResults: '',
|
||||
isSearchList: '',
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
history,
|
||||
@ -85,6 +101,10 @@ class SettingsPage extends PureComponent {
|
||||
addNewNetwork,
|
||||
isSnapViewPage,
|
||||
} = this.props;
|
||||
|
||||
const { searchResults, isSearchList, searchText } = this.state;
|
||||
const { t } = this.context;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('main-container settings-page', {
|
||||
@ -98,9 +118,11 @@ class SettingsPage extends PureComponent {
|
||||
onClick={() => history.push(backRoute)}
|
||||
/>
|
||||
)}
|
||||
<div className="settings-page__header__title-container">
|
||||
{this.renderTitle()}
|
||||
|
||||
<div
|
||||
className="settings-page__close-button"
|
||||
className="settings-page__header__title-container__close-button"
|
||||
onClick={() => {
|
||||
if (addNewNetwork) {
|
||||
history.push(NETWORKS_ROUTE);
|
||||
@ -110,6 +132,28 @@ class SettingsPage extends PureComponent {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="settings-page__header__search">
|
||||
<SettingsSearch
|
||||
onSearch={({ searchQuery = '', results = [] }) => {
|
||||
this.setState({
|
||||
searchResults: results,
|
||||
isSearchList: searchQuery !== '',
|
||||
searchText: searchQuery,
|
||||
});
|
||||
}}
|
||||
settingsRoutesList={getSettingsRoutes(t)}
|
||||
/>
|
||||
{isSearchList && searchText.length >= 2 && (
|
||||
<SettingsSearchList
|
||||
key=""
|
||||
results={searchResults}
|
||||
onClickSetting={(setting) => this.handleClickSetting(setting)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-page__content">
|
||||
<div className="settings-page__content__tabs">
|
||||
{this.renderTabs()}
|
||||
@ -142,7 +186,11 @@ class SettingsPage extends PureComponent {
|
||||
titleText = t('settings');
|
||||
}
|
||||
|
||||
return <div className="settings-page__header__title">{titleText}</div>;
|
||||
return (
|
||||
<div className="settings-page__header__title-container__title">
|
||||
{titleText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSubHeader() {
|
||||
|
52
ui/pages/settings/settings.component.test.js
Normal file
52
ui/pages/settings/settings.component.test.js
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import TextField from '../../components/ui/text-field';
|
||||
import Settings from './settings.container';
|
||||
import SettingsSearch from './settings-search';
|
||||
|
||||
describe('SettingsPage', () => {
|
||||
let wrapper;
|
||||
|
||||
const props = {
|
||||
isAddressEntryPage: false,
|
||||
backRoute: '/',
|
||||
currentPath: '/settings',
|
||||
location: '/settings',
|
||||
mostRecentOverviewPage: '',
|
||||
isPopup: false,
|
||||
pathnameI18nKey: undefined,
|
||||
addressName: '',
|
||||
initialBreadCrumbRoute: undefined,
|
||||
initialBreadCrumbKey: undefined,
|
||||
addNewNetwork: false,
|
||||
conversionDate: Date.now(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<Settings.WrappedComponent {...props} />, {
|
||||
context: {
|
||||
t: (str) => str,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should render title correctly', () => {
|
||||
expect(
|
||||
wrapper.find('.settings-page__header__title-container__title').text(),
|
||||
).toStrictEqual('settings');
|
||||
});
|
||||
|
||||
it('should render search correctly', () => {
|
||||
wrapper = shallow(
|
||||
<SettingsSearch onSearch={() => undefined} settingsRoutesList={[]} />,
|
||||
{
|
||||
context: {
|
||||
t: (s) => `${s}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(wrapper.find(TextField).props().id).toStrictEqual('search-settings');
|
||||
expect(wrapper.find(TextField).props().value).toStrictEqual('');
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user