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

Merge pull request #5893 from MetaMask/loading-network-screen

Loading network screen
This commit is contained in:
Dan J Miller 2018-12-13 14:20:27 -03:30 committed by GitHub
commit c5861c88a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 341 additions and 38 deletions

View File

@ -242,6 +242,9 @@
"connecting": { "connecting": {
"message": "Connecting..." "message": "Connecting..."
}, },
"connectingTo": {
"message": "Connecting to $1"
},
"connectingToKovan": { "connectingToKovan": {
"message": "Connecting to Kovan Test Network" "message": "Connecting to Kovan Test Network"
}, },
@ -1198,6 +1201,9 @@
"sigRequested": { "sigRequested": {
"message": "Signature Requested" "message": "Signature Requested"
}, },
"somethingWentWrong": {
"message": "Oops! Something went wrong."
},
"spaceBetween": { "spaceBetween": {
"message": "there can only be a space between words" "message": "there can only be a space between words"
}, },
@ -1216,6 +1222,9 @@
"speedUpTransaction": { "speedUpTransaction": {
"message": "Speed up this transaction" "message": "Speed up this transaction"
}, },
"switchNetworks": {
"message": "Switch Networks"
},
"status": { "status": {
"message": "Status" "message": "Status"
}, },

View File

@ -1079,8 +1079,10 @@ describe('Actions', () => {
describe('#setProviderType', () => { describe('#setProviderType', () => {
let setProviderTypeSpy let setProviderTypeSpy
let store
beforeEach(() => { beforeEach(() => {
store = mockStore({ metamask: { provider: {} } })
setProviderTypeSpy = sinon.stub(background, 'setProviderType') setProviderTypeSpy = sinon.stub(background, 'setProviderType')
}) })
@ -1089,13 +1091,11 @@ describe('Actions', () => {
}) })
it('', () => { it('', () => {
const store = mockStore()
store.dispatch(actions.setProviderType()) store.dispatch(actions.setProviderType())
assert(setProviderTypeSpy.calledOnce) assert(setProviderTypeSpy.calledOnce)
}) })
it('', () => { it('', () => {
const store = mockStore()
const expectedActions = [ const expectedActions = [
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
] ]

View File

@ -226,6 +226,7 @@ var actions = {
SET_RPC_TARGET: 'SET_RPC_TARGET', SET_RPC_TARGET: 'SET_RPC_TARGET',
SET_DEFAULT_RPC_TARGET: 'SET_DEFAULT_RPC_TARGET', SET_DEFAULT_RPC_TARGET: 'SET_DEFAULT_RPC_TARGET',
SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE', SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE',
SET_PREVIOUS_PROVIDER: 'SET_PREVIOUS_PROVIDER',
showConfigPage, showConfigPage,
SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE', SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE',
SHOW_ADD_SUGGESTED_TOKEN_PAGE: 'SHOW_ADD_SUGGESTED_TOKEN_PAGE', SHOW_ADD_SUGGESTED_TOKEN_PAGE: 'SHOW_ADD_SUGGESTED_TOKEN_PAGE',
@ -1866,13 +1867,15 @@ function createSpeedUpTransaction (txId, customGasPrice) {
// //
function setProviderType (type) { function setProviderType (type) {
return (dispatch) => { return (dispatch, getState) => {
const { type: currentProviderType } = getState().metamask.provider
log.debug(`background.setProviderType`, type) log.debug(`background.setProviderType`, type)
background.setProviderType(type, (err, result) => { background.setProviderType(type, (err, result) => {
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!'))
} }
dispatch(setPreviousProvider(currentProviderType))
dispatch(actions.updateProviderType(type)) dispatch(actions.updateProviderType(type))
dispatch(actions.setSelectedToken()) dispatch(actions.setSelectedToken())
}) })
@ -1887,6 +1890,13 @@ function updateProviderType (type) {
} }
} }
function setPreviousProvider (type) {
return {
type: actions.SET_PREVIOUS_PROVIDER,
value: type,
}
}
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}`)

View File

@ -7,7 +7,7 @@ const h = require('react-hyperscript')
const actions = require('./actions') const actions = require('./actions')
const classnames = require('classnames') const classnames = require('classnames')
const log = require('loglevel') const log = require('loglevel')
const { getMetaMaskAccounts } = require('./selectors') const { getMetaMaskAccounts, getNetworkIdentifier } = require('./selectors')
// init // init
const InitializeScreen = require('../../mascara/src/app/first-time').default const InitializeScreen = require('../../mascara/src/app/first-time').default
@ -32,6 +32,7 @@ const CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice') const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading-screen') const Loading = require('./components/loading-screen')
const LoadingNetwork = require('./components/loading-network-screen').default
const NetworkDropdown = require('./components/dropdowns/network-dropdown') const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu') const AccountMenu = require('./components/account-menu')
@ -169,9 +170,10 @@ class App extends Component {
h(AccountMenu), h(AccountMenu),
h('div.main-container-wrapper', [ h('div.main-container-wrapper', [
(isLoading || isLoadingNetwork) && h(Loading, { isLoading && h(Loading, {
loadingMessage: loadMessage, loadingMessage: loadMessage,
}), }),
!isLoading && isLoadingNetwork && h(LoadingNetwork),
// content // content
this.renderRoutes(), this.renderRoutes(),
@ -196,7 +198,7 @@ class App extends Component {
if (loadingMessage) { if (loadingMessage) {
return loadingMessage return loadingMessage
} }
const { provider } = this.props const { provider, providerId } = this.props
const providerName = provider.type const providerName = provider.type
let name let name
@ -210,7 +212,7 @@ class App extends Component {
} else if (providerName === 'rinkeby') { } else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby') name = this.context.t('connectingToRinkeby')
} else { } else {
name = this.context.t('connectingToUnknown') name = this.context.t('connectingTo', [providerId])
} }
return name return name
@ -279,6 +281,7 @@ App.propTypes = {
isMouseUser: PropTypes.bool, isMouseUser: PropTypes.bool,
setMouseUserState: PropTypes.func, setMouseUserState: PropTypes.func,
t: PropTypes.func, t: PropTypes.func,
providerId: PropTypes.string,
} }
function mapStateToProps (state) { function mapStateToProps (state) {
@ -348,6 +351,7 @@ function mapStateToProps (state) {
isRevealingSeedWords: state.metamask.isRevealingSeedWords, isRevealingSeedWords: state.metamask.isRevealingSeedWords,
Qr: state.appState.Qr, Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen, welcomeScreenSeen: state.metamask.welcomeScreenSeen,
providerId: getNetworkIdentifier(state),
// state needed to get account dropdown temporarily rendering from app bar // state needed to get account dropdown temporarily rendering from app bar
identities, identities,

View File

@ -16,16 +16,32 @@ NetworkDropdownIcon.prototype.render = function () {
isSelected, isSelected,
innerBorder = 'none', innerBorder = 'none',
diameter = '12', diameter = '12',
loading,
} = this.props } = this.props
return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {}, return loading
h('div', { ? h('span.pointer.network-indicator', {
style: { style: {
background: backgroundColor, display: 'flex',
border: innerBorder, alignItems: 'center',
height: `${diameter}px`, flexDirection: 'row',
width: `${diameter}px`,
}, },
}) }, [
) h('img', {
style: {
width: '27px',
},
src: 'images/loading.svg',
}),
])
: h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
h('div', {
style: {
background: backgroundColor,
border: innerBorder,
height: `${diameter}px`,
width: `${diameter}px`,
},
})
)
} }

View File

@ -0,0 +1 @@
export { default } from './loading-network-screen.container'

View File

@ -0,0 +1,138 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Spinner from '../spinner'
import Button from '../button'
export default class LoadingNetworkScreen extends PureComponent {
state = {
showErrorScreen: false,
}
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
loadingMessage: PropTypes.string,
cancelTime: PropTypes.number,
provider: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
providerId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
showNetworkDropdown: PropTypes.func,
setProviderArgs: PropTypes.array,
lastSelectedProvider: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
setProviderType: PropTypes.func,
isLoadingNetwork: PropTypes.bool,
}
componentDidMount = () => {
this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000)
}
getConnectingLabel = function (loadingMessage) {
if (loadingMessage) {
return loadingMessage
}
const { provider, providerId } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = this.context.t('connectingToMainnet')
} else if (providerName === 'ropsten') {
name = this.context.t('connectingToRopsten')
} else if (providerName === 'kovan') {
name = this.context.t('connectingToKovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
} else {
name = this.context.t('connectingTo', [providerId])
}
return name
}
renderMessage = () => {
return <span>{ this.getConnectingLabel(this.props.loadingMessage) }</span>
}
renderLoadingScreenContent = () => {
return <div className="loading-overlay__screen-content">
<Spinner color="#F7C06C" />
{this.renderMessage()}
</div>
}
renderErrorScreenContent = () => {
const { showNetworkDropdown, setProviderArgs, setProviderType } = this.props
return <div className="loading-overlay__error-screen">
<span className="loading-overlay__emoji">&#128542;</span>
<span>{ this.context.t('somethingWentWrong') }</span>
<div className="loading-overlay__error-buttons">
<Button
type="default"
onClick={() => {
window.clearTimeout(this.cancelCallTimeout)
showNetworkDropdown()
}}
>
{ this.context.t('switchNetworks') }
</Button>
<Button
type="primary"
onClick={() => {
this.setState({ showErrorScreen: false })
setProviderType(...setProviderArgs)
window.clearTimeout(this.cancelCallTimeout)
this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000)
}}
>
{ this.context.t('tryAgain') }
</Button>
</div>
</div>
}
cancelCall = () => {
const { isLoadingNetwork } = this.props
if (isLoadingNetwork) {
this.setState({ showErrorScreen: true })
}
}
componentDidUpdate = (prevProps) => {
const { provider } = this.props
const { provider: prevProvider } = prevProps
if (provider.type !== prevProvider.type) {
window.clearTimeout(this.cancelCallTimeout)
this.setState({ showErrorScreen: false })
this.cancelCallTimeout = setTimeout(this.cancelCall, this.props.cancelTime || 15000)
}
}
componentWillUnmount = () => {
window.clearTimeout(this.cancelCallTimeout)
}
render () {
const { lastSelectedProvider, setProviderType } = this.props
return (
<div className="loading-overlay">
<div
className="page-container__header-close"
onClick={() => setProviderType(lastSelectedProvider || 'ropsten')}
/>
<div className="loading-overlay__container">
{ this.state.showErrorScreen
? this.renderErrorScreenContent()
: this.renderLoadingScreenContent()
}
</div>
</div>
)
}
}

View File

@ -0,0 +1,41 @@
import { connect } from 'react-redux'
import LoadingNetworkScreen from './loading-network-screen.component'
import actions from '../../actions'
import { getNetworkIdentifier } from '../../selectors'
const mapStateToProps = state => {
const {
loadingMessage,
currentView,
} = state.appState
const {
provider,
lastSelectedProvider,
network,
} = state.metamask
const { rpcTarget, chainId, ticker, nickname, type } = provider
const setProviderArgs = type === 'rpc'
? [rpcTarget, chainId, ticker, nickname]
: [provider.type]
return {
isLoadingNetwork: network === 'loading' && currentView.name !== 'config',
loadingMessage,
lastSelectedProvider,
setProviderArgs,
provider,
providerId: getNetworkIdentifier(state),
}
}
const mapDispatchToProps = dispatch => {
return {
setProviderType: (type) => {
dispatch(actions.setProviderType(type))
},
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoadingNetworkScreen)

View File

@ -0,0 +1 @@
export { default } from './loading-network-error.container'

View File

@ -0,0 +1,29 @@
import React from 'react'
import PropTypes from 'prop-types'
import Modal, { ModalContent } from '../../modal'
const LoadingNetworkError = (props, context) => {
const { t } = context
const { hideModal } = props
return (
<Modal
onSubmit={() => hideModal()}
submitText={t('tryAgain')}
>
<ModalContent
description={'Oops! Something went wrong.'}
/>
</Modal>
)
}
LoadingNetworkError.contextTypes = {
t: PropTypes.func,
}
LoadingNetworkError.propTypes = {
hideModal: PropTypes.func,
}
export default LoadingNetworkError

View File

@ -0,0 +1,4 @@
import LoadingNetworkError from './loading-network-error.component'
import withModalProps from '../../../higher-order-components/with-modal-props'
export default withModalProps(LoadingNetworkError)

View File

@ -23,33 +23,19 @@ Network.prototype.render = function () {
const props = this.props const props = this.props
const context = this.context const context = this.context
const networkNumber = props.network const networkNumber = props.network
let providerName, providerNick let providerName, providerNick, providerUrl
try { try {
providerName = props.provider.type providerName = props.provider.type
providerNick = props.provider.nickname || '' providerNick = props.provider.nickname || ''
providerUrl = props.provider.rpcTarget
} catch (e) { } catch (e) {
providerName = null providerName = null
} }
let iconName, hoverText const providerId = providerNick || providerName || providerUrl || null
let iconName
let hoverText
if (networkNumber === 'loading') { if (providerName === 'mainnet') {
return h('span.pointer.network-indicator', {
style: {
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
},
onClick: (event) => this.props.onClick(event),
}, [
h('img', {
title: context.t('attemptingConnect'),
style: {
width: '27px',
},
src: 'images/loading.svg',
}),
])
} else if (providerName === 'mainnet') {
hoverText = context.t('mainnet') hoverText = context.t('mainnet')
iconName = 'ethereum-network' iconName = 'ethereum-network'
} else if (providerName === 'ropsten') { } else if (providerName === 'ropsten') {
@ -65,8 +51,8 @@ Network.prototype.render = function () {
hoverText = context.t('rinkeby') hoverText = context.t('rinkeby')
iconName = 'rinkeby-test-network' iconName = 'rinkeby-test-network'
} else { } else {
hoverText = context.t('unknownNetwork') hoverText = providerId
iconName = 'unknown-private-network' iconName = 'private-network'
} }
return ( return (
@ -92,6 +78,7 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, { h(NetworkDropdownIcon, {
backgroundColor: '#038789', // $blue-lagoon backgroundColor: '#038789', // $blue-lagoon
nonSelectBackgroundColor: '#15afb2', nonSelectBackgroundColor: '#15afb2',
loading: networkNumber === 'loading',
}), }),
h('.network-name', context.t('mainnet')), h('.network-name', context.t('mainnet')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'), h('i.fa.fa-chevron-down.fa-lg.network-caret'),
@ -101,6 +88,7 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, { h(NetworkDropdownIcon, {
backgroundColor: '#e91550', // $crimson backgroundColor: '#e91550', // $crimson
nonSelectBackgroundColor: '#ec2c50', nonSelectBackgroundColor: '#ec2c50',
loading: networkNumber === 'loading',
}), }),
h('.network-name', context.t('ropsten')), h('.network-name', context.t('ropsten')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'), h('i.fa.fa-chevron-down.fa-lg.network-caret'),
@ -110,6 +98,7 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, { h(NetworkDropdownIcon, {
backgroundColor: '#690496', // $purple backgroundColor: '#690496', // $purple
nonSelectBackgroundColor: '#b039f3', nonSelectBackgroundColor: '#b039f3',
loading: networkNumber === 'loading',
}), }),
h('.network-name', context.t('kovan')), h('.network-name', context.t('kovan')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'), h('i.fa.fa-chevron-down.fa-lg.network-caret'),
@ -119,13 +108,31 @@ Network.prototype.render = function () {
h(NetworkDropdownIcon, { h(NetworkDropdownIcon, {
backgroundColor: '#ebb33f', // $tulip-tree backgroundColor: '#ebb33f', // $tulip-tree
nonSelectBackgroundColor: '#ecb23e', nonSelectBackgroundColor: '#ecb23e',
loading: networkNumber === 'loading',
}), }),
h('.network-name', context.t('rinkeby')), h('.network-name', context.t('rinkeby')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'), h('i.fa.fa-chevron-down.fa-lg.network-caret'),
]) ])
default: default:
return h('.network-indicator', [ return h('.network-indicator', [
h('i.fa.fa-question-circle.fa-lg', { networkNumber === 'loading'
? h('span.pointer.network-indicator', {
style: {
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
},
onClick: (event) => this.props.onClick(event),
}, [
h('img', {
title: context.t('attemptingConnect'),
style: {
width: '27px',
},
src: 'images/loading.svg',
}),
])
: h('i.fa.fa-question-circle.fa-lg', {
style: { style: {
margin: '10px', margin: '10px',
color: 'rgb(125, 128, 130)', color: 'rgb(125, 128, 130)',

View File

@ -11,6 +11,12 @@
height: 100%; height: 100%;
background: rgba(255, 255, 255, .8); background: rgba(255, 255, 255, .8);
&__screen-content {
display: flex;
flex-direction: column;
align-items: center;
}
&__container { &__container {
position: absolute; position: absolute;
top: 33%; top: 33%;
@ -26,6 +32,27 @@
font-size: 20px; font-size: 20px;
color: $manatee; color: $manatee;
} }
&__error-screen {
display: flex;
flex-direction: column;
align-items: center;
height: 160px;
justify-content: space-evenly;
}
&__error-buttons {
display: flex;
flex-direction: row;
button {
margin: 5px;
}
}
&__emoji {
font-size: 32px;
}
} }
.spinner { .spinner {

View File

@ -76,6 +76,7 @@ function reduceApp (state, action) {
trezor: `m/44'/60'/0'/0`, trezor: `m/44'/60'/0'/0`,
ledger: `m/44'/60'/0'/0/0`, ledger: `m/44'/60'/0'/0/0`,
}, },
lastSelectedProvider: null,
}, state.appState) }, state.appState)
switch (action.type) { switch (action.type) {
@ -748,6 +749,14 @@ function reduceApp (state, action) {
networkNonce: action.value, networkNonce: action.value,
}) })
case actions.SET_PREVIOUS_PROVIDER:
if (action.value === 'loading') {
return appState
}
return extend(appState, {
lastSelectedProvider: action.value,
})
default: default:
return appState return appState
} }

View File

@ -36,10 +36,17 @@ const selectors = {
preferencesSelector, preferencesSelector,
getMetaMaskAccounts, getMetaMaskAccounts,
getCurrentEthBalance, getCurrentEthBalance,
getNetworkIdentifier,
} }
module.exports = selectors module.exports = selectors
function getNetworkIdentifier (state) {
const { metamask: { provider: { type, nickname, rpcTarget } } } = state
return nickname || rpcTarget || type
}
function getSelectedAddress (state) { function getSelectedAddress (state) {
const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0] const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0]