mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
New settings custom rpc form (#6490)
* Add networks tab to settings, with header. * Adds network list to settings network tab. * Adds form to settings networks tab and connects it to network list. * Network tab: form adding and editing working * Settings network form properly handles input errors * Add translations for settings network form * Clean up styles of settings network tab. * Add popup-view styles and behaviour to settings network tab. * Fix save button on settings network form * Adds 'Add Network' button and addMode to settings networks tab * Lint fix for settings networks tab addition * Fix navigation in settings networks tab. * Editing an rpcurl in networks tab does not create new network, just changes rpc of old * Fix layout of settings tabs other than network * Networks dropdown 'Custom Rpc' item links to networks tab in settings. * Update settings sidebar networks subheader. * Make networks tab buttons width consistent with input widths in extension view. * Fix settings screen subheader height in popup view * Fix height of add networks button in popup view * Add optional label to chainId and symbol form labels in networks setting tab * Style fixes for networks tab headers * Add ability to customize block explorer used by custom rpc * Stylistic improvements+fixes to custom rpc form. * Hide cancel button. * Highlight and show network form of provider by default. * Standardize network subheader name to 'Networks' * Update e2e tests for new settings network form * Update unit tests for new rpcPrefs prop * Extract blockexplorer url construction into method. * Fix broken styles on non-network tabs in popup mode * Fix block explorer url links for cases when provider in state has not been updated. * Fix vertical spacing of network form * Don't allow click of save button on network form if nothing has changed * Ensure add network button is shown in popup view * Lint fix for networks tab * Fix block explorer url preference setting. * Fix e2e tests for custom blockexplorer in account details modal changes. * Update integration test states to include frequentRpcList property * Fix some capitalizations in en/messages.json * Remove some console.logs added during custom rpc form work * Fix external account link text and url for modal and dropdown. * Documentation, url validation, proptype required additions and lint fixes on network tab and form.
This commit is contained in:
parent
094e4cf555
commit
13be683701
@ -83,6 +83,9 @@
|
|||||||
"address": {
|
"address": {
|
||||||
"message": "Address"
|
"message": "Address"
|
||||||
},
|
},
|
||||||
|
"addNetwork": {
|
||||||
|
"message": "Add Network"
|
||||||
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"message": "Advanced"
|
"message": "Advanced"
|
||||||
},
|
},
|
||||||
@ -191,6 +194,13 @@
|
|||||||
"message": "must be greater than or equal to $1 and less than or equal to $2.",
|
"message": "must be greater than or equal to $1 and less than or equal to $2.",
|
||||||
"description": "helper for inputting hex as decimal input"
|
"description": "helper for inputting hex as decimal input"
|
||||||
},
|
},
|
||||||
|
"blockExplorerUrl": {
|
||||||
|
"message": "Block Explorer"
|
||||||
|
},
|
||||||
|
"blockExplorerView": {
|
||||||
|
"message": "View account at $1",
|
||||||
|
"description": "$1 replaced by URL for custom block explorer"
|
||||||
|
},
|
||||||
"blockiesIdenticon": {
|
"blockiesIdenticon": {
|
||||||
"message": "Use Blockies Identicon"
|
"message": "Use Blockies Identicon"
|
||||||
},
|
},
|
||||||
@ -230,6 +240,9 @@
|
|||||||
"ok": {
|
"ok": {
|
||||||
"message": "Ok"
|
"message": "Ok"
|
||||||
},
|
},
|
||||||
|
"optionalBlockExplorerUrl": {
|
||||||
|
"message": "Block Explorer URL (optional)"
|
||||||
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancel"
|
"message": "Cancel"
|
||||||
},
|
},
|
||||||
@ -245,6 +258,9 @@
|
|||||||
"cancelN": {
|
"cancelN": {
|
||||||
"message": "Cancel all $1 transactions"
|
"message": "Cancel all $1 transactions"
|
||||||
},
|
},
|
||||||
|
"chainId": {
|
||||||
|
"message": "Chain ID"
|
||||||
|
},
|
||||||
"classicInterface": {
|
"classicInterface": {
|
||||||
"message": "Use classic interface"
|
"message": "Use classic interface"
|
||||||
},
|
},
|
||||||
@ -502,6 +518,9 @@
|
|||||||
"edit": {
|
"edit": {
|
||||||
"message": "Edit"
|
"message": "Edit"
|
||||||
},
|
},
|
||||||
|
"editNetwork": {
|
||||||
|
"message": "Edit Network"
|
||||||
|
},
|
||||||
"editAccountName": {
|
"editAccountName": {
|
||||||
"message": "Edit Account Name"
|
"message": "Edit Account Name"
|
||||||
},
|
},
|
||||||
@ -934,9 +953,15 @@
|
|||||||
"negativeETH": {
|
"negativeETH": {
|
||||||
"message": "Can not send negative amounts of ETH."
|
"message": "Can not send negative amounts of ETH."
|
||||||
},
|
},
|
||||||
|
"networkName": {
|
||||||
|
"message": "Network Name"
|
||||||
|
},
|
||||||
"networks": {
|
"networks": {
|
||||||
"message": "Networks"
|
"message": "Networks"
|
||||||
},
|
},
|
||||||
|
"networkSettingsDescription": {
|
||||||
|
"message": "Add and edit custom RPC networks"
|
||||||
|
},
|
||||||
"nevermind": {
|
"nevermind": {
|
||||||
"message": "Nevermind"
|
"message": "Nevermind"
|
||||||
},
|
},
|
||||||
@ -977,7 +1002,7 @@
|
|||||||
"protectYourKeysMessage2": {
|
"protectYourKeysMessage2": {
|
||||||
"message": "Keep your phrase safe. If you see something fishy, or you’re uncertain about a website, email support@metamask.io"
|
"message": "Keep your phrase safe. If you see something fishy, or you’re uncertain about a website, email support@metamask.io"
|
||||||
},
|
},
|
||||||
"rpcURL": {
|
"rpcUrl": {
|
||||||
"message": "New RPC URL"
|
"message": "New RPC URL"
|
||||||
},
|
},
|
||||||
"showAdvancedOptions": {
|
"showAdvancedOptions": {
|
||||||
@ -1492,6 +1517,9 @@
|
|||||||
"supportCenter": {
|
"supportCenter": {
|
||||||
"message": "Visit our Support Center"
|
"message": "Visit our Support Center"
|
||||||
},
|
},
|
||||||
|
"symbol": {
|
||||||
|
"message": "Symbol"
|
||||||
|
},
|
||||||
"symbolBetweenZeroTwelve": {
|
"symbolBetweenZeroTwelve": {
|
||||||
"message": "Symbol must be between 0 and 12 characters."
|
"message": "Symbol must be between 0 and 12 characters."
|
||||||
},
|
},
|
||||||
@ -1714,9 +1742,15 @@
|
|||||||
"viewAccount": {
|
"viewAccount": {
|
||||||
"message": "View Account"
|
"message": "View Account"
|
||||||
},
|
},
|
||||||
|
"viewOnCustomBlockExplorer": {
|
||||||
|
"message": "View at $1"
|
||||||
|
},
|
||||||
"viewOnEtherscan": {
|
"viewOnEtherscan": {
|
||||||
"message": "View on Etherscan"
|
"message": "View on Etherscan"
|
||||||
},
|
},
|
||||||
|
"viewNetworkInfo": {
|
||||||
|
"message": "View Network Info"
|
||||||
|
},
|
||||||
"visitWebSite": {
|
"visitWebSite": {
|
||||||
"message": "Visit our web site"
|
"message": "Visit our web site"
|
||||||
},
|
},
|
||||||
|
@ -129,13 +129,14 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
|
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
|
||||||
const providerConfig = {
|
const providerConfig = {
|
||||||
type: 'rpc',
|
type: 'rpc',
|
||||||
rpcTarget,
|
rpcTarget,
|
||||||
chainId,
|
chainId,
|
||||||
ticker,
|
ticker,
|
||||||
nickname,
|
nickname,
|
||||||
|
rpcPrefs,
|
||||||
}
|
}
|
||||||
this.providerConfig = providerConfig
|
this.providerConfig = providerConfig
|
||||||
}
|
}
|
||||||
|
@ -488,8 +488,8 @@ class PreferencesController {
|
|||||||
rpcList[index] = updatedRpc
|
rpcList[index] = updatedRpc
|
||||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||||
} else {
|
} else {
|
||||||
const { rpcUrl, chainId, ticker, nickname } = newRpcDetails
|
const { rpcUrl, chainId, ticker, nickname, rpcPrefs = {} } = newRpcDetails
|
||||||
return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname)
|
return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs)
|
||||||
}
|
}
|
||||||
return Promise.resolve(rpcList)
|
return Promise.resolve(rpcList)
|
||||||
}
|
}
|
||||||
@ -503,22 +503,22 @@ class PreferencesController {
|
|||||||
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '') {
|
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
|
||||||
const rpcList = this.getFrequentRpcListDetail()
|
const rpcList = this.getFrequentRpcListDetail()
|
||||||
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
|
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
rpcList.splice(index, 1)
|
rpcList.splice(index, 1)
|
||||||
}
|
|
||||||
if (url !== 'http://localhost:8545') {
|
|
||||||
let checkedChainId
|
|
||||||
if (!!chainId && !Number.isNaN(parseInt(chainId))) {
|
|
||||||
checkedChainId = chainId
|
|
||||||
}
|
}
|
||||||
rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname })
|
if (url !== 'http://localhost:8545') {
|
||||||
|
let checkedChainId
|
||||||
|
if (!!chainId && !Number.isNaN(parseInt(chainId))) {
|
||||||
|
checkedChainId = chainId
|
||||||
|
}
|
||||||
|
rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs })
|
||||||
|
}
|
||||||
|
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||||
|
return Promise.resolve(rpcList)
|
||||||
}
|
}
|
||||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
|
||||||
return Promise.resolve(rpcList)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes custom RPC url from state.
|
* Removes custom RPC url from state.
|
||||||
|
@ -1633,9 +1633,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @returns {Promise<String>} - The RPC Target URL confirmed.
|
* @returns {Promise<String>} - The RPC Target URL confirmed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname) {
|
async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname, rpcPrefs) {
|
||||||
await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname })
|
await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname, rpcPrefs })
|
||||||
this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname)
|
this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname, rpcPrefs)
|
||||||
return rpcUrl
|
return rpcUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1648,15 +1648,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {string} nickname - Optional nickname of the selected network.
|
* @param {string} nickname - Optional nickname of the selected network.
|
||||||
* @returns {Promise<String>} - The RPC Target URL confirmed.
|
* @returns {Promise<String>} - The RPC Target URL confirmed.
|
||||||
*/
|
*/
|
||||||
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
|
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
|
||||||
const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail()
|
const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail()
|
||||||
const rpcSettings = frequentRpcListDetail.find((rpc) => rpcTarget === rpc.rpcUrl)
|
const rpcSettings = frequentRpcListDetail.find((rpc) => rpcTarget === rpc.rpcUrl)
|
||||||
|
|
||||||
if (rpcSettings) {
|
if (rpcSettings) {
|
||||||
this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname)
|
this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname, rpcPrefs)
|
||||||
} else {
|
} else {
|
||||||
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname)
|
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname, rpcPrefs)
|
||||||
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname)
|
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname, rpcPrefs)
|
||||||
}
|
}
|
||||||
return rpcTarget
|
return rpcTarget
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,8 @@
|
|||||||
"type": "testnet"
|
"type": "testnet"
|
||||||
},
|
},
|
||||||
"shapeShiftTxList": [],
|
"shapeShiftTxList": [],
|
||||||
"lostAccounts": []
|
"lostAccounts": [],
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -134,7 +134,8 @@
|
|||||||
"useNativeCurrencyAsPrimaryCurrency": true,
|
"useNativeCurrencyAsPrimaryCurrency": true,
|
||||||
"showFiatInTestnets": true
|
"showFiatInTestnets": true
|
||||||
},
|
},
|
||||||
"completedUiMigration": true
|
"completedUiMigration": true,
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -157,7 +157,8 @@
|
|||||||
"preferences": {
|
"preferences": {
|
||||||
"useNativeCurrencyAsPrimaryCurrency": true
|
"useNativeCurrencyAsPrimaryCurrency": true
|
||||||
},
|
},
|
||||||
"completedUiMigration": true
|
"completedUiMigration": true,
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -116,7 +116,8 @@
|
|||||||
"useNativeCurrencyAsPrimaryCurrency": true,
|
"useNativeCurrencyAsPrimaryCurrency": true,
|
||||||
"showFiatInTestnets": true
|
"showFiatInTestnets": true
|
||||||
},
|
},
|
||||||
"completedUiMigration": true
|
"completedUiMigration": true,
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -138,7 +138,8 @@
|
|||||||
"useNativeCurrencyAsPrimaryCurrency": true,
|
"useNativeCurrencyAsPrimaryCurrency": true,
|
||||||
"showFiatInTestnets": true
|
"showFiatInTestnets": true
|
||||||
},
|
},
|
||||||
"completedUiMigration": true
|
"completedUiMigration": true,
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -117,7 +117,8 @@
|
|||||||
"useNativeCurrencyAsPrimaryCurrency": true,
|
"useNativeCurrencyAsPrimaryCurrency": true,
|
||||||
"showFiatInTestnets": true
|
"showFiatInTestnets": true
|
||||||
},
|
},
|
||||||
"completedUiMigration": true
|
"completedUiMigration": true,
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -87,7 +87,8 @@
|
|||||||
"type": "testnet"
|
"type": "testnet"
|
||||||
},
|
},
|
||||||
"shapeShiftTxList": [],
|
"shapeShiftTxList": [],
|
||||||
"lostAccounts": []
|
"lostAccounts": [],
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -1059,7 +1059,8 @@
|
|||||||
"preferences": {
|
"preferences": {
|
||||||
"useNativeCurrencyAsPrimaryCurrency": true
|
"useNativeCurrencyAsPrimaryCurrency": true
|
||||||
},
|
},
|
||||||
"completedUiMigration": true
|
"completedUiMigration": true,
|
||||||
|
"frequentRpcListDetail": []
|
||||||
},
|
},
|
||||||
"appState": {
|
"appState": {
|
||||||
"menuOpen": false,
|
"menuOpen": false,
|
||||||
|
@ -1341,11 +1341,14 @@ describe('MetaMask', function () {
|
|||||||
await customRpcButton.click()
|
await customRpcButton.click()
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const customRpcInput = await findElement(driver, By.css('input[placeholder="New RPC URL"]'))
|
await findElement(driver, By.css('.settings-page__sub-header-text'))
|
||||||
|
|
||||||
|
const customRpcInputs = await findElements(driver, By.css('input[type="text"]'))
|
||||||
|
const customRpcInput = customRpcInputs[1]
|
||||||
await customRpcInput.clear()
|
await customRpcInput.clear()
|
||||||
await customRpcInput.sendKeys(customRpcUrl)
|
await customRpcInput.sendKeys(customRpcUrl)
|
||||||
|
|
||||||
const customRpcSave = await findElement(driver, By.css('.settings-tab__rpc-save-button'))
|
const customRpcSave = await findElement(driver, By.css('.page-container__footer-button'))
|
||||||
await customRpcSave.click()
|
await customRpcSave.click()
|
||||||
await delay(largeDelayMs * 2)
|
await delay(largeDelayMs * 2)
|
||||||
})
|
})
|
||||||
|
@ -527,14 +527,14 @@ describe('preferences controller', function () {
|
|||||||
it('should add custom RPC url to state', function () {
|
it('should add custom RPC url to state', function () {
|
||||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||||
preferencesController.addToFrequentRpcList('http://localhost:8545', 1)
|
preferencesController.addToFrequentRpcList('http://localhost:8545', 1)
|
||||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }])
|
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
|
||||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }])
|
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should remove custom RPC url from state', function () {
|
it('should remove custom RPC url from state', function () {
|
||||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }])
|
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
|
||||||
preferencesController.removeFromFrequentRpcList('other_rpc_url')
|
preferencesController.removeFromFrequentRpcList('other_rpc_url')
|
||||||
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
|
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
|
||||||
preferencesController.removeFromFrequentRpcList('rpc_url')
|
preferencesController.removeFromFrequentRpcList('rpc_url')
|
||||||
|
@ -4,7 +4,7 @@ const h = require('react-hyperscript')
|
|||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const actions = require('../../../store/actions')
|
const actions = require('../../../store/actions')
|
||||||
const { getSelectedIdentity } = require('../../../selectors/selectors')
|
const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
|
||||||
const genAccountLink = require('../../../../lib/account-link.js')
|
const genAccountLink = require('../../../../lib/account-link.js')
|
||||||
const { Menu, Item, CloseArea } = require('./components/menu')
|
const { Menu, Item, CloseArea } = require('./components/menu')
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ function mapStateToProps (state) {
|
|||||||
selectedIdentity: getSelectedIdentity(state),
|
selectedIdentity: getSelectedIdentity(state),
|
||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
keyrings: state.metamask.keyrings,
|
keyrings: state.metamask.keyrings,
|
||||||
|
rpcPrefs: getRpcPrefsForCurrentProvider(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,8 +29,8 @@ function mapDispatchToProps (dispatch) {
|
|||||||
showAccountDetailModal: () => {
|
showAccountDetailModal: () => {
|
||||||
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
|
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
|
||||||
},
|
},
|
||||||
viewOnEtherscan: (address, network) => {
|
viewOnEtherscan: (address, network, rpcPrefs) => {
|
||||||
global.platform.openWindow({ url: genAccountLink(address, network) })
|
global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
|
||||||
},
|
},
|
||||||
showRemoveAccountConfirmationModal: (identity) => {
|
showRemoveAccountConfirmationModal: (identity) => {
|
||||||
return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
|
return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
|
||||||
@ -56,7 +57,9 @@ AccountDetailsDropdown.prototype.render = function () {
|
|||||||
keyrings,
|
keyrings,
|
||||||
showAccountDetailModal,
|
showAccountDetailModal,
|
||||||
viewOnEtherscan,
|
viewOnEtherscan,
|
||||||
showRemoveAccountConfirmationModal } = this.props
|
showRemoveAccountConfirmationModal,
|
||||||
|
rpcPrefs,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
const address = selectedIdentity.address
|
const address = selectedIdentity.address
|
||||||
|
|
||||||
@ -112,10 +115,12 @@ AccountDetailsDropdown.prototype.render = function () {
|
|||||||
name: 'Clicked View on Etherscan',
|
name: 'Clicked View on Etherscan',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
viewOnEtherscan(address, network)
|
viewOnEtherscan(address, network, rpcPrefs)
|
||||||
this.props.onClose()
|
this.props.onClose()
|
||||||
},
|
},
|
||||||
text: this.context.t('viewOnEtherscan'),
|
text: (rpcPrefs.blockExplorerUrl
|
||||||
|
? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
|
||||||
|
: this.context.t('viewOnEtherscan')),
|
||||||
icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }),
|
icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }),
|
||||||
}),
|
}),
|
||||||
isRemovable ? h(Item, {
|
isRemovable ? h(Item, {
|
||||||
|
@ -10,7 +10,7 @@ const Dropdown = require('./components/dropdown').Dropdown
|
|||||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
||||||
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
|
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
|
||||||
const R = require('ramda')
|
const R = require('ramda')
|
||||||
const { ADVANCED_ROUTE } = require('../../../helpers/constants/routes')
|
const { NETWORKS_ROUTE } = require('../../../helpers/constants/routes')
|
||||||
|
|
||||||
// classes from nodes of the toggle element.
|
// classes from nodes of the toggle element.
|
||||||
const notToggleElementClassnames = [
|
const notToggleElementClassnames = [
|
||||||
@ -49,6 +49,7 @@ function mapDispatchToProps (dispatch) {
|
|||||||
},
|
},
|
||||||
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
|
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
|
||||||
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
|
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
|
||||||
|
setNetworksTabAddMode: isInAddMode => dispatch(actions.setNetworksTabAddMode(isInAddMode)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ module.exports = compose(
|
|||||||
// TODO: specify default props and proptypes
|
// TODO: specify default props and proptypes
|
||||||
NetworkDropdown.prototype.render = function () {
|
NetworkDropdown.prototype.render = function () {
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props
|
||||||
const rpcListDetail = props.frequentRpcListDetail
|
const rpcListDetail = props.frequentRpcListDetail
|
||||||
const isOpen = this.props.networkDropdownOpen
|
const isOpen = this.props.networkDropdownOpen
|
||||||
const dropdownMenuItemStyle = {
|
const dropdownMenuItemStyle = {
|
||||||
@ -255,7 +256,10 @@ NetworkDropdown.prototype.render = function () {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
{
|
{
|
||||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||||
onClick: () => this.props.history.push(ADVANCED_ROUTE),
|
onClick: () => {
|
||||||
|
setNetworksTabAddMode(true)
|
||||||
|
this.props.history.push(NETWORKS_ROUTE)
|
||||||
|
},
|
||||||
style: dropdownMenuItemStyle,
|
style: dropdownMenuItemStyle,
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
@ -5,7 +5,7 @@ const inherits = require('util').inherits
|
|||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const actions = require('../../../store/actions')
|
const actions = require('../../../store/actions')
|
||||||
const AccountModalContainer = require('./account-modal-container')
|
const AccountModalContainer = require('./account-modal-container')
|
||||||
const { getSelectedIdentity } = require('../../../selectors/selectors')
|
const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
|
||||||
const genAccountLink = require('../../../../lib/account-link.js')
|
const genAccountLink = require('../../../../lib/account-link.js')
|
||||||
const QrView = require('../../ui/qr-code')
|
const QrView = require('../../ui/qr-code')
|
||||||
const EditableLabel = require('../../ui/editable-label')
|
const EditableLabel = require('../../ui/editable-label')
|
||||||
@ -17,6 +17,7 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
selectedIdentity: getSelectedIdentity(state),
|
selectedIdentity: getSelectedIdentity(state),
|
||||||
keyrings: state.metamask.keyrings,
|
keyrings: state.metamask.keyrings,
|
||||||
|
rpcPrefs: getRpcPrefsForCurrentProvider(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ AccountDetailsModal.prototype.render = function () {
|
|||||||
showExportPrivateKeyModal,
|
showExportPrivateKeyModal,
|
||||||
setAccountLabel,
|
setAccountLabel,
|
||||||
keyrings,
|
keyrings,
|
||||||
|
rpcPrefs,
|
||||||
} = this.props
|
} = this.props
|
||||||
const { name, address } = selectedIdentity
|
const { name, address } = selectedIdentity
|
||||||
|
|
||||||
@ -86,8 +88,12 @@ AccountDetailsModal.prototype.render = function () {
|
|||||||
h(Button, {
|
h(Button, {
|
||||||
type: 'secondary',
|
type: 'secondary',
|
||||||
className: 'account-modal__button',
|
className: 'account-modal__button',
|
||||||
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
|
onClick: () => {
|
||||||
}, this.context.t('etherscanView')),
|
global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
|
||||||
|
},
|
||||||
|
}, (rpcPrefs.blockExplorerUrl
|
||||||
|
? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
|
||||||
|
: this.context.t('viewOnEtherscan'))),
|
||||||
|
|
||||||
// Holding on redesign for Export Private Key functionality
|
// Holding on redesign for Export Private Key functionality
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import copyToClipboard from 'copy-to-clipboard'
|
import copyToClipboard from 'copy-to-clipboard'
|
||||||
|
import {
|
||||||
|
getBlockExplorerUrlForTx,
|
||||||
|
} from '../../../helpers/utils/transactions.util'
|
||||||
import SenderToRecipient from '../../ui/sender-to-recipient'
|
import SenderToRecipient from '../../ui/sender-to-recipient'
|
||||||
import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants'
|
import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants'
|
||||||
import TransactionActivityLog from '../transaction-activity-log'
|
import TransactionActivityLog from '../transaction-activity-log'
|
||||||
import TransactionBreakdown from '../transaction-breakdown'
|
import TransactionBreakdown from '../transaction-breakdown'
|
||||||
import Button from '../../ui/button'
|
import Button from '../../ui/button'
|
||||||
import Tooltip from '../../ui/tooltip'
|
import Tooltip from '../../ui/tooltip'
|
||||||
import prefixForNetwork from '../../../../lib/etherscan-prefix-for-network'
|
|
||||||
|
|
||||||
export default class TransactionListItemDetails extends PureComponent {
|
export default class TransactionListItemDetails extends PureComponent {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -22,6 +24,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
showRetry: PropTypes.bool,
|
showRetry: PropTypes.bool,
|
||||||
cancelDisabled: PropTypes.bool,
|
cancelDisabled: PropTypes.bool,
|
||||||
transactionGroup: PropTypes.object,
|
transactionGroup: PropTypes.object,
|
||||||
|
rpcPrefs: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -30,12 +33,9 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleEtherscanClick = () => {
|
handleEtherscanClick = () => {
|
||||||
const { transactionGroup: { primaryTransaction } } = this.props
|
const { transactionGroup: { primaryTransaction }, rpcPrefs } = this.props
|
||||||
const { hash, metamaskNetworkId } = primaryTransaction
|
const { hash, metamaskNetworkId } = primaryTransaction
|
||||||
|
|
||||||
const prefix = prefixForNetwork(metamaskNetworkId)
|
|
||||||
const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
|
|
||||||
|
|
||||||
this.context.metricsEvent({
|
this.context.metricsEvent({
|
||||||
eventOpts: {
|
eventOpts: {
|
||||||
category: 'Navigation',
|
category: 'Navigation',
|
||||||
@ -44,7 +44,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
global.platform.openWindow({ url: etherscanUrl })
|
global.platform.openWindow({ url: getBlockExplorerUrlForTx(metamaskNetworkId, hash, rpcPrefs) })
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCancel = event => {
|
handleCancel = event => {
|
||||||
@ -125,6 +125,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
showRetry,
|
showRetry,
|
||||||
onCancel,
|
onCancel,
|
||||||
onRetry,
|
onRetry,
|
||||||
|
rpcPrefs: { blockExplorerUrl } = {},
|
||||||
} = this.props
|
} = this.props
|
||||||
const { primaryTransaction: transaction } = transactionGroup
|
const { primaryTransaction: transaction } = transactionGroup
|
||||||
const { txParams: { to, from } = {} } = transaction
|
const { txParams: { to, from } = {} } = transaction
|
||||||
@ -158,7 +159,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={t('viewOnEtherscan')}>
|
<Tooltip title={blockExplorerUrl ? t('viewOnCustomBlockExplorer', [blockExplorerUrl]) : t('viewOnEtherscan')}>
|
||||||
<Button
|
<Button
|
||||||
type="raised"
|
type="raised"
|
||||||
onClick={this.handleEtherscanClick}
|
onClick={this.handleEtherscanClick}
|
||||||
|
@ -33,6 +33,7 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
fetchBasicGasAndTimeEstimates: PropTypes.func,
|
fetchBasicGasAndTimeEstimates: PropTypes.func,
|
||||||
fetchGasEstimates: PropTypes.func,
|
fetchGasEstimates: PropTypes.func,
|
||||||
|
rpcPrefs: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -161,6 +162,7 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
showRetry,
|
showRetry,
|
||||||
tokenData,
|
tokenData,
|
||||||
transactionGroup,
|
transactionGroup,
|
||||||
|
rpcPrefs,
|
||||||
} = this.props
|
} = this.props
|
||||||
const { txParams = {} } = transaction
|
const { txParams = {} } = transaction
|
||||||
const { showTransactionDetails } = this.state
|
const { showTransactionDetails } = this.state
|
||||||
@ -216,6 +218,7 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
onCancel={this.handleCancel}
|
onCancel={this.handleCancel}
|
||||||
showCancel={showCancel}
|
showCancel={showCancel}
|
||||||
cancelDisabled={!hasEnoughCancelGas}
|
cancelDisabled={!hasEnoughCancelGas}
|
||||||
|
rpcPrefs={rpcPrefs}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -18,12 +18,14 @@ import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSe
|
|||||||
import { isBalanceSufficient } from '../../../pages/send/send.utils'
|
import { isBalanceSufficient } from '../../../pages/send/send.utils'
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const { metamask: { knownMethodData, accounts } } = state
|
const { metamask: { knownMethodData, accounts, provider, frequentRpcListDetail } } = state
|
||||||
const { showFiatInTestnets } = preferencesSelector(state)
|
const { showFiatInTestnets } = preferencesSelector(state)
|
||||||
const isMainnet = getIsMainnet(state)
|
const isMainnet = getIsMainnet(state)
|
||||||
const { transactionGroup: { primaryTransaction } = {} } = ownProps
|
const { transactionGroup: { primaryTransaction } = {} } = ownProps
|
||||||
const { txParams: { gas: gasLimit, gasPrice } = {} } = primaryTransaction
|
const { txParams: { gas: gasLimit, gasPrice } = {} } = primaryTransaction
|
||||||
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
|
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
|
||||||
|
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
|
||||||
|
const { rpcPrefs } = selectRpcInfo || {}
|
||||||
|
|
||||||
const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({
|
const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({
|
||||||
amount: '0x0',
|
amount: '0x0',
|
||||||
@ -40,6 +42,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
showFiat: (isMainnet || !!showFiatInTestnets),
|
showFiat: (isMainnet || !!showFiatInTestnets),
|
||||||
selectedAccountBalance,
|
selectedAccountBalance,
|
||||||
hasEnoughCancelGas,
|
hasEnoughCancelGas,
|
||||||
|
rpcPrefs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,11 +41,11 @@ const styles = {
|
|||||||
inputFocused: {},
|
inputFocused: {},
|
||||||
inputRoot: {
|
inputRoot: {
|
||||||
'label + &': {
|
'label + &': {
|
||||||
marginTop: '8px',
|
marginTop: '9px',
|
||||||
},
|
},
|
||||||
border: '1px solid #d2d8dd',
|
border: '2px solid #BBC0C5',
|
||||||
height: '48px',
|
height: '48px',
|
||||||
borderRadius: '4px',
|
borderRadius: '6px',
|
||||||
padding: '0 16px',
|
padding: '0 16px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -77,6 +77,8 @@ function reduceApp (state, action) {
|
|||||||
ledger: `m/44'/60'/0'/0/0`,
|
ledger: `m/44'/60'/0'/0/0`,
|
||||||
},
|
},
|
||||||
lastSelectedProvider: null,
|
lastSelectedProvider: null,
|
||||||
|
networksTabSelectedRpcUrl: '',
|
||||||
|
networksTabIsInAddMode: false,
|
||||||
}, state.appState)
|
}, state.appState)
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -751,6 +753,16 @@ function reduceApp (state, action) {
|
|||||||
lastSelectedProvider: action.value,
|
lastSelectedProvider: action.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case actions.SET_SELECTED_SETTINGS_RPC_URL:
|
||||||
|
return extend(appState, {
|
||||||
|
networksTabSelectedRpcUrl: action.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
case actions.SET_NETWORKS_TAB_ADD_MODE:
|
||||||
|
return extend(appState, {
|
||||||
|
networksTabIsInAddMode: action.value,
|
||||||
|
})
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return appState
|
return appState
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ const ADVANCED_ROUTE = '/settings/advanced'
|
|||||||
const SECURITY_ROUTE = '/settings/security'
|
const SECURITY_ROUTE = '/settings/security'
|
||||||
const COMPANY_ROUTE = '/settings/company'
|
const COMPANY_ROUTE = '/settings/company'
|
||||||
const ABOUT_US_ROUTE = '/settings/about-us'
|
const ABOUT_US_ROUTE = '/settings/about-us'
|
||||||
|
const NETWORKS_ROUTE = '/settings/networks'
|
||||||
const REVEAL_SEED_ROUTE = '/seed'
|
const REVEAL_SEED_ROUTE = '/seed'
|
||||||
const MOBILE_SYNC_ROUTE = '/mobile-sync'
|
const MOBILE_SYNC_ROUTE = '/mobile-sync'
|
||||||
const CONFIRM_SEED_ROUTE = '/confirm-seed'
|
const CONFIRM_SEED_ROUTE = '/confirm-seed'
|
||||||
@ -86,4 +87,5 @@ module.exports = {
|
|||||||
COMPANY_ROUTE,
|
COMPANY_ROUTE,
|
||||||
GENERAL_ROUTE,
|
GENERAL_ROUTE,
|
||||||
ABOUT_US_ROUTE,
|
ABOUT_US_ROUTE,
|
||||||
|
NETWORKS_ROUTE,
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import {
|
|||||||
TRANSACTION_TYPE_CANCEL,
|
TRANSACTION_TYPE_CANCEL,
|
||||||
TRANSACTION_STATUS_CONFIRMED,
|
TRANSACTION_STATUS_CONFIRMED,
|
||||||
} from '../../../../app/scripts/controllers/transactions/enums'
|
} from '../../../../app/scripts/controllers/transactions/enums'
|
||||||
|
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TOKEN_METHOD_TRANSFER,
|
TOKEN_METHOD_TRANSFER,
|
||||||
@ -188,3 +190,17 @@ export function getStatusKey (transaction) {
|
|||||||
|
|
||||||
return transaction.status
|
return transaction.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an external block explorer URL at which a transaction can be viewed.
|
||||||
|
* @param {number} networkId
|
||||||
|
* @param {string} hash
|
||||||
|
* @param {Object} rpcPrefs
|
||||||
|
*/
|
||||||
|
export function getBlockExplorerUrlForTx (networkId, hash, rpcPrefs = {}) {
|
||||||
|
if (rpcPrefs.blockExplorerUrl) {
|
||||||
|
return `${rpcPrefs.blockExplorerUrl}/tx/${hash}`
|
||||||
|
}
|
||||||
|
const prefix = prefixForNetwork(networkId)
|
||||||
|
return `https://${prefix}etherscan.io/tx/${hash}`
|
||||||
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
@import 'info-tab/index';
|
@import 'info-tab/index';
|
||||||
|
|
||||||
|
@import 'networks-tab/index';
|
||||||
|
|
||||||
@import 'settings-tab/index';
|
@import 'settings-tab/index';
|
||||||
|
|
||||||
.settings-page {
|
.settings-page {
|
||||||
@ -13,7 +15,6 @@
|
|||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid $alto;
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
@ -33,6 +34,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__sub-header {
|
||||||
|
height: 72px;
|
||||||
|
border-bottom: 1px solid #D8D8D8;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
height: 69px;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sub-header-text {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__back-button {
|
&__back-button {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
@ -60,8 +89,9 @@
|
|||||||
&__content {
|
&__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
height: auto;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
border-top: 1px solid #D8D8D8;
|
||||||
|
|
||||||
&__tabs {
|
&__tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -93,7 +123,7 @@
|
|||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
|
|
||||||
@media screen and (min-width: 576px) {
|
@media screen and (min-width: 576px) {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
1
ui/app/pages/settings/networks-tab/index.js
Normal file
1
ui/app/pages/settings/networks-tab/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './networks-tab.container'
|
200
ui/app/pages/settings/networks-tab/index.scss
Normal file
200
ui/app/pages/settings/networks-tab/index.scss
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
.networks-tab {
|
||||||
|
&__content {
|
||||||
|
margin-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 739px;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
padding: 12px 24px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__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: .5;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
margin-right: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__network-form {
|
||||||
|
flex: 0.5 0 auto;
|
||||||
|
max-width: 343px;
|
||||||
|
max-height: 465px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.page-container__footer {
|
||||||
|
border-top: none;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
width: 93%;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 10px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__network-form-row {
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 93%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__network-form-label {
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__networks-list {
|
||||||
|
flex: 0.5 0 auto;
|
||||||
|
max-width: 343px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
max-width: 100vw;
|
||||||
|
width: 100vw;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__add-network-button-wrapper {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__add-network-header-button-wrapper {
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: 21px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 178px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__networks-list--selection {
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__networks-list-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 13px 0px 13px 17px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.menu-icon-circle {
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
padding: 20px 23px 21px 17px;
|
||||||
|
border-bottom: 1px solid #D8D8D8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__networks-list-item:last-of-type {
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__networks-list-name {
|
||||||
|
margin-left: 11px;
|
||||||
|
font-family: Roboto;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 23px;
|
||||||
|
color: #6A737D;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__networks-list-arrow {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
display: block;
|
||||||
|
background-image: url('/images/caret-right.svg');
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__networks-list-name--selected {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
}
|
1
ui/app/pages/settings/networks-tab/network-form/index.js
Normal file
1
ui/app/pages/settings/networks-tab/network-form/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './network-form.component'
|
@ -0,0 +1,225 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import validUrl from 'valid-url'
|
||||||
|
import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'
|
||||||
|
import TextField from '../../../../components/ui/text-field'
|
||||||
|
|
||||||
|
export default class NetworksTab extends PureComponent {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
metricsEvent: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
editRpc: PropTypes.func.isRequired,
|
||||||
|
rpcUrl: PropTypes.string,
|
||||||
|
chainId: PropTypes.string,
|
||||||
|
ticker: PropTypes.string,
|
||||||
|
viewOnly: PropTypes.bool,
|
||||||
|
networkName: PropTypes.string,
|
||||||
|
onClear: PropTypes.func.isRequired,
|
||||||
|
setRpcTarget: PropTypes.func.isRequired,
|
||||||
|
networksTabIsInAddMode: PropTypes.bool,
|
||||||
|
blockExplorerUrl: PropTypes.string,
|
||||||
|
rpcPrefs: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
rpcUrl: this.props.rpcUrl,
|
||||||
|
chainId: this.props.chainId,
|
||||||
|
ticker: this.props.ticker,
|
||||||
|
networkName: this.props.networkName,
|
||||||
|
blockExplorerUrl: this.props.blockExplorerUrl,
|
||||||
|
errors: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
const { rpcUrl: prevRpcUrl, networksTabIsInAddMode: prevAddMode } = prevProps
|
||||||
|
const {
|
||||||
|
rpcUrl,
|
||||||
|
chainId,
|
||||||
|
ticker,
|
||||||
|
networkName,
|
||||||
|
networksTabIsInAddMode,
|
||||||
|
blockExplorerUrl,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
if (!prevAddMode && networksTabIsInAddMode) {
|
||||||
|
this.setState({
|
||||||
|
rpcUrl: '',
|
||||||
|
chainId: '',
|
||||||
|
ticker: '',
|
||||||
|
networkName: '',
|
||||||
|
blockExplorerUrl: '',
|
||||||
|
errors: {},
|
||||||
|
})
|
||||||
|
} else if (prevRpcUrl !== rpcUrl) {
|
||||||
|
this.setState({ rpcUrl, chainId, ticker, networkName, blockExplorerUrl, errors: {} })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.props.onClear()
|
||||||
|
this.setState({
|
||||||
|
rpcUrl: '',
|
||||||
|
chainId: '',
|
||||||
|
ticker: '',
|
||||||
|
networkName: '',
|
||||||
|
blockExplorerUrl: '',
|
||||||
|
errors: {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stateIsUnchanged () {
|
||||||
|
const {
|
||||||
|
rpcUrl,
|
||||||
|
chainId,
|
||||||
|
ticker,
|
||||||
|
networkName,
|
||||||
|
blockExplorerUrl,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const {
|
||||||
|
rpcUrl: stateRpcUrl,
|
||||||
|
chainId: stateChainId,
|
||||||
|
ticker: stateTicker,
|
||||||
|
networkName: stateNetworkName,
|
||||||
|
blockExplorerUrl: stateBlockExplorerUrl,
|
||||||
|
} = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
stateRpcUrl === rpcUrl &&
|
||||||
|
stateChainId === chainId &&
|
||||||
|
stateTicker === ticker &&
|
||||||
|
stateNetworkName === networkName &&
|
||||||
|
stateBlockExplorerUrl === blockExplorerUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFormTextField (fieldKey, textFieldId, onChange, value, optionalTextFieldKey) {
|
||||||
|
const { errors } = this.state
|
||||||
|
const { viewOnly } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="networks-tab__network-form-row">
|
||||||
|
<div className="networks-tab__network-form-label">{this.context.t(optionalTextFieldKey || fieldKey)}</div>
|
||||||
|
<TextField
|
||||||
|
type="text"
|
||||||
|
id={textFieldId}
|
||||||
|
onChange={onChange}
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
value={value}
|
||||||
|
disabled={viewOnly}
|
||||||
|
error={errors[fieldKey]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setStateWithValue = (stateKey, validator) => {
|
||||||
|
return (e) => {
|
||||||
|
validator && validator(e.target.value, stateKey)
|
||||||
|
this.setState({ [stateKey]: e.target.value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrorTo = (errorKey, errorVal) => {
|
||||||
|
this.setState({
|
||||||
|
errors: {
|
||||||
|
...this.state.errors,
|
||||||
|
[errorKey]: errorVal,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
validateChainId = (chainId) => {
|
||||||
|
this.setErrorTo('chainId', !!chainId && Number.isNaN(parseInt(chainId))
|
||||||
|
? `${this.context.t('invalidInput')} chainId`
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
validateUrl = (url, stateKey) => {
|
||||||
|
if (validUrl.isWebUri(url)) {
|
||||||
|
this.setErrorTo(stateKey, '')
|
||||||
|
} else {
|
||||||
|
const appendedRpc = `http://${url}`
|
||||||
|
const validWhenAppended = validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/)
|
||||||
|
|
||||||
|
this.setErrorTo(stateKey, this.context.t(validWhenAppended ? 'uriErrorMsg' : 'invalidRPC'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { setRpcTarget, viewOnly, rpcUrl: propsRpcUrl, editRpc, rpcPrefs = {} } = this.props
|
||||||
|
const {
|
||||||
|
networkName,
|
||||||
|
rpcUrl,
|
||||||
|
chainId,
|
||||||
|
ticker,
|
||||||
|
blockExplorerUrl,
|
||||||
|
errors,
|
||||||
|
} = this.state
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="networks-tab__network-form">
|
||||||
|
{this.renderFormTextField(
|
||||||
|
'networkName',
|
||||||
|
'network-name',
|
||||||
|
this.setStateWithValue('networkName'),
|
||||||
|
networkName,
|
||||||
|
)}
|
||||||
|
{this.renderFormTextField(
|
||||||
|
'rpcUrl',
|
||||||
|
'rpc-url',
|
||||||
|
this.setStateWithValue('rpcUrl', this.validateUrl),
|
||||||
|
rpcUrl,
|
||||||
|
)}
|
||||||
|
{this.renderFormTextField(
|
||||||
|
'chainId',
|
||||||
|
'chainId',
|
||||||
|
this.setStateWithValue('chainId', this.validateChainId),
|
||||||
|
chainId,
|
||||||
|
'optionalChainId',
|
||||||
|
)}
|
||||||
|
{this.renderFormTextField(
|
||||||
|
'symbol',
|
||||||
|
'network-ticker',
|
||||||
|
this.setStateWithValue('ticker'),
|
||||||
|
ticker,
|
||||||
|
'optionalSymbol',
|
||||||
|
)}
|
||||||
|
{this.renderFormTextField(
|
||||||
|
'blockExplorerUrl',
|
||||||
|
'block-explorer-url',
|
||||||
|
this.setStateWithValue('blockExplorerUrl', this.validateUrl),
|
||||||
|
blockExplorerUrl,
|
||||||
|
'optionalBlockExplorerUrl',
|
||||||
|
)}
|
||||||
|
<PageContainerFooter
|
||||||
|
cancelText={this.context.t('cancel')}
|
||||||
|
hideCancel={true}
|
||||||
|
onSubmit={() => {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
submitText={this.context.t('save')}
|
||||||
|
submitButtonType={'confirm'}
|
||||||
|
disabled={viewOnly || this.stateIsUnchanged() || Object.values(errors).some(x => x) || !rpcUrl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
214
ui/app/pages/settings/networks-tab/networks-tab.component.js
Normal file
214
ui/app/pages/settings/networks-tab/networks-tab.component.js
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { SETTINGS_ROUTE } from '../../../helpers/constants/routes'
|
||||||
|
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
|
||||||
|
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import Button from '../../../components/ui/button'
|
||||||
|
import NetworkForm from './network-form'
|
||||||
|
import NetworkDropdownIcon from '../../../components/app/dropdowns/components/network-dropdown-icon'
|
||||||
|
|
||||||
|
export default class NetworksTab extends PureComponent {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
metricsEvent: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
editRpc: PropTypes.func.isRequired,
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
location: PropTypes.object.isRequired,
|
||||||
|
networkIsSelected: PropTypes.bool,
|
||||||
|
networksTabIsInAddMode: PropTypes.bool,
|
||||||
|
networksToRender: PropTypes.array.isRequired,
|
||||||
|
selectedNetwork: PropTypes.object,
|
||||||
|
setNetworksTabAddMode: PropTypes.func.isRequired,
|
||||||
|
setRpcTarget: PropTypes.func.isRequired,
|
||||||
|
setSelectedSettingsRpcUrl: PropTypes.func.isRequired,
|
||||||
|
providerUrl: PropTypes.string,
|
||||||
|
providerType: PropTypes.string,
|
||||||
|
networkDefaultedToProvider: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.props.setSelectedSettingsRpcUrl(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
isCurrentPath (pathname) {
|
||||||
|
return this.props.location.pathname === pathname
|
||||||
|
}
|
||||||
|
|
||||||
|
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="primary"
|
||||||
|
onClick={event => {
|
||||||
|
event.preventDefault()
|
||||||
|
setSelectedSettingsRpcUrl(null)
|
||||||
|
setNetworksTabAddMode(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ this.context.t('addNetwork') }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNetworkListItem (network, selectRpcUrl) {
|
||||||
|
const {
|
||||||
|
setSelectedSettingsRpcUrl,
|
||||||
|
setNetworksTabAddMode,
|
||||||
|
networkIsSelected,
|
||||||
|
providerUrl,
|
||||||
|
providerType,
|
||||||
|
networksTabIsInAddMode,
|
||||||
|
} = this.props
|
||||||
|
const {
|
||||||
|
border,
|
||||||
|
iconColor,
|
||||||
|
label,
|
||||||
|
labelKey,
|
||||||
|
rpcUrl,
|
||||||
|
providerType: currentProviderType,
|
||||||
|
} = network
|
||||||
|
|
||||||
|
const listItemNetworkIsSelected = selectRpcUrl && selectRpcUrl === rpcUrl
|
||||||
|
const listItemUrlIsProviderUrl = rpcUrl === providerUrl
|
||||||
|
const listItemTypeIsProviderNonRpcType = providerType !== 'rpc' && currentProviderType === providerType
|
||||||
|
const listItemNetworkIsCurrentProvider = !networkIsSelected && !networksTabIsInAddMode && (listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType)
|
||||||
|
const displayNetworkListItemAsSelected = listItemNetworkIsSelected || listItemNetworkIsCurrentProvider
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={'settings-network-list-item:' + rpcUrl}
|
||||||
|
className="networks-tab__networks-list-item"
|
||||||
|
onClick={ () => {
|
||||||
|
setNetworksTabAddMode(false)
|
||||||
|
setSelectedSettingsRpcUrl(rpcUrl)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NetworkDropdownIcon
|
||||||
|
backgroundColor={iconColor || 'white'}
|
||||||
|
innerBorder={border}
|
||||||
|
/>
|
||||||
|
<div className={ classnames('networks-tab__networks-list-name', {
|
||||||
|
'networks-tab__networks-list-name--selected': displayNetworkListItemAsSelected,
|
||||||
|
}) }>
|
||||||
|
{ label || this.context.t(labelKey) }
|
||||||
|
</div>
|
||||||
|
<div className="networks-tab__networks-list-arrow" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNetworksList () {
|
||||||
|
const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classnames('networks-tab__networks-list', {
|
||||||
|
'networks-tab__networks-list--selection': (networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode,
|
||||||
|
})}>
|
||||||
|
{ networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNetworksTabContent () {
|
||||||
|
const {
|
||||||
|
setRpcTarget,
|
||||||
|
setSelectedSettingsRpcUrl,
|
||||||
|
setNetworksTabAddMode,
|
||||||
|
selectedNetwork: {
|
||||||
|
labelKey,
|
||||||
|
label,
|
||||||
|
rpcUrl,
|
||||||
|
chainId,
|
||||||
|
ticker,
|
||||||
|
viewOnly,
|
||||||
|
rpcPrefs,
|
||||||
|
blockExplorerUrl,
|
||||||
|
},
|
||||||
|
networksTabIsInAddMode,
|
||||||
|
editRpc,
|
||||||
|
networkDefaultedToProvider,
|
||||||
|
} = this.props
|
||||||
|
const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="networks-tab__content">
|
||||||
|
{this.renderNetworksList()}
|
||||||
|
{networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider)
|
||||||
|
? <NetworkForm
|
||||||
|
setRpcTarget={setRpcTarget}
|
||||||
|
editRpc={editRpc}
|
||||||
|
networkName={label || labelKey && this.context.t(labelKey) || ''}
|
||||||
|
rpcUrl={rpcUrl}
|
||||||
|
chainId={chainId}
|
||||||
|
ticker={ticker}
|
||||||
|
onClear={() => {
|
||||||
|
setNetworksTabAddMode(false)
|
||||||
|
setSelectedSettingsRpcUrl(null)
|
||||||
|
}}
|
||||||
|
viewOnly={viewOnly}
|
||||||
|
networksTabIsInAddMode={networksTabIsInAddMode}
|
||||||
|
rpcPrefs={rpcPrefs}
|
||||||
|
blockExplorerUrl={blockExplorerUrl}
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent () {
|
||||||
|
const { setNetworksTabAddMode, setSelectedSettingsRpcUrl, networkIsSelected, networksTabIsInAddMode } = 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
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return this.renderContent()
|
||||||
|
}
|
||||||
|
}
|
50
ui/app/pages/settings/networks-tab/networks-tab.constants.js
Normal file
50
ui/app/pages/settings/networks-tab/networks-tab.constants.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
const defaultNetworksData = [
|
||||||
|
{
|
||||||
|
labelKey: 'mainnet',
|
||||||
|
iconColor: '#29B6AF',
|
||||||
|
providerType: 'mainnet',
|
||||||
|
rpcUrl: 'https://api.infura.io/v1/jsonrpc/mainnet',
|
||||||
|
chainId: '1',
|
||||||
|
ticker: 'ETH',
|
||||||
|
blockExplorerUrl: 'https://etherscan.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'ropsten',
|
||||||
|
iconColor: '#FF4A8D',
|
||||||
|
providerType: 'ropsten',
|
||||||
|
rpcUrl: 'https://api.infura.io/v1/jsonrpc/ropsten',
|
||||||
|
chainId: '3',
|
||||||
|
ticker: 'ETH',
|
||||||
|
blockExplorerUrl: 'https://ropsten.etherscan.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'kovan',
|
||||||
|
iconColor: '#9064FF',
|
||||||
|
providerType: 'kovan',
|
||||||
|
rpcUrl: 'https://api.infura.io/v1/jsonrpc/kovan',
|
||||||
|
chainId: '4',
|
||||||
|
ticker: 'ETH',
|
||||||
|
blockExplorerUrl: 'https://etherscan.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'rinkeby',
|
||||||
|
iconColor: '#F6C343',
|
||||||
|
providerType: 'rinkeby',
|
||||||
|
rpcUrl: 'https://api.infura.io/v1/jsonrpc/rinkeby',
|
||||||
|
chainId: '42',
|
||||||
|
ticker: 'ETH',
|
||||||
|
blockExplorerUrl: 'https://rinkeby.etherscan.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labelKey: 'localhost',
|
||||||
|
iconColor: 'white',
|
||||||
|
border: '1px solid #6A737D',
|
||||||
|
providerType: 'localhost',
|
||||||
|
rpcUrl: 'http://localhost:8545/',
|
||||||
|
blockExplorerUrl: 'https://etherscan.io',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export {
|
||||||
|
defaultNetworksData,
|
||||||
|
}
|
77
ui/app/pages/settings/networks-tab/networks-tab.container.js
Normal file
77
ui/app/pages/settings/networks-tab/networks-tab.container.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import NetworksTab from './networks-tab.component'
|
||||||
|
import { compose } from 'recompose'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { withRouter } from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
setSelectedSettingsRpcUrl,
|
||||||
|
updateAndSetCustomRpc,
|
||||||
|
displayWarning,
|
||||||
|
setNetworksTabAddMode,
|
||||||
|
editRpc,
|
||||||
|
} from '../../../store/actions'
|
||||||
|
import { defaultNetworksData } from './networks-tab.constants'
|
||||||
|
const defaultNetworks = defaultNetworksData.map(network => ({ ...network, viewOnly: true }))
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const {
|
||||||
|
frequentRpcListDetail,
|
||||||
|
provider,
|
||||||
|
} = state.metamask
|
||||||
|
const {
|
||||||
|
networksTabSelectedRpcUrl,
|
||||||
|
networksTabIsInAddMode,
|
||||||
|
} = state.appState
|
||||||
|
|
||||||
|
const frequentRpcNetworkListDetails = frequentRpcListDetail.map(rpc => {
|
||||||
|
return {
|
||||||
|
label: rpc.nickname,
|
||||||
|
iconColor: '#6A737D',
|
||||||
|
providerType: 'rpc',
|
||||||
|
rpcUrl: rpc.rpcUrl,
|
||||||
|
chainId: rpc.chainId,
|
||||||
|
ticker: rpc.ticker,
|
||||||
|
blockExplorerUrl: rpc.rpcPrefs && rpc.rpcPrefs.blockExplorerUrl || '',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const networksToRender = [ ...defaultNetworks, ...frequentRpcNetworkListDetails ]
|
||||||
|
let selectedNetwork = networksToRender.find(network => network.rpcUrl === networksTabSelectedRpcUrl) || {}
|
||||||
|
const networkIsSelected = Boolean(selectedNetwork.rpcUrl)
|
||||||
|
|
||||||
|
let networkDefaultedToProvider = false
|
||||||
|
if (!networkIsSelected && !networksTabIsInAddMode) {
|
||||||
|
selectedNetwork = networksToRender.find(network => {
|
||||||
|
return network.rpcUrl === provider.rpcTarget || network.providerType !== 'rpc' && network.providerType === provider.type
|
||||||
|
}) || {}
|
||||||
|
networkDefaultedToProvider = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedNetwork,
|
||||||
|
networksToRender,
|
||||||
|
networkIsSelected,
|
||||||
|
networksTabIsInAddMode,
|
||||||
|
providerType: provider.type,
|
||||||
|
providerUrl: provider.rpcTarget,
|
||||||
|
networkDefaultedToProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
setSelectedSettingsRpcUrl: newRpcUrl => dispatch(setSelectedSettingsRpcUrl(newRpcUrl)),
|
||||||
|
setRpcTarget: (newRpc, chainId, ticker, nickname, rpcPrefs) => {
|
||||||
|
dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname, rpcPrefs))
|
||||||
|
},
|
||||||
|
displayWarning: warning => dispatch(displayWarning(warning)),
|
||||||
|
setNetworksTabAddMode: isInAddMode => dispatch(setNetworksTabAddMode(isInAddMode)),
|
||||||
|
editRpc: (oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs) => {
|
||||||
|
dispatch(editRpc(oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
)(NetworksTab)
|
@ -6,6 +6,7 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util'
|
|||||||
import TabBar from '../../components/app/tab-bar'
|
import TabBar from '../../components/app/tab-bar'
|
||||||
import c from 'classnames'
|
import c from 'classnames'
|
||||||
import SettingsTab from './settings-tab'
|
import SettingsTab from './settings-tab'
|
||||||
|
import NetworksTab from './networks-tab'
|
||||||
import AdvancedTab from './advanced-tab'
|
import AdvancedTab from './advanced-tab'
|
||||||
import InfoTab from './info-tab'
|
import InfoTab from './info-tab'
|
||||||
import SecurityTab from './security-tab'
|
import SecurityTab from './security-tab'
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
GENERAL_ROUTE,
|
GENERAL_ROUTE,
|
||||||
ABOUT_US_ROUTE,
|
ABOUT_US_ROUTE,
|
||||||
SETTINGS_ROUTE,
|
SETTINGS_ROUTE,
|
||||||
|
NETWORKS_ROUTE,
|
||||||
} from '../../helpers/constants/routes'
|
} from '../../helpers/constants/routes'
|
||||||
|
|
||||||
const ROUTES_TO_I18N_KEYS = {
|
const ROUTES_TO_I18N_KEYS = {
|
||||||
@ -55,7 +57,7 @@ class SettingsPage extends PureComponent {
|
|||||||
>
|
>
|
||||||
<div className="settings-page__header">
|
<div className="settings-page__header">
|
||||||
{
|
{
|
||||||
!this.isCurrentPath(SETTINGS_ROUTE) && (
|
!this.isCurrentPath(SETTINGS_ROUTE) && !this.isCurrentPath(NETWORKS_ROUTE) && (
|
||||||
<div
|
<div
|
||||||
className="settings-page__back-button"
|
className="settings-page__back-button"
|
||||||
onClick={() => history.push(SETTINGS_ROUTE)}
|
onClick={() => history.push(SETTINGS_ROUTE)}
|
||||||
@ -104,6 +106,7 @@ class SettingsPage extends PureComponent {
|
|||||||
{ content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE },
|
{ content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE },
|
||||||
{ content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE },
|
{ content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE },
|
||||||
{ content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE },
|
{ content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE },
|
||||||
|
{ content: t('networks'), description: t('networkSettingsDescription'), key: NETWORKS_ROUTE },
|
||||||
{ content: t('about'), description: t('aboutSettingsDescription'), key: ABOUT_US_ROUTE },
|
{ content: t('about'), description: t('aboutSettingsDescription'), key: ABOUT_US_ROUTE },
|
||||||
]}
|
]}
|
||||||
isActive={key => {
|
isActive={key => {
|
||||||
@ -135,6 +138,11 @@ class SettingsPage extends PureComponent {
|
|||||||
path={ADVANCED_ROUTE}
|
path={ADVANCED_ROUTE}
|
||||||
component={AdvancedTab}
|
component={AdvancedTab}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={NETWORKS_ROUTE}
|
||||||
|
component={NetworksTab}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={SECURITY_ROUTE}
|
path={SECURITY_ROUTE}
|
||||||
|
@ -49,6 +49,7 @@ const selectors = {
|
|||||||
getNumberOfTokens,
|
getNumberOfTokens,
|
||||||
isEthereumNetwork,
|
isEthereumNetwork,
|
||||||
getMetaMetricState,
|
getMetaMetricState,
|
||||||
|
getRpcPrefsForCurrentProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = selectors
|
module.exports = selectors
|
||||||
@ -327,3 +328,10 @@ function getMetaMetricState (state) {
|
|||||||
participateInMetaMetrics: state.metamask.participateInMetaMetrics,
|
participateInMetaMetrics: state.metamask.participateInMetaMetrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRpcPrefsForCurrentProvider (state) {
|
||||||
|
const { frequentRpcListDetail, provider } = state.metamask
|
||||||
|
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
|
||||||
|
const { rpcPrefs = {} } = selectRpcInfo || {}
|
||||||
|
return rpcPrefs
|
||||||
|
}
|
||||||
|
@ -239,6 +239,7 @@ var actions = {
|
|||||||
updateAndSetCustomRpc: updateAndSetCustomRpc,
|
updateAndSetCustomRpc: updateAndSetCustomRpc,
|
||||||
setRpcTarget: setRpcTarget,
|
setRpcTarget: setRpcTarget,
|
||||||
delRpcTarget: delRpcTarget,
|
delRpcTarget: delRpcTarget,
|
||||||
|
editRpc: editRpc,
|
||||||
setProviderType: setProviderType,
|
setProviderType: setProviderType,
|
||||||
SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
|
SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
|
||||||
setHardwareWalletDefaultHdPath,
|
setHardwareWalletDefaultHdPath,
|
||||||
@ -350,6 +351,11 @@ var actions = {
|
|||||||
|
|
||||||
setFirstTimeFlowType,
|
setFirstTimeFlowType,
|
||||||
SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE',
|
SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE',
|
||||||
|
|
||||||
|
SET_SELECTED_SETTINGS_RPC_URL: 'SET_SELECTED_SETTINGS_RPC_URL',
|
||||||
|
setSelectedSettingsRpcUrl,
|
||||||
|
SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE',
|
||||||
|
setNetworksTabAddMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = actions
|
module.exports = actions
|
||||||
@ -1958,10 +1964,10 @@ function setPreviousProvider (type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
|
function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`)
|
log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`)
|
||||||
background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => {
|
background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
return dispatch(actions.displayWarning('Had a problem changing networks!'))
|
return dispatch(actions.displayWarning('Had a problem changing networks!'))
|
||||||
@ -1974,6 +1980,29 @@ function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editRpc (oldRpc, newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
|
||||||
|
return (dispatch) => {
|
||||||
|
log.debug(`background.delRpcTarget: ${oldRpc}`)
|
||||||
|
background.delCustomRpc(oldRpc, (err) => {
|
||||||
|
if (err) {
|
||||||
|
log.error(err)
|
||||||
|
return dispatch(self.displayWarning('Had a problem removing network!'))
|
||||||
|
}
|
||||||
|
dispatch(actions.setSelectedToken())
|
||||||
|
background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
|
||||||
|
if (err) {
|
||||||
|
log.error(err)
|
||||||
|
return dispatch(actions.displayWarning('Had a problem changing networks!'))
|
||||||
|
}
|
||||||
|
dispatch({
|
||||||
|
type: actions.SET_RPC_TARGET,
|
||||||
|
value: newRpc,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
|
function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
|
log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
|
||||||
@ -2000,6 +2029,7 @@ function delRpcTarget (oldRpc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Calls the addressBookController to add a new address.
|
// Calls the addressBookController to add a new address.
|
||||||
function addToAddressBook (recipient, nickname = '') {
|
function addToAddressBook (recipient, nickname = '') {
|
||||||
log.debug(`background.addToAddressBook`)
|
log.debug(`background.addToAddressBook`)
|
||||||
@ -2716,3 +2746,17 @@ function setFirstTimeFlowType (type) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSelectedSettingsRpcUrl (newRpcUrl) {
|
||||||
|
return {
|
||||||
|
type: actions.SET_SELECTED_SETTINGS_RPC_URL,
|
||||||
|
value: newRpcUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNetworksTabAddMode (isInAddMode) {
|
||||||
|
return {
|
||||||
|
type: actions.SET_NETWORKS_TAB_ADD_MODE,
|
||||||
|
value: isInAddMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
module.exports = function (address, network) {
|
module.exports = function (address, network, rpcPrefs) {
|
||||||
|
if (rpcPrefs.blockExplorerUrl) {
|
||||||
|
return `${rpcPrefs.blockExplorerUrl}/address/${address}`
|
||||||
|
}
|
||||||
|
|
||||||
const net = parseInt(network)
|
const net = parseInt(network)
|
||||||
let link
|
let link
|
||||||
switch (net) {
|
switch (net) {
|
||||||
|
Loading…
Reference in New Issue
Block a user