Add delete to custom RPC form (#6718, #6650)

feature/default_network_editable
Dan J Miller 6 years ago committed by Whymarrh Whitby
parent a47370057e
commit 18179fd345
  1. 3
      app/_locales/en/messages.json
  2. 3
      app/_locales/zh_TW/messages.json
  3. 16
      test/e2e/beta/metamask-beta-ui.spec.js
  4. 4
      ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js
  5. 8
      ui/app/pages/settings/index.scss
  6. 28
      ui/app/pages/settings/networks-tab/index.scss
  7. 128
      ui/app/pages/settings/networks-tab/network-form/network-form.component.js
  8. 80
      ui/app/pages/settings/networks-tab/networks-tab.component.js
  9. 9
      ui/app/pages/settings/networks-tab/networks-tab.constants.js
  10. 4
      ui/app/pages/settings/networks-tab/networks-tab.container.js
  11. 2
      ui/app/pages/settings/settings.component.js

@ -454,6 +454,9 @@
"defaultNetwork": { "defaultNetwork": {
"message": "The default network for Ether transactions is Main Net." "message": "The default network for Ether transactions is Main Net."
}, },
"delete": {
"message": "Delete"
},
"denExplainer": { "denExplainer": {
"message": "Your DEN is your password-encrypted storage within MetaMask." "message": "Your DEN is your password-encrypted storage within MetaMask."
}, },

@ -366,6 +366,9 @@
"defaultNetwork": { "defaultNetwork": {
"message": "預設乙太幣交易網路為主網路" "message": "預設乙太幣交易網路為主網路"
}, },
"delete": {
"message": "刪除"
},
"denExplainer": { "denExplainer": {
"message": "您的 DEN 是 MetaMask 中您的的密碼加密儲存庫。" "message": "您的 DEN 是 MetaMask 中您的的密碼加密儲存庫。"
}, },

@ -1478,7 +1478,7 @@ describe('MetaMask', function () {
await customRpcInput.clear() await customRpcInput.clear()
await customRpcInput.sendKeys(customRpcUrl) await customRpcInput.sendKeys(customRpcUrl)
const customRpcSave = await findElement(driver, By.css('.page-container__footer-button')) const customRpcSave = await findElement(driver, By.css('.network-form__footer .btn-secondary'))
await customRpcSave.click() await customRpcSave.click()
await delay(largeDelayMs * 2) await delay(largeDelayMs * 2)
}) })
@ -1504,5 +1504,19 @@ describe('MetaMask', function () {
assert.equal(customRpcs.length, customRpcUrls.length) assert.equal(customRpcs.length, customRpcUrls.length)
}) })
it('deletes a custom RPC', async () => {
const networkListItems = await findElements(driver, By.css('.networks-tab__networks-list-name'))
const lastNetworkListItem = networkListItems[networkListItems.length - 1]
await lastNetworkListItem.click()
await delay(100)
const deleteButton = await findElement(driver, By.css('.btn-danger'))
await deleteButton.click()
await delay(regularDelayMs)
const newNetworkListItems = await findElements(driver, By.css('.networks-tab__networks-list-name'))
assert.equal(networkListItems.length - 1, newNetworkListItems.length)
})
}) })
}) })

@ -8,6 +8,7 @@ export default class PageContainerFooter extends Component {
children: PropTypes.node, children: PropTypes.node,
onCancel: PropTypes.func, onCancel: PropTypes.func,
cancelText: PropTypes.string, cancelText: PropTypes.string,
cancelButtonType: PropTypes.string,
onSubmit: PropTypes.func, onSubmit: PropTypes.func,
submitText: PropTypes.string, submitText: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
@ -29,6 +30,7 @@ export default class PageContainerFooter extends Component {
disabled, disabled,
submitButtonType, submitButtonType,
hideCancel, hideCancel,
cancelButtonType,
} = this.props } = this.props
return ( return (
@ -36,7 +38,7 @@ export default class PageContainerFooter extends Component {
<header> <header>
{!hideCancel && <Button {!hideCancel && <Button
type="default" type={cancelButtonType || 'default'}
large large
className="page-container__footer-button" className="page-container__footer-button"
onClick={e => onCancel(e)} onClick={e => onCancel(e)}

@ -28,6 +28,10 @@
font-size: 20px; font-size: 20px;
border-bottom: 1px solid $alto; border-bottom: 1px solid $alto;
margin-right: 24px; margin-right: 24px;
height: 72px;
align-items: center;
display: flex;
flex-flow: row nowrap;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
display: none; display: none;
@ -52,9 +56,7 @@
font-family: Roboto; font-family: Roboto;
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
font-size: 24px; font-size: 20px;
line-height: 24px;
color: black;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
font-size: 16px; font-size: 16px;

@ -12,7 +12,7 @@
} }
&__body { &__body {
padding: 12px 24px; padding-right: 24px;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -118,12 +118,12 @@
} }
&__add-network-header-button-wrapper { &__add-network-header-button-wrapper {
padding-top: 15px;
padding-bottom: 21px;
justify-content: center; justify-content: center;
.button { .button {
width: 178px; width: 138px;
padding: 10px;
line-height: 20px;
} }
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
@ -198,3 +198,23 @@
color: #000000; color: #000000;
} }
} }
.network-form {
&__footer {
display: flex;
flex-flow: row nowrap;
margin: .75rem 0;
.btn-default {
margin-right: .375rem;
}
.btn-secondary {
margin-left: .375rem;
}
.btn-danger {
margin-right: 3.75rem;
}
}
}

@ -1,10 +1,10 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import validUrl from 'valid-url' import validUrl from 'valid-url'
import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'
import TextField from '../../../../components/ui/text-field' import TextField from '../../../../components/ui/text-field'
import Button from '../../../../components/ui/button'
export default class NetworksTab extends PureComponent { export default class NetworkForm extends PureComponent {
static contextTypes = { static contextTypes = {
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
metricsEvent: PropTypes.func.isRequired, metricsEvent: PropTypes.func.isRequired,
@ -12,6 +12,7 @@ export default class NetworksTab extends PureComponent {
static propTypes = { static propTypes = {
editRpc: PropTypes.func.isRequired, editRpc: PropTypes.func.isRequired,
delRpcTarget: PropTypes.func.isRequired,
rpcUrl: PropTypes.string, rpcUrl: PropTypes.string,
chainId: PropTypes.string, chainId: PropTypes.string,
ticker: PropTypes.string, ticker: PropTypes.string,
@ -20,6 +21,7 @@ export default class NetworksTab extends PureComponent {
onClear: PropTypes.func.isRequired, onClear: PropTypes.func.isRequired,
setRpcTarget: PropTypes.func.isRequired, setRpcTarget: PropTypes.func.isRequired,
networksTabIsInAddMode: PropTypes.bool, networksTabIsInAddMode: PropTypes.bool,
isCurrentRpcTarget: PropTypes.bool,
blockExplorerUrl: PropTypes.string, blockExplorerUrl: PropTypes.string,
rpcPrefs: PropTypes.object, rpcPrefs: PropTypes.object,
} }
@ -70,6 +72,71 @@ export default class NetworksTab extends PureComponent {
}) })
} }
resetForm () {
const {
rpcUrl,
chainId,
ticker,
networkName,
blockExplorerUrl,
} = this.props
this.setState({ rpcUrl, chainId, ticker, networkName, blockExplorerUrl, errors: {} })
}
onSubmit = () => {
const {
setRpcTarget,
rpcUrl: propsRpcUrl,
editRpc,
rpcPrefs = {},
onClear,
networksTabIsInAddMode,
} = this.props
const {
networkName,
rpcUrl,
chainId,
ticker,
blockExplorerUrl,
} = this.state
if (propsRpcUrl && rpcUrl !== propsRpcUrl) {
editRpc(propsRpcUrl, rpcUrl, chainId, ticker, networkName, {
blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
...rpcPrefs,
})
} else {
setRpcTarget(rpcUrl, chainId, ticker, networkName, {
blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
...rpcPrefs,
})
}
if (networksTabIsInAddMode) {
onClear()
}
}
onCancel = () => {
const {
networksTabIsInAddMode,
onClear,
} = this.props
if (networksTabIsInAddMode) {
onClear()
} else {
this.resetForm()
}
}
onDelete = () => {
const { delRpcTarget, rpcUrl, onClear } = this.props
delRpcTarget(rpcUrl)
this.resetForm()
onClear()
}
stateIsUnchanged () { stateIsUnchanged () {
const { const {
rpcUrl, rpcUrl,
@ -152,16 +219,23 @@ export default class NetworksTab extends PureComponent {
} }
render () { render () {
const { setRpcTarget, viewOnly, rpcUrl: propsRpcUrl, editRpc, rpcPrefs = {} } = this.props const { t } = this.context
const {
viewOnly,
isCurrentRpcTarget,
networksTabIsInAddMode,
} = this.props
const { const {
networkName, networkName,
rpcUrl, rpcUrl,
chainId, chainId = '',
ticker, ticker,
blockExplorerUrl, blockExplorerUrl,
errors, errors,
} = this.state } = this.state
const isSubmitDisabled = viewOnly || this.stateIsUnchanged() || Object.values(errors).some(x => x) || !rpcUrl
const deletable = !networksTabIsInAddMode && !isCurrentRpcTarget && !viewOnly
return ( return (
<div className="networks-tab__network-form"> <div className="networks-tab__network-form">
@ -198,26 +272,32 @@ export default class NetworksTab extends PureComponent {
blockExplorerUrl, blockExplorerUrl,
'optionalBlockExplorerUrl', 'optionalBlockExplorerUrl',
)} )}
<PageContainerFooter <div className="network-form__footer">
cancelText={this.context.t('cancel')} {
hideCancel={true} deletable && (
onSubmit={() => { <Button
if (propsRpcUrl && rpcUrl !== propsRpcUrl) { type="danger"
editRpc(propsRpcUrl, rpcUrl, chainId, ticker, networkName, { onClick={this.onDelete}
blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl, >
...rpcPrefs, { t('delete') }
}) </Button>
} else { )
setRpcTarget(rpcUrl, chainId, ticker, networkName, { }
blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl, <Button
...rpcPrefs, type="default"
}) onClick={this.onCancel}
} disabled={viewOnly || this.stateIsUnchanged()}
}} >
submitText={this.context.t('save')} { t('cancel') }
submitButtonType={'confirm'} </Button>
disabled={viewOnly || this.stateIsUnchanged() || Object.values(errors).some(x => x) || !rpcUrl} <Button
/> type="secondary"
disabled={isSubmitDisabled}
onClick={this.onSubmit}
>
{ t('save') }
</Button>
</div>
</div> </div>
) )
} }

@ -25,6 +25,7 @@ export default class NetworksTab extends PureComponent {
setNetworksTabAddMode: PropTypes.func.isRequired, setNetworksTabAddMode: PropTypes.func.isRequired,
setRpcTarget: PropTypes.func.isRequired, setRpcTarget: PropTypes.func.isRequired,
setSelectedSettingsRpcUrl: PropTypes.func.isRequired, setSelectedSettingsRpcUrl: PropTypes.func.isRequired,
delRpcTarget: PropTypes.func.isRequired,
providerUrl: PropTypes.string, providerUrl: PropTypes.string,
providerType: PropTypes.string, providerType: PropTypes.string,
networkDefaultedToProvider: PropTypes.bool, networkDefaultedToProvider: PropTypes.bool,
@ -62,7 +63,7 @@ export default class NetworksTab extends PureComponent {
<span className="settings-page__sub-header-text">{ this.context.t('networks') }</span> <span className="settings-page__sub-header-text">{ this.context.t('networks') }</span>
<div className="networks-tab__add-network-header-button-wrapper"> <div className="networks-tab__add-network-header-button-wrapper">
<Button <Button
type="primary" type="secondary"
onClick={event => { onClick={event => {
event.preventDefault() event.preventDefault()
setSelectedSettingsRpcUrl(null) setSelectedSettingsRpcUrl(null)
@ -125,19 +126,41 @@ export default class NetworksTab extends PureComponent {
renderNetworksList () { renderNetworksList () {
const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props
console.log(networksToRender)
return ( return (
<div className={classnames('networks-tab__networks-list', { <div
'networks-tab__networks-list--selection': (networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode, className={classnames('networks-tab__networks-list', {
})}> 'networks-tab__networks-list--selection': (networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode,
})}
>
{ networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) } { networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) }
{
networksTabIsInAddMode && (
<div
className="networks-tab__networks-list-item"
>
<NetworkDropdownIcon
backgroundColor="white"
innerBorder="1px solid rgb(106, 115, 125)"
/>
<div
className="networks-tab__networks-list-name networks-tab__networks-list-name--selected"
>
{ this.context.t('newNetwork') }
</div>
<div className="networks-tab__networks-list-arrow" />
</div>
)
}
</div> </div>
) )
} }
renderNetworksTabContent () { renderNetworksTabContent () {
const { t } = this.context
const { const {
setRpcTarget, setRpcTarget,
delRpcTarget,
setSelectedSettingsRpcUrl, setSelectedSettingsRpcUrl,
setNetworksTabAddMode, setNetworksTabAddMode,
selectedNetwork: { selectedNetwork: {
@ -153,30 +176,39 @@ export default class NetworksTab extends PureComponent {
networksTabIsInAddMode, networksTabIsInAddMode,
editRpc, editRpc,
networkDefaultedToProvider, networkDefaultedToProvider,
providerUrl,
} = this.props } = this.props
const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
const shouldRenderNetworkForm = networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider)
return ( return (
<div className="networks-tab__content"> <div className="networks-tab__content">
{this.renderNetworksList()} { this.renderNetworksList() }
{networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider) {
? <NetworkForm shouldRenderNetworkForm
setRpcTarget={setRpcTarget} ? (
editRpc={editRpc} <NetworkForm
networkName={label || labelKey && this.context.t(labelKey) || ''} setRpcTarget={setRpcTarget}
rpcUrl={rpcUrl} editRpc={editRpc}
chainId={chainId} networkName={label || labelKey && t(labelKey) || ''}
ticker={ticker} rpcUrl={rpcUrl}
onClear={() => { chainId={chainId}
setNetworksTabAddMode(false) ticker={ticker}
setSelectedSettingsRpcUrl(null) onClear={() => {
}} setNetworksTabAddMode(false)
viewOnly={viewOnly} setSelectedSettingsRpcUrl(null)
networksTabIsInAddMode={networksTabIsInAddMode} }}
rpcPrefs={rpcPrefs} delRpcTarget={delRpcTarget}
blockExplorerUrl={blockExplorerUrl} viewOnly={viewOnly}
/> isCurrentRpcTarget={providerUrl === rpcUrl}
: null networksTabIsInAddMode={networksTabIsInAddMode}
rpcPrefs={rpcPrefs}
blockExplorerUrl={blockExplorerUrl}
cancelText={t('cancel')}
/>
)
: null
} }
</div> </div>
) )

@ -35,6 +35,15 @@ const defaultNetworksData = [
ticker: 'ETH', ticker: 'ETH',
blockExplorerUrl: 'https://rinkeby.etherscan.io', blockExplorerUrl: 'https://rinkeby.etherscan.io',
}, },
{
labelKey: 'goerli',
iconColor: '#3099f2',
providerType: 'goerli',
rpcUrl: 'https://api.infura.io/v1/jsonrpc/goerli',
chainId: '5',
ticker: 'ETH',
blockExplorerUrl: 'https://goerli.etherscan.io',
},
{ {
labelKey: 'localhost', labelKey: 'localhost',
iconColor: 'white', iconColor: 'white',

@ -8,6 +8,7 @@ import {
displayWarning, displayWarning,
setNetworksTabAddMode, setNetworksTabAddMode,
editRpc, editRpc,
delRpcTarget,
} from '../../../store/actions' } from '../../../store/actions'
import { defaultNetworksData } from './networks-tab.constants' import { defaultNetworksData } from './networks-tab.constants'
const defaultNetworks = defaultNetworksData.map(network => ({ ...network, viewOnly: true })) const defaultNetworks = defaultNetworksData.map(network => ({ ...network, viewOnly: true }))
@ -63,6 +64,9 @@ const mapDispatchToProps = dispatch => {
setRpcTarget: (newRpc, chainId, ticker, nickname, rpcPrefs) => { setRpcTarget: (newRpc, chainId, ticker, nickname, rpcPrefs) => {
dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname, rpcPrefs)) dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname, rpcPrefs))
}, },
delRpcTarget: (target) => {
dispatch(delRpcTarget(target))
},
displayWarning: warning => dispatch(displayWarning(warning)), displayWarning: warning => dispatch(displayWarning(warning)),
setNetworksTabAddMode: isInAddMode => dispatch(setNetworksTabAddMode(isInAddMode)), setNetworksTabAddMode: isInAddMode => dispatch(setNetworksTabAddMode(isInAddMode)),
editRpc: (oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs) => { editRpc: (oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs) => {

@ -89,7 +89,7 @@ class SettingsPage extends PureComponent {
const { t } = this.context const { t } = this.context
const { location: { pathname } } = this.props const { location: { pathname } } = this.props
return ( return pathname !== NETWORKS_ROUTE && (
<div className="settings-page__subheader"> <div className="settings-page__subheader">
{t(ROUTES_TO_I18N_KEYS[pathname] || 'general')} {t(ROUTES_TO_I18N_KEYS[pathname] || 'general')}
</div> </div>

Loading…
Cancel
Save