1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Standardize network settings page (#9740)

* Standardize appearance of network settings

* Add separate route for network settings form

* Control network form rendering in popup via route

* Hide network form buttons when form is viewOnly

* Handle extremely long network names
This commit is contained in:
Erik Marks 2020-10-29 13:17:52 -07:00 committed by GitHub
commit 7a90766294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 195 additions and 149 deletions

View File

@ -7,8 +7,13 @@ import * as actions from '../../../store/actions'
import {
openAlert as displayInvalidCustomNetworkAlert,
} from '../../../ducks/alerts/invalid-custom-network'
import { NETWORKS_ROUTE } from '../../../helpers/constants/routes'
import { isPrefixedFormattedHexString } from '../../../../../app/scripts/lib/util'
import {
NETWORKS_ROUTE,
NETWORKS_FORM_ROUTE,
} from '../../../helpers/constants/routes'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums'
import { getEnvironmentType, isPrefixedFormattedHexString } from '../../../../../app/scripts/lib/util'
import { Dropdown, DropdownMenuItem } from './components/dropdown'
import NetworkDropdownIcon from './components/network-dropdown-icon'
@ -44,6 +49,9 @@ function mapDispatchToProps (dispatch) {
setNetworksTabAddMode: (isInAddMode) => {
dispatch(actions.setNetworksTabAddMode(isInAddMode))
},
setSelectedSettingsRpcUrl: (url) => {
dispatch(actions.setSelectedSettingsRpcUrl(url))
},
displayInvalidCustomNetworkAlert: (networkName) => {
dispatch(displayInvalidCustomNetworkAlert(networkName))
},
@ -67,6 +75,7 @@ class NetworkDropdown extends Component {
setRpcTarget: PropTypes.func.isRequired,
hideNetworkDropdown: PropTypes.func.isRequired,
setNetworksTabAddMode: PropTypes.func.isRequired,
setSelectedSettingsRpcUrl: PropTypes.func.isRequired,
frequentRpcListDetail: PropTypes.array.isRequired,
networkDropdownOpen: PropTypes.bool.isRequired,
history: PropTypes.object.isRequired,
@ -176,7 +185,11 @@ class NetworkDropdown extends Component {
}
render () {
const { provider: { type: providerType, rpcUrl: activeNetwork }, setNetworksTabAddMode } = this.props
const {
provider: { type: providerType, rpcUrl: activeNetwork },
setNetworksTabAddMode,
setSelectedSettingsRpcUrl,
} = this.props
const rpcListDetail = this.props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = {
@ -337,8 +350,13 @@ class NetworkDropdown extends Component {
<DropdownMenuItem
closeMenu={() => this.props.hideNetworkDropdown()}
onClick={() => {
this.props.history.push(
getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN
? NETWORKS_ROUTE
: NETWORKS_FORM_ROUTE,
)
setSelectedSettingsRpcUrl('')
setNetworksTabAddMode(true)
this.props.history.push(NETWORKS_ROUTE)
}}
style={dropdownMenuItemStyle}
>

View File

@ -9,6 +9,7 @@ const SECURITY_ROUTE = '/settings/security'
const ABOUT_US_ROUTE = '/settings/about-us'
const ALERTS_ROUTE = '/settings/alerts'
const NETWORKS_ROUTE = '/settings/networks'
const NETWORKS_FORM_ROUTE = '/settings/networks/form'
const CONTACT_LIST_ROUTE = '/settings/contact-list'
const CONTACT_EDIT_ROUTE = '/settings/contact-list/edit-contact'
const CONTACT_ADD_ROUTE = '/settings/contact-list/add-contact'
@ -75,6 +76,7 @@ const PATH_NAME_MAP = {
[ABOUT_US_ROUTE]: 'About Us Page',
[ALERTS_ROUTE]: 'Alerts Settings Page',
[NETWORKS_ROUTE]: 'Network Settings Page',
[NETWORKS_FORM_ROUTE]: 'Network Settings Page Form',
[CONTACT_LIST_ROUTE]: 'Contact List Settings Page',
[`${CONTACT_EDIT_ROUTE}/:address`]: 'Edit Contact Settings Page',
[CONTACT_ADD_ROUTE]: 'Add Contact Settings Page',
@ -172,6 +174,7 @@ export {
CONTACT_MY_ACCOUNTS_VIEW_ROUTE,
CONTACT_MY_ACCOUNTS_EDIT_ROUTE,
NETWORKS_ROUTE,
NETWORKS_FORM_ROUTE,
INITIALIZE_BACKUP_SEED_PHRASE_ROUTE,
CONNECT_ROUTE,
CONNECT_CONFIRM_PERMISSIONS_ROUTE,

View File

@ -8,6 +8,9 @@
@media screen and (max-width: 575px) {
margin-top: 0;
flex-direction: column;
overflow-x: hidden;
align-items: center;
}
}
@ -22,36 +25,13 @@
}
}
&__back-button {
display: none;
@media screen and (max-width: 575px) {
display: block;
background-image: url('/images/caret-left-black.svg');
width: 18px;
height: 18px;
opacity: 0.5;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
margin-right: 16px;
cursor: pointer;
position: absolute;
margin-left: 10px;
[dir='rtl'] & {
transform: rotate(180deg);
}
}
}
&__network-form {
flex: 0.5 0 auto;
max-width: 343px;
max-height: 465px;
display: flex;
flex: 1 0 auto;
flex-direction: column;
justify-content: space-between;
max-width: 343px;
max-height: 465px;
.page-container__footer {
border-top: none;
@ -78,8 +58,6 @@
&__network-form-row {
@media screen and (max-width: 575px) {
display: flex;
flex-direction: column;
width: 93%;
}
@ -88,11 +66,14 @@
background-color: #fefae8;
border: 1px solid #ffd33d;
width: 93%;
border-radius: 5px;
box-sizing: border-box;
padding: 12px;
margin: 12px 0;
@media screen and (max-width: 575px) {
width: 93%;
}
}
}
@ -118,26 +99,23 @@
max-width: 343px;
@media screen and (max-width: 575px) {
flex: 1;
overflow-y: auto;
max-width: 100vw;
width: 100vw;
overflow-y: scroll;
}
}
&__add-network-button-wrapper {
display: none;
&__networks-list-popup-footer {
width: 100%;
display: flex;
justify-content: center;
padding-top: 23px;
padding-bottom: 23px;
border-top: 1px solid #d8d8d8;
@media screen and (max-width: 575px) {
display: flex;
padding-top: 19px;
padding-bottom: 23px;
justify-content: center;
align-items: center;
border-top: 1px solid #d8d8d8;
.button {
width: 178px;
}
.button {
width: 178px;
}
}
@ -169,11 +147,16 @@
&:hover {
cursor: pointer;
}
@media screen and (max-width: 575px) {
margin: 0 4px 0 10px;
}
}
@media screen and (max-width: 575px) {
padding: 20px 23px 21px 17px;
border-bottom: 1px solid #d8d8d8;
max-width: 351px;
}
}
@ -188,6 +171,10 @@
margin-left: 11px;
color: #6a737d;
width: 70%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
&:hover {
cursor: pointer;
@ -215,6 +202,7 @@
position: absolute;
width: 24px;
height: 24px;
margin: 0 5px;
[dir='rtl'] & {
transform: rotate(180deg);
@ -239,6 +227,10 @@
flex-flow: row nowrap;
margin: 0.75rem 0;
@media screen and (max-width: 575px) {
width: 93%;
}
.btn-default {
margin-right: 0.375rem;
}

View File

@ -30,6 +30,7 @@ export default class NetworkForm extends PureComponent {
blockExplorerUrl: PropTypes.string,
rpcPrefs: PropTypes.object,
rpcUrls: PropTypes.array,
isFullScreen: PropTypes.bool,
}
state = {
@ -67,7 +68,6 @@ export default class NetworkForm extends PureComponent {
}
componentWillUnmount () {
this.props.onClear()
this.setState({
rpcUrl: '',
chainId: '',
@ -76,6 +76,11 @@ export default class NetworkForm extends PureComponent {
blockExplorerUrl: '',
errors: {},
})
// onClear will push the network settings route unless was pass false.
// Since we call onClear to cause this component to be unmounted, the
// route will already have been updated, and we avoid setting it twice.
this.props.onClear(false)
}
resetForm () {
@ -136,11 +141,12 @@ export default class NetworkForm extends PureComponent {
onCancel = () => {
const {
isFullScreen,
networksTabIsInAddMode,
onClear,
} = this.props
if (networksTabIsInAddMode) {
if (networksTabIsInAddMode || !isFullScreen) {
onClear()
} else {
this.resetForm()
@ -337,14 +343,14 @@ export default class NetworkForm extends PureComponent {
errors,
} = this.state
const deletable = !networksTabIsInAddMode && !isCurrentRpcTarget && !viewOnly
const isSubmitDisabled = (
viewOnly ||
this.stateIsUnchanged() ||
!rpcUrl ||
!chainId ||
Object.values(errors).some((x) => x)
)
const deletable = !networksTabIsInAddMode && !isCurrentRpcTarget && !viewOnly
return (
<div className="networks-tab__network-form">
@ -384,30 +390,34 @@ export default class NetworkForm extends PureComponent {
'optionalBlockExplorerUrl',
)}
<div className="network-form__footer">
{
deletable && (
{!viewOnly && (
<>
{
deletable && (
<Button
type="danger"
onClick={this.onDelete}
>
{ t('delete') }
</Button>
)
}
<Button
type="danger"
onClick={this.onDelete}
type="default"
onClick={this.onCancel}
disabled={this.stateIsUnchanged()}
>
{ t('delete') }
{t('cancel')}
</Button>
)
}
<Button
type="default"
onClick={this.onCancel}
disabled={viewOnly || this.stateIsUnchanged()}
>
{ t('cancel') }
</Button>
<Button
type="secondary"
disabled={isSubmitDisabled}
onClick={this.onSubmit}
>
{ t('save') }
</Button>
<Button
type="secondary"
disabled={isSubmitDisabled}
onClick={this.onSubmit}
>
{ t('save') }
</Button>
</>
)}
</div>
</div>
)

View File

@ -1,11 +1,12 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { SETTINGS_ROUTE } from '../../../helpers/constants/routes'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import Button from '../../../components/ui/button'
import LockIcon from '../../../components/ui/lock-icon'
import {
NETWORKS_ROUTE,
NETWORKS_FORM_ROUTE,
} from '../../../helpers/constants/routes'
import NetworkDropdownIcon from '../../../components/app/dropdowns/components/network-dropdown-icon'
import NetworkForm from './network-form'
@ -17,7 +18,6 @@ export default class NetworksTab extends PureComponent {
static propTypes = {
editRpc: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
networkIsSelected: PropTypes.bool,
networksTabIsInAddMode: PropTypes.bool,
@ -30,10 +30,13 @@ export default class NetworksTab extends PureComponent {
providerUrl: PropTypes.string,
providerType: PropTypes.string,
networkDefaultedToProvider: PropTypes.bool,
history: PropTypes.object.isRequired,
shouldRenderNetworkForm: PropTypes.bool.isRequired,
isFullScreen: PropTypes.bool.isRequired,
}
UNSAFE_componentWillMount () {
this.props.setSelectedSettingsRpcUrl(null)
componentWillUnmount () {
this.props.setSelectedSettingsRpcUrl('')
}
isCurrentPath (pathname) {
@ -42,32 +45,19 @@ export default class NetworksTab extends PureComponent {
renderSubHeader () {
const {
networkIsSelected,
setSelectedSettingsRpcUrl,
setNetworksTabAddMode,
networksTabIsInAddMode,
networkDefaultedToProvider,
} = this.props
return (
<div className="settings-page__sub-header">
<div
className="networks-tab__back-button"
onClick={(networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode
? () => {
setNetworksTabAddMode(false)
setSelectedSettingsRpcUrl(null)
}
: () => this.props.history.push(SETTINGS_ROUTE)
}
/>
<span className="settings-page__sub-header-text">{ this.context.t('networks') }</span>
<div className="networks-tab__add-network-header-button-wrapper">
<Button
type="secondary"
onClick={(event) => {
event.preventDefault()
setSelectedSettingsRpcUrl(null)
setSelectedSettingsRpcUrl('')
setNetworksTabAddMode(true)
}}
>
@ -86,6 +76,8 @@ export default class NetworksTab extends PureComponent {
providerUrl,
providerType,
networksTabIsInAddMode,
history,
isFullScreen,
} = this.props
const {
border,
@ -106,9 +98,12 @@ export default class NetworksTab extends PureComponent {
<div
key={`settings-network-list-item:${rpcUrl}`}
className="networks-tab__networks-list-item"
onClick={ () => {
onClick={() => {
setNetworksTabAddMode(false)
setSelectedSettingsRpcUrl(rpcUrl)
if (!isFullScreen) {
history.push(NETWORKS_FORM_ROUTE)
}
}}
>
<NetworkDropdownIcon
@ -186,16 +181,15 @@ export default class NetworksTab extends PureComponent {
},
networksTabIsInAddMode,
editRpc,
networkDefaultedToProvider,
providerUrl,
networksToRender,
history,
isFullScreen,
shouldRenderNetworkForm,
} = this.props
const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
const shouldRenderNetworkForm = networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider)
return (
<div className="networks-tab__content">
<>
{ this.renderNetworksList() }
{
shouldRenderNetworkForm
@ -208,9 +202,12 @@ export default class NetworksTab extends PureComponent {
rpcUrl={rpcUrl}
chainId={chainId}
ticker={ticker}
onClear={() => {
onClear={(shouldUpdateHistory = true) => {
setNetworksTabAddMode(false)
setSelectedSettingsRpcUrl(null)
setSelectedSettingsRpcUrl('')
if (shouldUpdateHistory && !isFullScreen) {
history.push(NETWORKS_ROUTE)
}
}}
showConfirmDeleteNetworkModal={showConfirmDeleteNetworkModal}
viewOnly={viewOnly}
@ -218,39 +215,48 @@ export default class NetworksTab extends PureComponent {
networksTabIsInAddMode={networksTabIsInAddMode}
rpcPrefs={rpcPrefs}
blockExplorerUrl={blockExplorerUrl}
cancelText={t('cancel')}
isFullScreen={isFullScreen}
/>
)
: null
}
</div>
</>
)
}
render () {
const { setNetworksTabAddMode, setSelectedSettingsRpcUrl, networkIsSelected, networksTabIsInAddMode } = this.props
const {
setNetworksTabAddMode,
setSelectedSettingsRpcUrl,
history,
isFullScreen,
shouldRenderNetworkForm,
} = this.props
return (
<div className="networks-tab__body">
{this.renderSubHeader()}
{this.renderNetworksTabContent()}
{!networkIsSelected && !networksTabIsInAddMode
? (
<div className="networks-tab__add-network-button-wrapper">
<Button
type="primary"
onClick={(event) => {
event.preventDefault()
setSelectedSettingsRpcUrl(null)
setNetworksTabAddMode(true)
}}
>
{ this.context.t('addNetwork') }
</Button>
</div>
)
: null
}
{isFullScreen && this.renderSubHeader()}
<div className="networks-tab__content">
{this.renderNetworksTabContent()}
{!isFullScreen && !shouldRenderNetworkForm
? (
<div className="networks-tab__networks-list-popup-footer">
<Button
type="primary"
onClick={(event) => {
event.preventDefault()
setSelectedSettingsRpcUrl('')
setNetworksTabAddMode(true)
history.push(NETWORKS_FORM_ROUTE)
}}
>
{ this.context.t('addNetwork') }
</Button>
</div>
)
: null
}
</div>
</div>
)
}

View File

@ -9,12 +9,23 @@ import {
editRpc,
showModal,
} from '../../../store/actions'
import { NETWORKS_FORM_ROUTE } from '../../../helpers/constants/routes'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import NetworksTab from './networks-tab.component'
import { defaultNetworksData } from './networks-tab.constants'
const defaultNetworks = defaultNetworksData.map((network) => ({ ...network, viewOnly: true }))
const mapStateToProps = (state) => {
const mapStateToProps = (state, ownProps) => {
const { location: { pathname } } = ownProps
const environmentType = getEnvironmentType()
const isFullScreen = environmentType === ENVIRONMENT_TYPE_FULLSCREEN
const shouldRenderNetworkForm = (
isFullScreen || Boolean(pathname.match(NETWORKS_FORM_ROUTE))
)
const {
frequentRpcListDetail,
provider,
@ -32,7 +43,7 @@ const mapStateToProps = (state) => {
rpcUrl: rpc.rpcUrl,
chainId: rpc.chainId,
ticker: rpc.ticker,
blockExplorerUrl: (rpc.rpcPrefs && rpc.rpcPrefs.blockExplorerUrl) || '',
blockExplorerUrl: (rpc.rpcPrefs?.blockExplorerUrl) || '',
}
})
@ -56,6 +67,8 @@ const mapStateToProps = (state) => {
providerType: provider.type,
providerUrl: provider.rpcUrl,
networkDefaultedToProvider,
isFullScreen,
shouldRenderNetworkForm,
}
}

View File

@ -34,7 +34,7 @@ class SettingsPage extends PureComponent {
currentPath: PropTypes.string,
history: PropTypes.object,
isAddressEntryPage: PropTypes.bool,
isPopupView: PropTypes.bool,
isPopup: PropTypes.bool,
pathnameI18nKey: PropTypes.string,
initialBreadCrumbRoute: PropTypes.string,
breadCrumbTextKey: PropTypes.string,
@ -57,7 +57,7 @@ class SettingsPage extends PureComponent {
>
<div className="settings-page__header">
{
currentPath !== SETTINGS_ROUTE && currentPath !== NETWORKS_ROUTE && (
currentPath !== SETTINGS_ROUTE && (
<div
className="settings-page__back-button"
onClick={() => history.push(backRoute)}
@ -85,13 +85,13 @@ class SettingsPage extends PureComponent {
renderTitle () {
const { t } = this.context
const { isPopupView, pathnameI18nKey, addressName } = this.props
const { isPopup, pathnameI18nKey, addressName } = this.props
let titleText
if (isPopupView && addressName) {
if (isPopup && addressName) {
titleText = addressName
} else if (pathnameI18nKey && isPopupView) {
} else if (pathnameI18nKey && isPopup) {
titleText = t(pathnameI18nKey)
} else {
titleText = t('settings')
@ -108,7 +108,7 @@ class SettingsPage extends PureComponent {
const { t } = this.context
const {
currentPath,
isPopupView,
isPopup,
isAddressEntryPage,
pathnameI18nKey,
addressName,
@ -120,7 +120,7 @@ class SettingsPage extends PureComponent {
let subheaderText
if (isPopupView && isAddressEntryPage) {
if (isPopup && isAddressEntryPage) {
subheaderText = t('settings')
} else if (initialBreadCrumbKey) {
subheaderText = t(initialBreadCrumbKey)
@ -128,7 +128,7 @@ class SettingsPage extends PureComponent {
subheaderText = t(pathnameI18nKey || 'general')
}
return currentPath !== NETWORKS_ROUTE && (
return !currentPath.startsWith(NETWORKS_ROUTE) && (
<div className="settings-page__subheader">
<div
className={classnames({ 'settings-page__subheader--link': initialBreadCrumbRoute })}
@ -200,7 +200,6 @@ class SettingsPage extends PureComponent {
component={AlertsTab}
/>
<Route
exact
path={NETWORKS_ROUTE}
component={NetworksTab}
/>

View File

@ -8,33 +8,37 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { getMostRecentOverviewPage } from '../../ducks/history/history'
import {
ADVANCED_ROUTE,
SECURITY_ROUTE,
GENERAL_ROUTE,
ALERTS_ROUTE,
ABOUT_US_ROUTE,
SETTINGS_ROUTE,
ADVANCED_ROUTE,
ALERTS_ROUTE,
CONTACT_LIST_ROUTE,
CONTACT_ADD_ROUTE,
CONTACT_EDIT_ROUTE,
CONTACT_VIEW_ROUTE,
CONTACT_MY_ACCOUNTS_ROUTE,
CONTACT_MY_ACCOUNTS_EDIT_ROUTE,
CONTACT_MY_ACCOUNTS_VIEW_ROUTE,
CONTACT_VIEW_ROUTE,
GENERAL_ROUTE,
NETWORKS_FORM_ROUTE,
NETWORKS_ROUTE,
SECURITY_ROUTE,
SETTINGS_ROUTE,
} from '../../helpers/constants/routes'
import Settings from './settings.component'
const ROUTES_TO_I18N_KEYS = {
[GENERAL_ROUTE]: 'general',
[ADVANCED_ROUTE]: 'advanced',
[SECURITY_ROUTE]: 'securityAndPrivacy',
[ABOUT_US_ROUTE]: 'about',
[ADVANCED_ROUTE]: 'advanced',
[ALERTS_ROUTE]: 'alerts',
[CONTACT_LIST_ROUTE]: 'contacts',
[GENERAL_ROUTE]: 'general',
[CONTACT_ADD_ROUTE]: 'newContact',
[CONTACT_EDIT_ROUTE]: 'editContact',
[CONTACT_VIEW_ROUTE]: 'viewContact',
[CONTACT_LIST_ROUTE]: 'contacts',
[CONTACT_MY_ACCOUNTS_ROUTE]: 'myAccounts',
[CONTACT_VIEW_ROUTE]: 'viewContact',
[NETWORKS_ROUTE]: 'networks',
[NETWORKS_FORM_ROUTE]: 'networks',
[SECURITY_ROUTE]: 'securityAndPrivacy',
}
const mapStateToProps = (state, ownProps) => {
@ -47,11 +51,12 @@ const mapStateToProps = (state, ownProps) => {
const isAddContactPage = Boolean(pathname.match(CONTACT_ADD_ROUTE))
const isEditContactPage = Boolean(pathname.match(CONTACT_EDIT_ROUTE))
const isEditMyAccountsContactPage = Boolean(pathname.match(CONTACT_MY_ACCOUNTS_EDIT_ROUTE))
const isNetworksFormPage = Boolean(pathname.match(NETWORKS_FORM_ROUTE))
const isPopupView = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
const pathnameI18nKey = ROUTES_TO_I18N_KEYS[pathname]
let backRoute
let backRoute = SETTINGS_ROUTE
if (isMyAccountsPage && isAddressEntryPage) {
backRoute = CONTACT_MY_ACCOUNTS_ROUTE
} else if (isEditContactPage) {
@ -60,8 +65,8 @@ const mapStateToProps = (state, ownProps) => {
backRoute = `${CONTACT_MY_ACCOUNTS_VIEW_ROUTE}/${pathNameTail}`
} else if (isAddressEntryPage || isMyAccountsPage || isAddContactPage) {
backRoute = CONTACT_LIST_ROUTE
} else {
backRoute = SETTINGS_ROUTE
} else if (isNetworksFormPage) {
backRoute = NETWORKS_ROUTE
}
let initialBreadCrumbRoute
@ -80,7 +85,7 @@ const mapStateToProps = (state, ownProps) => {
isMyAccountsPage,
backRoute,
currentPath: pathname,
isPopupView,
isPopup,
pathnameI18nKey,
addressName,
initialBreadCrumbRoute,