mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 18:00:18 +01:00
Merge branch 'develop' into TokensPerAccountBasis
This commit is contained in:
commit
c0cdda8321
@ -27,8 +27,9 @@ If you're a web dapp developer, we've got two types of guides for you:
|
||||
## Building locally
|
||||
|
||||
- Install [Node.js](https://nodejs.org/en/) version 8.11.3 and npm version 6.1.0
|
||||
- Install dependencies:
|
||||
- If you are using nvm (recommended) running `nvm use` will automatically choose the right node version for you.
|
||||
- If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
|
||||
- Select npm 6.1.0: ```npm install -g npm@6.1.0```
|
||||
- Install dependencies: ```npm install```
|
||||
- Install gulp globally with `npm install -g gulp-cli`.
|
||||
- Build the project to the `./dist/` folder with `gulp build`.
|
||||
- Optionally, to rebuild on file changes, run `gulp dev`.
|
||||
|
@ -510,6 +510,9 @@
|
||||
"invalidRPC": {
|
||||
"message": "Invalid RPC URI"
|
||||
},
|
||||
"invalidSeedPhrase": {
|
||||
"message": "Invalid seed phrase"
|
||||
},
|
||||
"jsonFail": {
|
||||
"message": "Something went wrong. Please make sure your JSON file is properly formatted."
|
||||
},
|
||||
|
@ -3,26 +3,29 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Phishing Warning</title>
|
||||
<title>Dangerous Website Warning</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: #c50000;
|
||||
padding: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
max-width: 600px;
|
||||
}
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
body {
|
||||
background: #c50000;
|
||||
padding: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@ -50,11 +53,11 @@ a {
|
||||
|
||||
<img src="/images/ethereum-metamask-chrome.png" style="width:100%">
|
||||
<h3>ATTENTION</h3>
|
||||
<p>MetaMask believes this domain to have malicious intent and has prevented you from interacting with it.</p>
|
||||
<p>This is because the site tested positive on the <a href="https://github.com/metamask/eth-phishing-detect">Ethereum Phishing Detector</a>.</p>
|
||||
<p>You can turn MetaMask off to interact with this site, but it's advised not to.</p>
|
||||
<p>If you think this domain is incorrectly flagged, <a href="https://github.com/metamask/eth-phishing-detect/issues/new">please file an issue</a>.</p>
|
||||
<p>MetaMask believes this domain could currently compromise your security and has prevented you from interacting with it.</p>
|
||||
<p>This is because the site tested positive on the <a href="https://github.com/metamask/eth-phishing-detect">Ethereum Phishing Detector</a>. This includes outright malicious websites and legitimate websites that have been compromised by a malicious actor.</p>
|
||||
<p>You can turn MetaMask off to interact with this site, but it is advised not to.</p>
|
||||
<p>If you think this domain is incorrectly flagged or if a blocked legitimate website has resolved its security issues, <a href="https://github.com/metamask/eth-phishing-detect/issues/new">please file an issue</a>.</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -54,6 +54,11 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
// also remap ids inbound and outbound
|
||||
MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) {
|
||||
const self = this
|
||||
|
||||
if (payload.method === 'eth_signTypedData') {
|
||||
console.warn('MetaMask: This experimental version of eth_signTypedData will be deprecated in the next release in favor of the standard as defined in EIP-712. See https://git.io/fNzPl for more information on the new standard.')
|
||||
}
|
||||
|
||||
self.rpcEngine.handle(payload, cb)
|
||||
}
|
||||
|
||||
|
@ -106,6 +106,7 @@ class ConfirmSeedScreen extends Component {
|
||||
key={i}
|
||||
className={classnames('backup-phrase__confirm-seed-option', {
|
||||
'backup-phrase__confirm-seed-option--selected': isSelected,
|
||||
'backup-phrase__confirm-seed-option--unselected': !isSelected,
|
||||
})}
|
||||
onClick={() => {
|
||||
if (!isSelected) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {validateMnemonic} from 'bip39'
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {connect} from 'react-redux'
|
||||
@ -39,8 +40,12 @@ class ImportSeedPhraseScreen extends Component {
|
||||
handleSeedPhraseChange (seedPhrase) {
|
||||
let seedPhraseError = null
|
||||
|
||||
if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
|
||||
seedPhraseError = this.context.t('seedPhraseReq')
|
||||
if (seedPhrase) {
|
||||
if (this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
|
||||
seedPhraseError = this.context.t('seedPhraseReq')
|
||||
} else if (!validateMnemonic(seedPhrase)) {
|
||||
seedPhraseError = this.context.t('invalidSeedPhrase')
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ seedPhrase, seedPhraseError })
|
||||
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
|
||||
import {connect} from 'react-redux'
|
||||
import { withRouter, Switch, Route } from 'react-router-dom'
|
||||
import { compose } from 'recompose'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import CreatePasswordScreen from './create-password-screen'
|
||||
import UniqueImageScreen from './unique-image-screen'
|
||||
@ -44,28 +43,9 @@ class FirstTimeFlow extends Component {
|
||||
noActiveNotices: false,
|
||||
};
|
||||
|
||||
renderAppBar () {
|
||||
const { welcomeScreenSeen } = this.props
|
||||
|
||||
return (
|
||||
<div className="alpha-warning__container">
|
||||
<h2 className={classnames({
|
||||
'alpha-warning': welcomeScreenSeen,
|
||||
'alpha-warning-welcome-screen': !welcomeScreenSeen,
|
||||
})}
|
||||
>
|
||||
Please be aware that this version is still under development
|
||||
</h2>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { isPopup } = this.props
|
||||
|
||||
return (
|
||||
<div className="flex-column flex-grow">
|
||||
{ !isPopup && this.renderAppBar() }
|
||||
<div className="first-time-flow">
|
||||
<Switch>
|
||||
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
|
||||
|
86
old-ui/app/account-qr.js
Normal file
86
old-ui/app/account-qr.js
Normal file
@ -0,0 +1,86 @@
|
||||
const PropTypes = require('prop-types')
|
||||
const {PureComponent} = require('react')
|
||||
const h = require('react-hyperscript')
|
||||
const {qrcode: qrCode} = require('qrcode-npm')
|
||||
const {connect} = require('react-redux')
|
||||
const {isHexPrefixed} = require('ethereumjs-util')
|
||||
const actions = require('../../ui/app/actions')
|
||||
const CopyButton = require('./components/copyButton')
|
||||
|
||||
class AccountQrScreen extends PureComponent {
|
||||
static defaultProps = {
|
||||
warning: null,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
buyView: PropTypes.any.isRequired,
|
||||
Qr: PropTypes.object.isRequired,
|
||||
selectedAddress: PropTypes.string.isRequired,
|
||||
warning: PropTypes.node,
|
||||
}
|
||||
|
||||
render () {
|
||||
const {dispatch, Qr, selectedAddress, warning} = this.props
|
||||
const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}`
|
||||
const qrImage = qrCode(4, 'M')
|
||||
|
||||
qrImage.addData(address)
|
||||
qrImage.make()
|
||||
|
||||
return h('div.flex-column.full-width', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
padding: '50px',
|
||||
},
|
||||
}, [
|
||||
h('div.flex-row.full-width', {
|
||||
style: {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick () {
|
||||
dispatch(actions.backToAccountDetail(selectedAddress))
|
||||
},
|
||||
}),
|
||||
]),
|
||||
h('div.qr-header', Qr.message),
|
||||
warning && h('span.error.flex-center', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
width: '229px',
|
||||
height: '82px',
|
||||
},
|
||||
}, [
|
||||
this.props.warning,
|
||||
]),
|
||||
h('div#qr-container.flex-column', {
|
||||
style: {
|
||||
marginTop: '25px',
|
||||
marginBottom: '15px',
|
||||
},
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: qrImage.createTableTag(4),
|
||||
},
|
||||
}),
|
||||
h('div.flex-row.full-width', [
|
||||
h('h3.ellip-address.grow-tenx', Qr.data),
|
||||
h(CopyButton, {
|
||||
value: Qr.data,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
Qr: state.appState.Qr,
|
||||
buyView: state.appState.buyView,
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountQrScreen)
|
@ -14,6 +14,7 @@ const NewKeyChainScreen = require('./new-keychain')
|
||||
const UnlockScreen = require('./unlock')
|
||||
// accounts
|
||||
const AccountDetailScreen = require('./account-detail')
|
||||
const AccountQrScreen = require('./account-qr')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
// notice
|
||||
@ -24,17 +25,13 @@ const ConfigScreen = require('./config')
|
||||
const AddTokenScreen = require('./add-token')
|
||||
const Import = require('./accounts/import')
|
||||
const InfoScreen = require('./info')
|
||||
const NewUiAnnouncement = require('./new-ui-annoucement')
|
||||
const AppBar = require('./components/app-bar')
|
||||
const Loading = require('./components/loading')
|
||||
const SandwichExpando = require('sandwich-expando')
|
||||
const Dropdown = require('./components/dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const BuyView = require('./components/buy-button-subview')
|
||||
const QrView = require('./components/qr-code')
|
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
|
||||
@ -86,13 +83,29 @@ function mapStateToProps (state) {
|
||||
}
|
||||
|
||||
App.prototype.render = function () {
|
||||
var props = this.props
|
||||
const { isLoading, loadingMessage, transForward, network } = props
|
||||
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
|
||||
const loadMessage = loadingMessage || isLoadingNetwork ?
|
||||
`Connecting to ${this.getNetworkName()}` : null
|
||||
const {
|
||||
currentView,
|
||||
dispatch,
|
||||
isLoading,
|
||||
loadingMessage,
|
||||
transForward,
|
||||
network,
|
||||
featureFlags,
|
||||
} = this.props
|
||||
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
|
||||
const loadMessage = loadingMessage || isLoadingNetwork
|
||||
? `Connecting to ${this.getNetworkName()}`
|
||||
: null
|
||||
log.debug('Main ui render function')
|
||||
|
||||
if (!featureFlags.skipAnnounceBetaUI) {
|
||||
return (
|
||||
h(NewUiAnnouncement, {
|
||||
dispatch,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
h('.flex-column.full-height', {
|
||||
style: {
|
||||
@ -102,12 +115,9 @@ App.prototype.render = function () {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// app bar
|
||||
this.renderAppBar(),
|
||||
this.renderNetworkDropdown(),
|
||||
this.renderDropdown(),
|
||||
|
||||
h(AppBar, {
|
||||
...this.props,
|
||||
}),
|
||||
this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
|
||||
|
||||
// panel content
|
||||
@ -121,299 +131,6 @@ App.prototype.render = function () {
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderAppBar = function () {
|
||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
||||
return null
|
||||
}
|
||||
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
|
||||
const {isMascara, isOnboarding} = props
|
||||
|
||||
// Do not render header if user is in mascara onboarding
|
||||
if (isMascara && isOnboarding) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Do not render header if user is in mascara buy ether
|
||||
if (isMascara && props.currentView.name === 'buyEth') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
h('.full-width', {
|
||||
height: '38px',
|
||||
}, [
|
||||
|
||||
h('.app-header.flex-row.flex-space-between', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
visibility: props.isUnlocked ? 'visible' : 'none',
|
||||
background: props.isUnlocked ? 'white' : 'none',
|
||||
height: '38px',
|
||||
position: 'relative',
|
||||
zIndex: 12,
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div.left-menu-section', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// mini logo
|
||||
h('img', {
|
||||
height: 24,
|
||||
width: 24,
|
||||
src: './images/icon-128.png',
|
||||
}),
|
||||
|
||||
h(NetworkIndicator, {
|
||||
network: this.props.network,
|
||||
provider: this.props.provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
|
||||
},
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
props.isUnlocked && h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
props.isUnlocked && h(AccountDropdowns, {
|
||||
style: {},
|
||||
enableAccountsSelector: true,
|
||||
identities: this.props.identities,
|
||||
selected: this.props.selectedAddress,
|
||||
network: this.props.network,
|
||||
keyrings: this.props.keyrings,
|
||||
}, []),
|
||||
|
||||
// hamburger
|
||||
props.isUnlocked && h(SandwichExpando, {
|
||||
className: 'sandwich-expando',
|
||||
width: 16,
|
||||
barHeight: 2,
|
||||
padding: 0,
|
||||
isOpen: state.isMainMenuOpen,
|
||||
color: 'rgb(247,146,30)',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
isMainMenuOpen: !state.isMainMenuOpen,
|
||||
})
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderNetworkDropdown = function () {
|
||||
const props = this.props
|
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
||||
const rpcList = props.frequentRpcList
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isNetworkMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = [
|
||||
classList.contains('menu-icon'),
|
||||
classList.contains('network-name'),
|
||||
classList.contains('network-indicator'),
|
||||
].filter(bool => bool).length === 0
|
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) {
|
||||
this.setState({ isNetworkMenuOpen: false })
|
||||
}
|
||||
},
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '2px',
|
||||
top: '36px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '2px 16px 2px 0px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'main',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.diamond'),
|
||||
'Main Ethereum Network',
|
||||
providerType === 'mainnet' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.red-dot'),
|
||||
'Ropsten Test Network',
|
||||
providerType === 'ropsten' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
'Kovan Test Network',
|
||||
providerType === 'kovan' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.golden-square'),
|
||||
'Rinkeby Test Network',
|
||||
providerType === 'rinkeby' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'default',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('localhost')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Localhost 8545',
|
||||
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => this.props.dispatch(actions.showConfigPage()),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Custom RPC',
|
||||
activeNetwork === 'custom' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderDropdown = function () {
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isMainMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen: isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside: (event) => {
|
||||
const classList = event.target.classList
|
||||
const parentClassList = event.target.parentElement.classList
|
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') ||
|
||||
parentClassList.contains('sandwich-expando')
|
||||
|
||||
if (isOpen && !isToggleElement) {
|
||||
this.setState({ isMainMenuOpen: false })
|
||||
}
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
top: '38px',
|
||||
},
|
||||
innerStyle: {},
|
||||
}, [
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showConfigPage()) },
|
||||
}, 'Settings'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
|
||||
}, 'Log Out'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showInfoPage()) },
|
||||
}, 'Info/Help'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||
},
|
||||
}, 'Try Beta!'),
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
|
||||
const { isMascara } = this.props
|
||||
|
||||
@ -425,25 +142,6 @@ App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork,
|
||||
})
|
||||
}
|
||||
|
||||
App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||
var props = this.props
|
||||
return (
|
||||
h('.flex-row', {
|
||||
key: 'leftArrow',
|
||||
style: style,
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.cursor-pointer'),
|
||||
justArrow ? null : h('div.cursor-pointer', {
|
||||
style: {
|
||||
marginLeft: '3px',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, 'BACK'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderPrimary = function () {
|
||||
log.debug('rendering primary')
|
||||
var props = this.props
|
||||
@ -465,22 +163,6 @@ App.prototype.renderPrimary = function () {
|
||||
key: 'NoticeScreen',
|
||||
onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
|
||||
}),
|
||||
|
||||
!props.isInitialized && h('.flex-row.flex-center.flex-grow', [
|
||||
h('p.pointer', {
|
||||
onClick: () => {
|
||||
global.platform.openExtensionInBrowser()
|
||||
props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||
},
|
||||
style: {
|
||||
fontSize: '0.8em',
|
||||
color: '#aeaeae',
|
||||
textDecoration: 'underline',
|
||||
marginTop: '32px',
|
||||
},
|
||||
}, 'Try Beta Version'),
|
||||
]),
|
||||
|
||||
])
|
||||
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||
log.debug('rendering notice screen for lost accounts view.')
|
||||
@ -580,31 +262,10 @@ App.prototype.renderPrimary = function () {
|
||||
|
||||
case 'qr':
|
||||
log.debug('rendering show qr screen')
|
||||
return h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick: () => props.dispatch(actions.backToAccountDetail(props.selectedAddress)),
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
marginTop: '50px',
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '44px',
|
||||
width: '285px',
|
||||
},
|
||||
}, [
|
||||
h(QrView, {key: 'qr'}),
|
||||
]),
|
||||
])
|
||||
return h(AccountQrScreen, {
|
||||
key: 'account-qr',
|
||||
selectedAddress: props.selectedAddress,
|
||||
})
|
||||
|
||||
default:
|
||||
log.debug('rendering default, account detail screen')
|
||||
@ -623,41 +284,6 @@ App.prototype.toggleMetamaskActive = function () {
|
||||
this.props.dispatch(actions.lockMetamask(false))
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
const props = this.props
|
||||
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget
|
||||
if (rpcTarget.length > 31) {
|
||||
label = label.substr(0, 34) + '...'
|
||||
}
|
||||
|
||||
switch (rpcTarget) {
|
||||
|
||||
case 'http://localhost:8545':
|
||||
return null
|
||||
|
||||
default:
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: rpcTarget,
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
label,
|
||||
h('.check', '✓'),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.getNetworkName = function () {
|
||||
const { provider } = this.props
|
||||
const providerName = provider.type
|
||||
@ -678,28 +304,3 @@ App.prototype.getNetworkName = function () {
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
App.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
const props = this.props
|
||||
const rpcTarget = provider.rpcTarget
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
rpcTarget === rpc ? h('.check', '✓') : null,
|
||||
]
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
432
old-ui/app/components/app-bar.js
Normal file
432
old-ui/app/components/app-bar.js
Normal file
@ -0,0 +1,432 @@
|
||||
const PropTypes = require('prop-types')
|
||||
const {Component} = require('react')
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const SandwichExpando = require('sandwich-expando')
|
||||
const {Dropdown} = require('./dropdown')
|
||||
const {DropdownMenuItem} = require('./dropdown')
|
||||
const NetworkIndicator = require('./network')
|
||||
const {AccountDropdowns} = require('./account-dropdowns')
|
||||
|
||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
||||
|
||||
module.exports = class AppBar extends Component {
|
||||
static defaultProps = {
|
||||
selectedAddress: undefined,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
frequentRpcList: PropTypes.array.isRequired,
|
||||
isMascara: PropTypes.bool.isRequired,
|
||||
isOnboarding: PropTypes.bool.isRequired,
|
||||
identities: PropTypes.any.isRequired,
|
||||
selectedAddress: PropTypes.string,
|
||||
isUnlocked: PropTypes.bool.isRequired,
|
||||
network: PropTypes.any.isRequired,
|
||||
keyrings: PropTypes.any.isRequired,
|
||||
provider: PropTypes.any.isRequired,
|
||||
}
|
||||
|
||||
static renderSpace () {
|
||||
return (
|
||||
h('span', {
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: ' ',
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
state = {
|
||||
isNetworkMenuOpen: false,
|
||||
}
|
||||
|
||||
renderAppBar () {
|
||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
||||
return null
|
||||
}
|
||||
|
||||
const props = this.props
|
||||
const {isMascara, isOnboarding} = props
|
||||
|
||||
// Do not render header if user is in mascara onboarding
|
||||
if (isMascara && isOnboarding) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Do not render header if user is in mascara buy ether
|
||||
if (isMascara && props.currentView.name === 'buyEth') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
h('div.app-bar', [
|
||||
this.renderAppBarNewUiNotice(),
|
||||
this.renderAppBarAppHeader(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderAppBarNewUiNotice () {
|
||||
const {dispatch} = this.props
|
||||
|
||||
return (
|
||||
h('div.app-bar__new-ui-banner', {
|
||||
style: {
|
||||
height: '28px',
|
||||
zIndex: 12,
|
||||
},
|
||||
}, [
|
||||
'Try the New MetaMask',
|
||||
AppBar.renderSpace(),
|
||||
h('span.banner__link', {
|
||||
async onClick () {
|
||||
await dispatch(actions.setFeatureFlag('betaUI', true))
|
||||
global.platform.openExtensionInBrowser()
|
||||
},
|
||||
}, [
|
||||
'Now',
|
||||
]),
|
||||
AppBar.renderSpace(),
|
||||
'or',
|
||||
AppBar.renderSpace(),
|
||||
h('span.banner__link', {
|
||||
onClick () {
|
||||
global.platform.openWindow({
|
||||
url: 'https://medium.com/metamask/74dba32cc7f7',
|
||||
})
|
||||
},
|
||||
}, [
|
||||
'Learn More',
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderAppBarAppHeader () {
|
||||
const {
|
||||
identities,
|
||||
selectedAddress,
|
||||
isUnlocked,
|
||||
network,
|
||||
keyrings,
|
||||
provider,
|
||||
} = this.props
|
||||
const {
|
||||
isNetworkMenuOpen,
|
||||
isMainMenuOpen,
|
||||
} = this.state
|
||||
|
||||
return (
|
||||
h('.full-width', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '38px',
|
||||
},
|
||||
}, [
|
||||
h('.app-header.flex-row.flex-space-between', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
visibility: isUnlocked ? 'visible' : 'none',
|
||||
background: isUnlocked ? 'white' : 'none',
|
||||
height: '38px',
|
||||
position: 'relative',
|
||||
zIndex: 12,
|
||||
},
|
||||
}, [
|
||||
h('div.left-menu-section', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
// mini logo
|
||||
h('img', {
|
||||
height: 24,
|
||||
width: 24,
|
||||
src: './images/icon-128.png',
|
||||
}),
|
||||
h(NetworkIndicator, {
|
||||
network: network,
|
||||
provider: provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
isUnlocked && h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h(AccountDropdowns, {
|
||||
style: {},
|
||||
enableAccountsSelector: true,
|
||||
identities: identities,
|
||||
selected: selectedAddress,
|
||||
network,
|
||||
keyrings,
|
||||
}, []),
|
||||
h(SandwichExpando, {
|
||||
className: 'sandwich-expando',
|
||||
width: 16,
|
||||
barHeight: 2,
|
||||
padding: 0,
|
||||
isOpen: isMainMenuOpen,
|
||||
color: 'rgb(247,146,30)',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
isMainMenuOpen: !isMainMenuOpen,
|
||||
})
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderNetworkDropdown () {
|
||||
const {
|
||||
dispatch,
|
||||
frequentRpcList: rpcList,
|
||||
provider,
|
||||
} = this.props
|
||||
const {
|
||||
type: providerType,
|
||||
rpcTarget: activeNetwork,
|
||||
} = provider
|
||||
const isOpen = this.state.isNetworkMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = [
|
||||
classList.contains('menu-icon'),
|
||||
classList.contains('network-name'),
|
||||
classList.contains('network-indicator'),
|
||||
].filter(bool => bool).length === 0
|
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) {
|
||||
this.setState({ isNetworkMenuOpen: false })
|
||||
}
|
||||
},
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '2px',
|
||||
top: '64px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '2px 16px 2px 0px',
|
||||
},
|
||||
}, [
|
||||
h(DropdownMenuItem, {
|
||||
key: 'main',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => dispatch(actions.setProviderType('mainnet')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
}, [
|
||||
h('.menu-icon.diamond'),
|
||||
'Main Ethereum Network',
|
||||
providerType === 'mainnet'
|
||||
? h('.check', '✓')
|
||||
: null,
|
||||
]),
|
||||
h(DropdownMenuItem, {
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => dispatch(actions.setProviderType('ropsten')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
}, [
|
||||
h('.menu-icon.red-dot'),
|
||||
'Ropsten Test Network',
|
||||
providerType === 'ropsten'
|
||||
? h('.check', '✓')
|
||||
: null,
|
||||
]),
|
||||
h(DropdownMenuItem, {
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => dispatch(actions.setProviderType('kovan')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
}, [
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
'Kovan Test Network',
|
||||
providerType === 'kovan'
|
||||
? h('.check', '✓')
|
||||
: null,
|
||||
]),
|
||||
h(DropdownMenuItem, {
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => dispatch(actions.setProviderType('rinkeby')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
}, [
|
||||
h('.menu-icon.golden-square'),
|
||||
'Rinkeby Test Network',
|
||||
providerType === 'rinkeby'
|
||||
? h('.check', '✓')
|
||||
: null,
|
||||
]),
|
||||
h(DropdownMenuItem, {
|
||||
key: 'default',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => dispatch(actions.setProviderType('localhost')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Localhost 8545',
|
||||
activeNetwork === LOCALHOST_RPC_URL
|
||||
? h('.check', '✓')
|
||||
: null,
|
||||
]),
|
||||
|
||||
this.renderCustomOption(provider),
|
||||
this.renderCommonRpc(rpcList, provider),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => dispatch(actions.showConfigPage()),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Custom RPC',
|
||||
activeNetwork === 'custom'
|
||||
? h('.check', '✓')
|
||||
: null,
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
renderCustomOption ({ rpcTarget, type }) {
|
||||
const {dispatch} = this.props
|
||||
|
||||
if (type !== 'rpc') {
|
||||
return null
|
||||
}
|
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget
|
||||
if (rpcTarget.length > 31) {
|
||||
label = label.substr(0, 34) + '...'
|
||||
}
|
||||
|
||||
switch (rpcTarget) {
|
||||
case LOCALHOST_RPC_URL:
|
||||
return null
|
||||
default:
|
||||
return h(DropdownMenuItem, {
|
||||
key: rpcTarget,
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
label,
|
||||
h('.check', '✓'),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
renderCommonRpc (rpcList, {rpcTarget}) {
|
||||
const {dispatch} = this.props
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === LOCALHOST_RPC_URL) || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(DropdownMenuItem, {
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpc)),
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
rpcTarget === rpc
|
||||
? h('.check', '✓')
|
||||
: null,
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
renderDropdown () {
|
||||
const {dispatch} = this.props
|
||||
const isOpen = this.state.isMainMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen: isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside: (event) => {
|
||||
const classList = event.target.classList
|
||||
const parentClassList = event.target.parentElement.classList
|
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') ||
|
||||
parentClassList.contains('sandwich-expando')
|
||||
|
||||
if (isOpen && !isToggleElement) {
|
||||
this.setState({ isMainMenuOpen: false })
|
||||
}
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
top: '66px',
|
||||
},
|
||||
innerStyle: {},
|
||||
}, [
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { dispatch(actions.showConfigPage()) },
|
||||
}, 'Settings'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { dispatch(actions.lockMetamask()) },
|
||||
}, 'Log Out'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { dispatch(actions.showInfoPage()) },
|
||||
}, 'Info/Help'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => {
|
||||
dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||
},
|
||||
}, 'Try Beta!'),
|
||||
])
|
||||
}
|
||||
|
||||
render () {
|
||||
return h('div.full-width', [
|
||||
this.renderAppBar(),
|
||||
this.renderNetworkDropdown(),
|
||||
this.renderDropdown(),
|
||||
])
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const qrCode = require('qrcode-npm').qrcode
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const isHexPrefixed = require('ethereumjs-util').isHexPrefixed
|
||||
const CopyButton = require('./copyButton')
|
||||
|
||||
module.exports = connect(mapStateToProps)(QrCodeView)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
Qr: state.appState.Qr,
|
||||
buyView: state.appState.buyView,
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(QrCodeView, Component)
|
||||
|
||||
function QrCodeView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
QrCodeView.prototype.render = function () {
|
||||
const props = this.props
|
||||
const Qr = props.Qr
|
||||
const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}`
|
||||
const qrImage = qrCode(4, 'M')
|
||||
qrImage.addData(address)
|
||||
qrImage.make()
|
||||
return h('.main-container.flex-column', {
|
||||
key: 'qr',
|
||||
style: {
|
||||
justifyContent: 'center',
|
||||
paddingBottom: '45px',
|
||||
paddingLeft: '45px',
|
||||
paddingRight: '45px',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message),
|
||||
|
||||
this.props.warning ? this.props.warning && h('span.error.flex-center', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
width: '229px',
|
||||
height: '82px',
|
||||
},
|
||||
},
|
||||
this.props.warning) : null,
|
||||
|
||||
h('#qr-container.flex-column', {
|
||||
style: {
|
||||
marginTop: '25px',
|
||||
marginBottom: '15px',
|
||||
},
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: qrImage.createTableTag(4),
|
||||
},
|
||||
}),
|
||||
h('.flex-row', [
|
||||
h('h3.ellip-address', {
|
||||
style: {
|
||||
width: '247px',
|
||||
},
|
||||
}, Qr.data),
|
||||
h(CopyButton, {
|
||||
value: Qr.data,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
QrCodeView.prototype.renderMultiMessage = function () {
|
||||
var Qr = this.props.Qr
|
||||
var multiMessage = Qr.message.map((message) => h('.qr-message', message))
|
||||
return multiMessage
|
||||
}
|
@ -3,7 +3,6 @@ const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const Qr = require('./qr-code')
|
||||
const isValidAddress = require('../util').isValidAddress
|
||||
module.exports = connect(mapStateToProps)(ShapeshiftForm)
|
||||
|
||||
@ -11,7 +10,6 @@ function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
isSubLoading: state.appState.isSubLoading,
|
||||
qrRequested: state.appState.qrRequested,
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +21,7 @@ function ShapeshiftForm () {
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.render = function () {
|
||||
return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain()
|
||||
return this.renderMain()
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.renderMain = function () {
|
||||
|
@ -36,7 +36,7 @@ TransactionListItem.prototype.showRetryButton = function () {
|
||||
return false
|
||||
}
|
||||
|
||||
let currentTxIsLatest = false
|
||||
let currentTxSharesEarliestNonce = false
|
||||
const currentNonce = txParams.nonce
|
||||
const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce)
|
||||
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
|
||||
@ -45,14 +45,14 @@ TransactionListItem.prototype.showRetryButton = function () {
|
||||
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce &&
|
||||
lastSubmittedTxWithCurrentNonce.id === transaction.id
|
||||
if (currentSubmittedTxs.length > 0) {
|
||||
const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => {
|
||||
const earliestSubmitted = currentSubmittedTxs.reduce((tx1, tx2) => {
|
||||
if (tx1.submittedTime < tx2.submittedTime) return tx1
|
||||
return tx2
|
||||
})
|
||||
currentTxIsLatest = lastTx.id === transaction.id
|
||||
currentTxSharesEarliestNonce = currentNonce === earliestSubmitted.txParams.nonce
|
||||
}
|
||||
|
||||
return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 && currentTxIsLatest
|
||||
return currentTxSharesEarliestNonce && currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000
|
||||
}
|
||||
|
||||
TransactionListItem.prototype.render = function () {
|
||||
|
@ -720,7 +720,131 @@ div.message-container > div:first-child {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
//Notification Modal
|
||||
.new-ui-announcement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: white;
|
||||
color: #4D4D4D;
|
||||
font-family: Roboto, Arial, sans-serif;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.new-ui-announcement__announcement-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.new-ui-announcement__announcement-header a.close {
|
||||
cursor: pointer;
|
||||
font-size: 32px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.new-ui-announcement__announcement-header a.close:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.new-ui-announcement__announcement-header h1 {
|
||||
color: #33A4E7;
|
||||
text-transform: uppercase;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.new-ui-announcement__body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
font-size: 10.5pt;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.new-ui-announcement__body h1 {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.new-ui-announcement__body a {
|
||||
color: #33A4E7;
|
||||
}
|
||||
|
||||
.new-ui-announcement__body .updates-list {
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.new-ui-announcement__body .updates-list h2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.new-ui-announcement__body .updates-list ul {
|
||||
list-style: disc inside;
|
||||
}
|
||||
|
||||
.new-ui-announcement__footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.new-ui-announcement__footer h1 {
|
||||
font-family: inherit;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.new-ui-announcement__footer button:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.new-ui-announcement__footer button.positive {
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
background: #33A4E7;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
box-shadow: none;
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.new-ui-announcement__footer button.negative {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
color: #33A4E7;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.app-bar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app-bar__new-ui-banner {
|
||||
background: #33A4E7;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
padding: 8px;
|
||||
font-family: Roboto, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner__link {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.notification-modal-wrapper {
|
||||
display: flex;
|
||||
@ -812,4 +936,4 @@ div.message-container > div:first-child {
|
||||
|
||||
.notification-modal__link {
|
||||
color: #2f9ae0;
|
||||
}
|
||||
}
|
||||
|
85
old-ui/app/new-ui-annoucement.js
Normal file
85
old-ui/app/new-ui-annoucement.js
Normal file
@ -0,0 +1,85 @@
|
||||
const PropTypes = require('prop-types')
|
||||
const {PureComponent} = require('react')
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../ui/app/actions')
|
||||
|
||||
module.exports = class NewUiAnnouncement extends PureComponent {
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
close = async () => {
|
||||
await this.props.dispatch(actions.setFeatureFlag('skipAnnounceBetaUI', true))
|
||||
}
|
||||
|
||||
switchToNewUi = async () => {
|
||||
const flag = 'betaUI'
|
||||
const enabled = true
|
||||
await this.props.dispatch(actions.setFeatureFlag(
|
||||
flag,
|
||||
enabled,
|
||||
))
|
||||
await this.close()
|
||||
global.platform.openExtensionInBrowser()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
h('div.new-ui-announcement', [
|
||||
h('section.new-ui-announcement__announcement-header', [
|
||||
h('h1', 'Announcement'),
|
||||
h('a.close', {
|
||||
onClick: this.close,
|
||||
}, '×'),
|
||||
]),
|
||||
h('section.new-ui-announcement__body', [
|
||||
h('h1', 'A New Version of MetaMask'),
|
||||
h('p', [
|
||||
"We're excited to announce a brand-new version of MetaMask with enhanced features and functionality.",
|
||||
]),
|
||||
h('div.updates-list', [
|
||||
h('h2', 'Updates include'),
|
||||
h('ul', [
|
||||
h('li', 'New user interface'),
|
||||
h('li', 'Full-screen mode'),
|
||||
h('li', 'Better token support'),
|
||||
h('li', 'Better gas controls'),
|
||||
h('li', 'Advanced features for developers'),
|
||||
h('li', 'New confirmation screens'),
|
||||
h('li', 'And more!'),
|
||||
]),
|
||||
]),
|
||||
h('p', [
|
||||
'You can still use the current version of MetaMask. The new version is still in beta, ' +
|
||||
'however we encourage you to try it out as we transition into this exciting new update.',
|
||||
h('span', {
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: ' ',
|
||||
},
|
||||
}),
|
||||
h('a', {
|
||||
href: 'https://medium.com/metamask/74dba32cc7f7',
|
||||
onClick ({target}) {
|
||||
const url = target.href
|
||||
global.platform.openWindow({
|
||||
url,
|
||||
})
|
||||
},
|
||||
}, [
|
||||
'Learn more.',
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
h('section.new-ui-announcement__footer', [
|
||||
h('h1', 'Ready to try the new MetaMask?'),
|
||||
h('button.positive', {
|
||||
onClick: this.switchToNewUi,
|
||||
}, 'Try it now'),
|
||||
h('button.negative', {
|
||||
onClick: this.close,
|
||||
}, 'No thanks, maybe later'),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
684
package-lock.json
generated
684
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -198,7 +198,6 @@
|
||||
"semaphore": "^1.0.5",
|
||||
"semver": "^5.4.1",
|
||||
"shallow-copy": "0.0.1",
|
||||
"superstatic": "^5.0.2",
|
||||
"sw-controller": "^1.0.3",
|
||||
"sw-stream": "^2.0.2",
|
||||
"textarea-caret": "^3.0.1",
|
||||
@ -228,7 +227,7 @@
|
||||
"brfs": "^1.6.1",
|
||||
"browserify": "^16.1.1",
|
||||
"chai": "^4.1.0",
|
||||
"chromedriver": "2.36.0",
|
||||
"chromedriver": "^2.41.0",
|
||||
"clipboardy": "^1.2.3",
|
||||
"compression": "^1.7.1",
|
||||
"coveralls": "^3.0.0",
|
||||
@ -308,6 +307,7 @@
|
||||
"shell-parallel": "^1.0.3",
|
||||
"sinon": "^5.0.0",
|
||||
"source-map": "^0.7.2",
|
||||
"static-server": "^2.2.1",
|
||||
"style-loader": "^0.21.0",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"tape": "^4.5.1",
|
||||
|
@ -50,15 +50,20 @@ deployButton.addEventListener('click', async function (event) {
|
||||
|
||||
console.log(`contract`, contract)
|
||||
|
||||
document.getElementById('contractStatus').innerHTML = 'Deployed'
|
||||
|
||||
depositButton.addEventListener('click', function (event) {
|
||||
document.getElementById('contractStatus').innerHTML = 'Deposit initiated'
|
||||
contract.deposit({ from: web3.eth.accounts[0], value: '0x3782dace9d900000' }, function (result) {
|
||||
console.log(result)
|
||||
document.getElementById('contractStatus').innerHTML = 'Deposit completed'
|
||||
})
|
||||
})
|
||||
|
||||
withdrawButton.addEventListener('click', function (event) {
|
||||
contract.withdraw('0xde0b6b3a7640000', { from: web3.eth.accounts[0] }, function (result) {
|
||||
console.log(result)
|
||||
document.getElementById('contractStatus').innerHTML = 'Withdrawn'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -10,6 +10,9 @@
|
||||
<button id="depositButton">Deposit</button>
|
||||
<button id="withdrawButton">Withdraw</button>
|
||||
</div>
|
||||
<div id="contractStatus" style="display: flex; font-size: 1rem;">
|
||||
Not yet deployed
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-flow: column;">
|
||||
<div style="display: flex; font-size: 1.25rem;">Send eth</div>
|
||||
|
@ -12,9 +12,11 @@ const {
|
||||
} = require('../func')
|
||||
const {
|
||||
checkBrowserForConsoleErrors,
|
||||
closeAllWindowHandlesExcept,
|
||||
verboseReportOnFailure,
|
||||
findElement,
|
||||
findElements,
|
||||
loadExtension,
|
||||
} = require('./helpers')
|
||||
|
||||
|
||||
@ -25,6 +27,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||
const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC'
|
||||
const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6'
|
||||
const tinyDelayMs = 500
|
||||
const regularDelayMs = 1000
|
||||
const largeDelayMs = regularDelayMs * 2
|
||||
|
||||
@ -74,37 +77,51 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
describe('New UI setup', async function () {
|
||||
it('switches to first tab', async function () {
|
||||
await delay(tinyDelayMs)
|
||||
const [firstTab] = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(firstTab)
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('use the local network', async function () {
|
||||
const networkSelector = await findElement(driver, By.css('#network_component'))
|
||||
await networkSelector.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const [localhost] = await findElements(driver, By.xpath(`//li[contains(text(), 'Localhost')]`))
|
||||
await localhost.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('selects the new UI option', async () => {
|
||||
const button = await findElement(driver, By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
||||
try {
|
||||
const overlay = await findElement(driver, By.css('.full-flex-height'))
|
||||
await driver.wait(until.stalenessOf(overlay))
|
||||
} catch (e) {}
|
||||
|
||||
const button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
await button.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
// Close all other tabs
|
||||
const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles()
|
||||
const [tab0, tab1, tab2] = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(tab0)
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const newUiOrInfoPage = newUi || infoPage
|
||||
await driver.switchTo().window(oldUi)
|
||||
await driver.close()
|
||||
if (infoPage !== newUiOrInfoPage) {
|
||||
await driver.switchTo().window(infoPage)
|
||||
await driver.close()
|
||||
let selectedUrl = await driver.getCurrentUrl()
|
||||
await delay(tinyDelayMs)
|
||||
if (tab0 && selectedUrl.match(/popup.html/)) {
|
||||
await closeAllWindowHandlesExcept(driver, tab0)
|
||||
} else if (tab1) {
|
||||
await driver.switchTo().window(tab1)
|
||||
selectedUrl = await driver.getCurrentUrl()
|
||||
await delay(tinyDelayMs)
|
||||
if (selectedUrl.match(/popup.html/)) {
|
||||
await closeAllWindowHandlesExcept(driver, tab1)
|
||||
} else if (tab2) {
|
||||
await driver.switchTo().window(tab2)
|
||||
selectedUrl = await driver.getCurrentUrl()
|
||||
selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2)
|
||||
}
|
||||
} else {
|
||||
throw new Error('popup.html not found')
|
||||
}
|
||||
await driver.switchTo().window(newUiOrInfoPage)
|
||||
await delay(regularDelayMs)
|
||||
const [appTab] = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(appTab)
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
await loadExtension(driver, extensionId)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
|
||||
@ -208,6 +225,16 @@ describe('Using MetaMask with an existing account', function () {
|
||||
})
|
||||
|
||||
describe('Add an account', () => {
|
||||
it('switches to localhost', async () => {
|
||||
const networkDropdown = await findElement(driver, By.css('.network-name'))
|
||||
await networkDropdown.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`))
|
||||
await localhost.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
|
||||
it('choose Create Account from the account menu', async () => {
|
||||
await driver.findElement(By.css('.account-menu__icon')).click()
|
||||
await delay(regularDelayMs)
|
||||
|
@ -75,30 +75,11 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
describe('New UI setup', async function () {
|
||||
let networkSelector
|
||||
it('switches to first tab', async function () {
|
||||
await delay(tinyDelayMs)
|
||||
const [firstTab] = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(firstTab)
|
||||
await delay(regularDelayMs)
|
||||
try {
|
||||
networkSelector = await findElement(driver, By.css('#network_component'))
|
||||
} catch (e) {
|
||||
await loadExtension(driver, extensionId)
|
||||
await delay(largeDelayMs * 2)
|
||||
networkSelector = await findElement(driver, By.css('#network_component'))
|
||||
}
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('uses the local network', async function () {
|
||||
await networkSelector.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const networks = await findElements(driver, By.css('.dropdown-menu-item'))
|
||||
const localhost = networks[4]
|
||||
await driver.wait(until.elementTextMatches(localhost, /Localhost/))
|
||||
await localhost.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('selects the new UI option', async () => {
|
||||
@ -107,27 +88,40 @@ describe('MetaMask', function () {
|
||||
await driver.wait(until.stalenessOf(overlay))
|
||||
} catch (e) {}
|
||||
|
||||
const button = await findElement(driver, By.xpath("//p[contains(text(), 'Try Beta Version')]"))
|
||||
const button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
await button.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
// Close all other tabs
|
||||
const [oldUi, tab1, tab2] = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(oldUi)
|
||||
await driver.close()
|
||||
const [tab0, tab1, tab2] = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(tab0)
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
await driver.switchTo().window(tab1)
|
||||
const tab1Url = await driver.getCurrentUrl()
|
||||
if (tab1Url.match(/metamask.io/)) {
|
||||
await driver.switchTo().window(tab1)
|
||||
await driver.close()
|
||||
await driver.switchTo().window(tab2)
|
||||
} else if (tab2) {
|
||||
await driver.switchTo().window(tab2)
|
||||
await driver.close()
|
||||
let selectedUrl = await driver.getCurrentUrl()
|
||||
await delay(tinyDelayMs)
|
||||
if (tab0 && selectedUrl.match(/popup.html/)) {
|
||||
await closeAllWindowHandlesExcept(driver, tab0)
|
||||
} else if (tab1) {
|
||||
await driver.switchTo().window(tab1)
|
||||
selectedUrl = await driver.getCurrentUrl()
|
||||
await delay(tinyDelayMs)
|
||||
if (selectedUrl.match(/popup.html/)) {
|
||||
await closeAllWindowHandlesExcept(driver, tab1)
|
||||
} else if (tab2) {
|
||||
await driver.switchTo().window(tab2)
|
||||
selectedUrl = await driver.getCurrentUrl()
|
||||
selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2)
|
||||
}
|
||||
} else {
|
||||
throw new Error('popup.html not found')
|
||||
}
|
||||
await delay(regularDelayMs)
|
||||
const [appTab] = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(appTab)
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
await loadExtension(driver, extensionId)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
|
||||
await continueBtn.click()
|
||||
@ -201,7 +195,16 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded) {
|
||||
async function clickWordAndWait (word) {
|
||||
const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected'
|
||||
const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]`
|
||||
const word0 = await findElement(driver, By.xpath(xpath), 10000)
|
||||
|
||||
await word0.click()
|
||||
await delay(tinyDelayMs)
|
||||
}
|
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
|
||||
try {
|
||||
if (wasReloaded) {
|
||||
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
|
||||
@ -215,67 +218,26 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
}
|
||||
|
||||
const word0 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[0]}')]`), 10000)
|
||||
await clickWordAndWait(words[0])
|
||||
await clickWordAndWait(words[1])
|
||||
await clickWordAndWait(words[2])
|
||||
await clickWordAndWait(words[3])
|
||||
await clickWordAndWait(words[4])
|
||||
await clickWordAndWait(words[5])
|
||||
await clickWordAndWait(words[6])
|
||||
await clickWordAndWait(words[7])
|
||||
await clickWordAndWait(words[8])
|
||||
await clickWordAndWait(words[9])
|
||||
await clickWordAndWait(words[10])
|
||||
await clickWordAndWait(words[11])
|
||||
|
||||
await word0.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word1 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[1]}')]`), 10000)
|
||||
|
||||
await word1.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word2 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[2]}')]`), 10000)
|
||||
|
||||
await word2.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word3 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[3]}')]`), 10000)
|
||||
|
||||
await word3.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word4 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[4]}')]`), 10000)
|
||||
|
||||
await word4.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word5 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[5]}')]`), 10000)
|
||||
|
||||
await word5.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word6 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[6]}')]`), 10000)
|
||||
|
||||
await word6.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word7 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[7]}')]`), 10000)
|
||||
|
||||
await word7.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word8 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[8]}')]`), 10000)
|
||||
|
||||
await word8.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word9 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[9]}')]`), 10000)
|
||||
|
||||
await word9.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word10 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[10]}')]`), 10000)
|
||||
|
||||
await word10.click()
|
||||
await delay(tinyDelayMs)
|
||||
|
||||
const word11 = await findElement(driver, By.xpath(`//button[contains(text(), '${words[11]}')]`), 10000)
|
||||
await word11.click()
|
||||
await delay(tinyDelayMs)
|
||||
} catch (e) {
|
||||
await loadExtension(driver, extensionId)
|
||||
await retypeSeedPhrase(words, true)
|
||||
if (count > 2) {
|
||||
throw e
|
||||
} else {
|
||||
await loadExtension(driver, extensionId)
|
||||
await retypeSeedPhrase(words, true, count + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,6 +351,16 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('switches to localhost', async () => {
|
||||
const networkDropdown = await findElement(driver, By.css('.network-name'))
|
||||
await networkDropdown.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`))
|
||||
await localhost.click()
|
||||
await delay(largeDelayMs * 2)
|
||||
})
|
||||
|
||||
it('balance renders', async () => {
|
||||
const balance = await findElement(driver, By.css('.balance-display .token-amount'))
|
||||
await driver.wait(until.elementTextMatches(balance, /100.+ETH/))
|
||||
@ -512,7 +484,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('displays the contract creation data', async () => {
|
||||
const dataTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Data')]`))
|
||||
dataTab.click()
|
||||
await dataTab.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
await findElement(driver, By.xpath(`//div[contains(text(), '127.0.0.1')]`))
|
||||
@ -522,7 +494,7 @@ describe('MetaMask', function () {
|
||||
assert.equal(confirmDataText.match(/0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff/))
|
||||
|
||||
const detailsTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Details')]`))
|
||||
detailsTab.click()
|
||||
await detailsTab.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
@ -543,9 +515,15 @@ describe('MetaMask', function () {
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
let contractStatus = await driver.findElement(By.css('#contractStatus'))
|
||||
await driver.wait(until.elementTextMatches(contractStatus, /Deployed/))
|
||||
|
||||
const depositButton = await findElement(driver, By.css('#depositButton'))
|
||||
await depositButton.click()
|
||||
await delay(regularDelayMs)
|
||||
await delay(largeDelayMs)
|
||||
|
||||
contractStatus = await driver.findElement(By.css('#contractStatus'))
|
||||
await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/))
|
||||
|
||||
await driver.switchTo().window(extension)
|
||||
await delay(largeDelayMs)
|
||||
|
@ -6,5 +6,5 @@ set -o pipefail
|
||||
|
||||
export PATH="$PATH:./node_modules/.bin"
|
||||
|
||||
shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && superstatic test/e2e/beta/contract-test/ --port 8080 --host 127.0.0.1' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec'
|
||||
shell-parallel -s 'npm run ganache:start -- -d' -x 'sleep 5 && superstatic test/e2e/beta/contract-test/ --port 8080 --host 127.0.0.1' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec'
|
||||
shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec'
|
||||
shell-parallel -s 'npm run ganache:start -- -d' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec'
|
||||
|
@ -59,6 +59,13 @@ describe('Metamask popup page', function () {
|
||||
await driver.switchTo().window(windowHandles[0])
|
||||
})
|
||||
|
||||
it('does not select the new UI option', async () => {
|
||||
await delay(300)
|
||||
const button = await driver.findElement(By.xpath("//button[contains(text(), 'No thanks, maybe later')]"))
|
||||
await button.click()
|
||||
await delay(1000)
|
||||
})
|
||||
|
||||
it('sets provider type to localhost', async function () {
|
||||
await delay(300)
|
||||
await setProviderType('localhost')
|
||||
@ -133,9 +140,9 @@ describe('Metamask popup page', function () {
|
||||
})
|
||||
|
||||
it('adds a second account', async function () {
|
||||
await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div')).click()
|
||||
await driver.findElement(By.css('div.full-width > div > div:nth-child(2) > span > div')).click()
|
||||
await delay(300)
|
||||
await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span')).click()
|
||||
await driver.findElement(By.css('div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span')).click()
|
||||
})
|
||||
|
||||
it('shows account address', async function () {
|
||||
@ -146,7 +153,7 @@ describe('Metamask popup page', function () {
|
||||
it('logs out of the vault', async () => {
|
||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||
await delay(500)
|
||||
const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||
const logoutButton = await driver.findElement(By.css('.menu-droppo > li:nth-child(3)'))
|
||||
assert.equal(await logoutButton.getText(), 'Log Out')
|
||||
await logoutButton.click()
|
||||
})
|
||||
@ -178,7 +185,7 @@ describe('Metamask popup page', function () {
|
||||
it('logs out', async function () {
|
||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||
await delay(200)
|
||||
const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||
const logOut = await driver.findElement(By.css('.menu-droppo > li:nth-child(3)'))
|
||||
assert.equal(await logOut.getText(), 'Log Out')
|
||||
await logOut.click()
|
||||
await delay(300)
|
||||
|
@ -27,6 +27,11 @@ async function runFirstTimeUsageTest(assert, done) {
|
||||
|
||||
const app = $('#app-content')
|
||||
|
||||
// Selects new ui
|
||||
const tryNewUIButton = (await findAsync(app, 'button.negative'))[0]
|
||||
tryNewUIButton.click()
|
||||
await timeout()
|
||||
|
||||
// recurse notices
|
||||
while (true) {
|
||||
const button = await findAsync(app, 'button')
|
||||
|
@ -15,14 +15,21 @@
|
||||
&__details {
|
||||
flex: 1;
|
||||
text-align: end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__fiat {
|
||||
font-size: 1.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__eth {
|
||||
color: $oslo-gray;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__header-text {
|
||||
|
@ -43,7 +43,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
// Footer
|
||||
onCancel: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
valid: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -54,7 +54,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
fromAddress,
|
||||
toName,
|
||||
toAddress,
|
||||
valid,
|
||||
disabled,
|
||||
errorKey,
|
||||
errorMessage,
|
||||
contentComponent,
|
||||
@ -110,7 +110,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
onSubmit={() => onSubmit()}
|
||||
submitText={this.context.t('confirm')}
|
||||
submitButtonType="confirm"
|
||||
disabled={!valid}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -87,7 +87,6 @@ class DropdownMenuItem extends Component {
|
||||
padding: '8px 0px',
|
||||
fontSize: '18px',
|
||||
fontStyle: 'normal',
|
||||
fontFamily: 'Montserrat Regular',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
|
@ -71,7 +71,6 @@ NetworkDropdown.prototype.render = function () {
|
||||
const rpcList = props.frequentRpcList
|
||||
const isOpen = this.props.networkDropdownOpen
|
||||
const dropdownMenuItemStyle = {
|
||||
fontFamily: 'DIN OT',
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
padding: '12px 0',
|
||||
@ -286,7 +285,6 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
onClick: () => props.setRpcTarget(rpc),
|
||||
style: {
|
||||
fontFamily: 'DIN OT',
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
padding: '12px 0',
|
||||
@ -325,7 +323,6 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
|
||||
onClick: () => props.setRpcTarget(rpcTarget),
|
||||
closeMenu: () => this.props.hideNetworkDropdown(),
|
||||
style: {
|
||||
fontFamily: 'DIN OT',
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
padding: '12px 0',
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
formatCurrency,
|
||||
convertTokenToFiat,
|
||||
addFiat,
|
||||
roundExponential,
|
||||
} from '../../../helpers/confirm-transaction/util'
|
||||
|
||||
export default class ConfirmTokenTransactionBase extends Component {
|
||||
@ -42,7 +43,8 @@ export default class ConfirmTokenTransactionBase extends Component {
|
||||
return this.context.t('noConversionRateAvailable')
|
||||
} else {
|
||||
const fiatTransactionAmount = this.getFiatTransactionAmount()
|
||||
return formatCurrency(fiatTransactionAmount, currentCurrency)
|
||||
const roundedFiatTransactionAmount = roundExponential(fiatTransactionAmount)
|
||||
return formatCurrency(roundedFiatTransactionAmount, currentCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +56,8 @@ export default class ConfirmTokenTransactionBase extends Component {
|
||||
} else {
|
||||
const fiatTransactionAmount = this.getFiatTransactionAmount()
|
||||
const fiatTotal = addFiat(fiatTransactionAmount, fiatTransactionTotal)
|
||||
return formatCurrency(fiatTotal, currentCurrency)
|
||||
const roundedFiatTotal = roundExponential(fiatTotal)
|
||||
return formatCurrency(roundedFiatTotal, currentCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,10 @@ export default class ConfirmTransactionBase extends Component {
|
||||
warning: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
submitting: false,
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
const {
|
||||
transactionStatus,
|
||||
@ -258,15 +262,25 @@ export default class ConfirmTransactionBase extends Component {
|
||||
|
||||
handleSubmit () {
|
||||
const { sendTransaction, clearConfirmTransaction, txData, history, onSubmit } = this.props
|
||||
const { submitting } = this.state
|
||||
|
||||
if (submitting) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({ submitting: true })
|
||||
|
||||
if (onSubmit) {
|
||||
onSubmit(txData)
|
||||
Promise.resolve(onSubmit(txData))
|
||||
.then(this.setState({ submitting: false }))
|
||||
} else {
|
||||
sendTransaction(txData)
|
||||
.then(() => {
|
||||
clearConfirmTransaction()
|
||||
this.setState({ submitting: false })
|
||||
history.push(DEFAULT_ROUTE)
|
||||
})
|
||||
.catch(() => this.setState({ submitting: false }))
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +294,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
methodData,
|
||||
ethTransactionAmount,
|
||||
fiatTransactionAmount,
|
||||
valid: propsValid,
|
||||
valid: propsValid = true,
|
||||
errorMessage,
|
||||
errorKey: propsErrorKey,
|
||||
currentCurrency,
|
||||
@ -295,6 +309,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
nonce,
|
||||
warning,
|
||||
} = this.props
|
||||
const { submitting } = this.state
|
||||
|
||||
const { name } = methodData
|
||||
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
|
||||
@ -320,7 +335,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
errorMessage={errorMessage}
|
||||
errorKey={propsErrorKey || errorKey}
|
||||
warning={warning}
|
||||
valid={propsValid || valid}
|
||||
disabled={!propsValid || !valid || submitting}
|
||||
onEdit={() => this.handleEdit()}
|
||||
onCancel={() => this.handleCancel()}
|
||||
onSubmit={() => this.handleSubmit()}
|
||||
|
@ -213,7 +213,7 @@ TxListItem.prototype.showRetryButton = function () {
|
||||
if (!txParams) {
|
||||
return false
|
||||
}
|
||||
let currentTxIsLatest = false
|
||||
let currentTxSharesEarliestNonce = false
|
||||
const currentNonce = txParams.nonce
|
||||
const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce)
|
||||
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
|
||||
@ -222,14 +222,14 @@ TxListItem.prototype.showRetryButton = function () {
|
||||
const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce &&
|
||||
lastSubmittedTxWithCurrentNonce.id === transactionId
|
||||
if (currentSubmittedTxs.length > 0) {
|
||||
const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => {
|
||||
const earliestSubmitted = currentSubmittedTxs.reduce((tx1, tx2) => {
|
||||
if (tx1.submittedTime < tx2.submittedTime) return tx1
|
||||
return tx2
|
||||
})
|
||||
currentTxIsLatest = lastTx.id === transactionId
|
||||
currentTxSharesEarliestNonce = currentNonce === earliestSubmitted.txParams.nonce
|
||||
}
|
||||
|
||||
return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 && currentTxIsLatest
|
||||
return currentTxSharesEarliestNonce && currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000
|
||||
}
|
||||
|
||||
TxListItem.prototype.setSelectedToken = function (tokenAddress) {
|
||||
|
@ -76,7 +76,6 @@
|
||||
}
|
||||
|
||||
.network-name-item {
|
||||
font-weight: 100;
|
||||
flex: 1;
|
||||
color: $dusty-gray;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -3,6 +3,7 @@ import currencies from 'currency-formatter/currencies'
|
||||
import abi from 'human-standard-token-abi'
|
||||
import abiDecoder from 'abi-decoder'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
abiDecoder.addABI(abi)
|
||||
|
||||
@ -137,3 +138,11 @@ export function convertTokenToFiat ({
|
||||
export function hasUnconfirmedTransactions (state) {
|
||||
return unconfirmedTransactionsCountSelector(state) > 0
|
||||
}
|
||||
|
||||
export function roundExponential (value) {
|
||||
const PRECISION = 4
|
||||
const bigNumberValue = new BigNumber(value)
|
||||
|
||||
// In JS, numbers with exponentials greater than 20 get displayed as an exponential.
|
||||
return bigNumberValue.e > 20 ? Number(bigNumberValue.toPrecision(PRECISION)) : value
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createSelector } from 'reselect'
|
||||
import txHelper from '../../lib/tx-helper'
|
||||
import { calcTokenAmount } from '../token-util'
|
||||
import { roundExponential } from '../helpers/confirm-transaction/util'
|
||||
|
||||
const unapprovedTxsSelector = state => state.metamask.unapprovedTxs
|
||||
const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
|
||||
@ -133,7 +134,8 @@ export const tokenAmountAndToAddressSelector = createSelector(
|
||||
const toParam = params.find(param => param.name === TOKEN_PARAM_TO)
|
||||
const valueParam = params.find(param => param.name === TOKEN_PARAM_VALUE)
|
||||
toAddress = toParam ? toParam.value : params[0].value
|
||||
tokenAmount = valueParam ? Number(valueParam.value) : Number(params[1].value)
|
||||
const value = valueParam ? Number(valueParam.value) : Number(params[1].value)
|
||||
tokenAmount = roundExponential(value)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -151,7 +153,8 @@ export const approveTokenAmountAndToAddressSelector = createSelector(
|
||||
|
||||
if (params && params.length) {
|
||||
toAddress = params.find(param => param.name === TOKEN_PARAM_SPENDER).value
|
||||
tokenAmount = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
|
||||
const value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
|
||||
tokenAmount = roundExponential(value)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -170,11 +173,13 @@ export const sendTokenTokenAmountAndToAddressSelector = createSelector(
|
||||
|
||||
if (params && params.length) {
|
||||
toAddress = params.find(param => param.name === TOKEN_PARAM_TO).value
|
||||
tokenAmount = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
|
||||
let value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
|
||||
|
||||
if (tokenDecimals) {
|
||||
tokenAmount = calcTokenAmount(tokenAmount, tokenDecimals)
|
||||
value = calcTokenAmount(value, tokenDecimals)
|
||||
}
|
||||
|
||||
tokenAmount = roundExponential(value)
|
||||
}
|
||||
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user