mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'develop' into WatchTokenFeature
This commit is contained in:
commit
3f57d5f66b
@ -2,6 +2,10 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
- [#4884](https://github.com/MetaMask/metamask-extension/pull/4884): Allow to have tokens per account and network.
|
||||
|
||||
## 4.9.0 Tue Aug 07 2018
|
||||
|
||||
- Add new tokens auto detection
|
||||
- Remove rejected transactions from transaction history
|
||||
- Add Trezor Support
|
||||
|
@ -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`.
|
||||
|
@ -513,6 +513,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>
|
||||
|
@ -85,7 +85,7 @@ class DetectTokensController {
|
||||
set preferences (preferences) {
|
||||
if (!preferences) { return }
|
||||
this._preferences = preferences
|
||||
preferences.store.subscribe(({ tokens }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) })
|
||||
preferences.store.subscribe(({ tokens = [] }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) })
|
||||
preferences.store.subscribe(({ selectedAddress }) => {
|
||||
if (this.selectedAddress !== selectedAddress) {
|
||||
this.selectedAddress = selectedAddress
|
||||
|
@ -14,6 +14,7 @@ class PreferencesController {
|
||||
* @property {array} store.frequentRpcList A list of custom rpcs to provide the user
|
||||
* @property {string} store.currentAccountTab Indicates the selected tab in the ui
|
||||
* @property {array} store.tokens The tokens the user wants display in their token lists
|
||||
* @property {object} store.accountTokens The tokens stored per account and then per network type
|
||||
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
|
||||
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
|
||||
* user wishes to see that feature
|
||||
@ -25,6 +26,7 @@ class PreferencesController {
|
||||
const initState = extend({
|
||||
frequentRpcList: [],
|
||||
currentAccountTab: 'history',
|
||||
accountTokens: {},
|
||||
tokens: [],
|
||||
suggestedTokens: {},
|
||||
useBlockie: false,
|
||||
@ -35,9 +37,10 @@ class PreferencesController {
|
||||
}, opts.initState)
|
||||
|
||||
this.diagnostics = opts.diagnostics
|
||||
|
||||
this.network = opts.network
|
||||
this.store = new ObservableStore(initState)
|
||||
this.showAddTokenUi = opts.showAddTokenUi
|
||||
this._subscribeProviderType()
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
|
||||
@ -121,12 +124,19 @@ class PreferencesController {
|
||||
*/
|
||||
setAddresses (addresses) {
|
||||
const oldIdentities = this.store.getState().identities
|
||||
const oldAccountTokens = this.store.getState().accountTokens
|
||||
|
||||
const identities = addresses.reduce((ids, address, index) => {
|
||||
const oldId = oldIdentities[address] || {}
|
||||
ids[address] = {name: `Account ${index + 1}`, address, ...oldId}
|
||||
return ids
|
||||
}, {})
|
||||
this.store.updateState({ identities })
|
||||
const accountTokens = addresses.reduce((tokens, address) => {
|
||||
const oldTokens = oldAccountTokens[address] || {}
|
||||
tokens[address] = oldTokens
|
||||
return tokens
|
||||
}, {})
|
||||
this.store.updateState({ identities, accountTokens })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,11 +147,13 @@ class PreferencesController {
|
||||
*/
|
||||
removeAddress (address) {
|
||||
const identities = this.store.getState().identities
|
||||
const accountTokens = this.store.getState().accountTokens
|
||||
if (!identities[address]) {
|
||||
throw new Error(`${address} can't be deleted cause it was not found`)
|
||||
}
|
||||
delete identities[address]
|
||||
this.store.updateState({ identities })
|
||||
delete accountTokens[address]
|
||||
this.store.updateState({ identities, accountTokens })
|
||||
|
||||
// If the selected account is no longer valid,
|
||||
// select an arbitrary other account:
|
||||
@ -161,14 +173,17 @@ class PreferencesController {
|
||||
*/
|
||||
addAddresses (addresses) {
|
||||
const identities = this.store.getState().identities
|
||||
const accountTokens = this.store.getState().accountTokens
|
||||
addresses.forEach((address) => {
|
||||
// skip if already exists
|
||||
if (identities[address]) return
|
||||
// add missing identity
|
||||
const identityCount = Object.keys(identities).length
|
||||
|
||||
accountTokens[address] = {}
|
||||
identities[address] = { name: `Account ${identityCount + 1}`, address }
|
||||
})
|
||||
this.store.updateState({ identities })
|
||||
this.store.updateState({ identities, accountTokens })
|
||||
}
|
||||
|
||||
/*
|
||||
@ -226,15 +241,15 @@ class PreferencesController {
|
||||
* Setter for the `selectedAddress` property
|
||||
*
|
||||
* @param {string} _address A new hex address for an account
|
||||
* @returns {Promise<void>} Promise resolves with undefined
|
||||
* @returns {Promise<void>} Promise resolves with tokens
|
||||
*
|
||||
*/
|
||||
setSelectedAddress (_address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const address = normalizeAddress(_address)
|
||||
this.store.updateState({ selectedAddress: address })
|
||||
resolve()
|
||||
})
|
||||
const address = normalizeAddress(_address)
|
||||
this._updateTokens(address)
|
||||
this.store.updateState({ selectedAddress: address })
|
||||
const tokens = this.store.getState().tokens
|
||||
return Promise.resolve(tokens)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -283,9 +298,7 @@ class PreferencesController {
|
||||
} else {
|
||||
tokens.push(newEntry)
|
||||
}
|
||||
|
||||
this.store.updateState({ tokens })
|
||||
|
||||
this._updateAccountTokens(tokens)
|
||||
return Promise.resolve(tokens)
|
||||
}
|
||||
|
||||
@ -298,10 +311,8 @@ class PreferencesController {
|
||||
*/
|
||||
removeToken (rawAddress) {
|
||||
const tokens = this.store.getState().tokens
|
||||
|
||||
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
|
||||
|
||||
this.store.updateState({ tokens: updatedTokens })
|
||||
this._updateAccountTokens(updatedTokens)
|
||||
return Promise.resolve(updatedTokens)
|
||||
}
|
||||
|
||||
@ -443,6 +454,57 @@ class PreferencesController {
|
||||
const numDecimals = parseInt(decimals, 10)
|
||||
if (isNaN(numDecimals) || numDecimals > 18 || numDecimals < 0) throw new Error(`Invalid decimals ${decimals}`)
|
||||
if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`)
|
||||
|
||||
/**
|
||||
* Subscription to network provider type.
|
||||
*
|
||||
*
|
||||
*/
|
||||
_subscribeProviderType () {
|
||||
this.network.providerStore.subscribe(() => {
|
||||
const { tokens } = this._getTokenRelatedStates()
|
||||
this.store.updateState({ tokens })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates `accountTokens` and `tokens` of current account and network according to it.
|
||||
*
|
||||
* @param {array} tokens Array of tokens to be updated.
|
||||
*
|
||||
*/
|
||||
_updateAccountTokens (tokens) {
|
||||
const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates()
|
||||
accountTokens[selectedAddress][providerType] = tokens
|
||||
this.store.updateState({ accountTokens, tokens })
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates `tokens` of current account and network.
|
||||
*
|
||||
* @param {string} selectedAddress Account address to be updated with.
|
||||
*
|
||||
*/
|
||||
_updateTokens (selectedAddress) {
|
||||
const { tokens } = this._getTokenRelatedStates(selectedAddress)
|
||||
this.store.updateState({ tokens })
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for `tokens` and `accountTokens` related states.
|
||||
*
|
||||
* @param {string} selectedAddress A new hex address for an account
|
||||
* @returns {Object.<array, object, string, string>} States to interact with tokens in `accountTokens`
|
||||
*
|
||||
*/
|
||||
_getTokenRelatedStates (selectedAddress) {
|
||||
const accountTokens = this.store.getState().accountTokens
|
||||
if (!selectedAddress) selectedAddress = this.store.getState().selectedAddress
|
||||
const providerType = this.network.providerStore.getState().type
|
||||
if (!(selectedAddress in accountTokens)) accountTokens[selectedAddress] = {}
|
||||
if (!(providerType in accountTokens[selectedAddress])) accountTokens[selectedAddress][providerType] = []
|
||||
const tokens = accountTokens[selectedAddress][providerType]
|
||||
return { tokens, accountTokens, providerType, selectedAddress }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
initState: initState.PreferencesController,
|
||||
initLangCode: opts.initLangCode,
|
||||
showAddTokenUi: opts.showAddTokenUi,
|
||||
network: this.networkController,
|
||||
})
|
||||
|
||||
// currency controller
|
||||
@ -1439,7 +1440,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for activating the retrieval of price data and auto detect tokens,
|
||||
* A method for activating the retrieval of price data,
|
||||
* which should only be fetched when the UI is visible.
|
||||
* @private
|
||||
* @param {boolean} active - True if price data should be getting fetched.
|
||||
|
40
app/scripts/migrations/028.js
Normal file
40
app/scripts/migrations/028.js
Normal file
@ -0,0 +1,40 @@
|
||||
// next version number
|
||||
const version = 28
|
||||
|
||||
/*
|
||||
|
||||
normalizes txParams on unconfirmed txs
|
||||
|
||||
*/
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
|
||||
if (newState.PreferencesController) {
|
||||
if (newState.PreferencesController.tokens) {
|
||||
const identities = newState.TransactionController.identities
|
||||
const tokens = newState.PreferencesController.tokens
|
||||
newState.PreferencesController.accountTokens = {}
|
||||
for (const identity in identities) {
|
||||
newState.PreferencesController.accountTokens[identity] = {'mainnet': tokens}
|
||||
}
|
||||
newState.PreferencesController.tokens = []
|
||||
}
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
@ -38,4 +38,5 @@ module.exports = [
|
||||
require('./025'),
|
||||
require('./026'),
|
||||
require('./027'),
|
||||
require('./028'),
|
||||
]
|
||||
|
@ -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
|
||||
@ -25,17 +26,13 @@ const AddTokenScreen = require('./add-token')
|
||||
const AddSuggestedTokenScreen = require('./add-suggested-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)
|
||||
|
||||
@ -88,13 +85,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: {
|
||||
@ -104,12 +117,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
|
||||
@ -123,299 +133,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
|
||||
|
||||
@ -427,25 +144,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
|
||||
@ -467,22 +165,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.')
|
||||
@ -586,31 +268,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')
|
||||
@ -629,41 +290,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
|
||||
@ -684,28 +310,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'),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
663
package-lock.json
generated
663
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')
|
||||
|
@ -7,10 +7,11 @@ const PreferencesController = require('../../../../app/scripts/controllers/prefe
|
||||
|
||||
describe('DetectTokensController', () => {
|
||||
const sandbox = sinon.createSandbox()
|
||||
let clock
|
||||
let keyringMemStore
|
||||
before(async () => {
|
||||
let clock, keyringMemStore, network, preferences
|
||||
beforeEach(async () => {
|
||||
keyringMemStore = new ObservableStore({ isUnlocked: false})
|
||||
network = new NetworkController({ provider: { type: 'mainnet' }})
|
||||
preferences = new PreferencesController({ network })
|
||||
})
|
||||
after(() => {
|
||||
sandbox.restore()
|
||||
@ -25,9 +26,7 @@ describe('DetectTokensController', () => {
|
||||
|
||||
it('should be called on every polling period', async () => {
|
||||
clock = sandbox.useFakeTimers()
|
||||
const network = new NetworkController()
|
||||
network.setProviderType('mainnet')
|
||||
const preferences = new PreferencesController()
|
||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||
controller.isOpen = true
|
||||
controller.isUnlocked = true
|
||||
@ -45,9 +44,7 @@ describe('DetectTokensController', () => {
|
||||
})
|
||||
|
||||
it('should not check tokens while in test network', async () => {
|
||||
const network = new NetworkController()
|
||||
network.setProviderType('rinkeby')
|
||||
const preferences = new PreferencesController()
|
||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||
controller.isOpen = true
|
||||
controller.isUnlocked = true
|
||||
@ -61,9 +58,7 @@ describe('DetectTokensController', () => {
|
||||
})
|
||||
|
||||
it('should only check and add tokens while in main network', async () => {
|
||||
const network = new NetworkController()
|
||||
network.setProviderType('mainnet')
|
||||
const preferences = new PreferencesController()
|
||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||
controller.isOpen = true
|
||||
controller.isUnlocked = true
|
||||
@ -80,9 +75,7 @@ describe('DetectTokensController', () => {
|
||||
})
|
||||
|
||||
it('should not detect same token while in main network', async () => {
|
||||
const network = new NetworkController()
|
||||
network.setProviderType('mainnet')
|
||||
const preferences = new PreferencesController()
|
||||
preferences.addToken('0x0d262e5dc4a06a0f1c90ce79c7a60c09dfc884e4', 'J8T', 8)
|
||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||
controller.isOpen = true
|
||||
@ -100,9 +93,7 @@ describe('DetectTokensController', () => {
|
||||
})
|
||||
|
||||
it('should trigger detect new tokens when change address', async () => {
|
||||
const network = new NetworkController()
|
||||
network.setProviderType('mainnet')
|
||||
const preferences = new PreferencesController()
|
||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||
controller.isOpen = true
|
||||
controller.isUnlocked = true
|
||||
@ -112,9 +103,7 @@ describe('DetectTokensController', () => {
|
||||
})
|
||||
|
||||
it('should trigger detect new tokens when submit password', async () => {
|
||||
const network = new NetworkController()
|
||||
network.setProviderType('mainnet')
|
||||
const preferences = new PreferencesController()
|
||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||
controller.isOpen = true
|
||||
controller.selectedAddress = '0x0'
|
||||
@ -124,9 +113,7 @@ describe('DetectTokensController', () => {
|
||||
})
|
||||
|
||||
it('should not trigger detect new tokens when not open or not unlocked', async () => {
|
||||
const network = new NetworkController()
|
||||
network.setProviderType('mainnet')
|
||||
const preferences = new PreferencesController()
|
||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||
controller.isOpen = true
|
||||
controller.isUnlocked = false
|
||||
|
@ -1,11 +1,14 @@
|
||||
const assert = require('assert')
|
||||
const ObservableStore = require('obs-store')
|
||||
const PreferencesController = require('../../../../app/scripts/controllers/preferences')
|
||||
|
||||
describe('preferences controller', function () {
|
||||
let preferencesController
|
||||
let network
|
||||
|
||||
beforeEach(() => {
|
||||
preferencesController = new PreferencesController()
|
||||
network = {providerStore: new ObservableStore({ type: 'mainnet' })}
|
||||
preferencesController = new PreferencesController({ network })
|
||||
})
|
||||
|
||||
describe('setAddresses', function () {
|
||||
@ -28,6 +31,20 @@ describe('preferences controller', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('should create account tokens for each account in the store', function () {
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le',
|
||||
'0x7e57e2',
|
||||
])
|
||||
|
||||
const accountTokens = preferencesController.store.getState().accountTokens
|
||||
|
||||
assert.deepEqual(accountTokens, {
|
||||
'0xda22le': {},
|
||||
'0x7e57e2': {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should replace its list of addresses', function () {
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le',
|
||||
@ -64,6 +81,17 @@ describe('preferences controller', function () {
|
||||
assert.equal(preferencesController.store.getState().identities['0xda22le'], undefined)
|
||||
})
|
||||
|
||||
it('should remove an address from state and respective tokens', function () {
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le',
|
||||
'0x7e57e2',
|
||||
])
|
||||
|
||||
preferencesController.removeAddress('0xda22le')
|
||||
|
||||
assert.equal(preferencesController.store.getState().accountTokens['0xda22le'], undefined)
|
||||
})
|
||||
|
||||
it('should switch accounts if the selected address is removed', function () {
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le',
|
||||
@ -158,6 +186,42 @@ describe('preferences controller', function () {
|
||||
await preferencesController.addToken(address, symbol, decimals)
|
||||
assert.equal(preferencesController.getTokens().length, 1, 'one token added for 2nd address')
|
||||
})
|
||||
|
||||
it('should add token per account', async function () {
|
||||
const addressFirst = '0xabcdef1234567'
|
||||
const addressSecond = '0xabcdef1234568'
|
||||
const symbolFirst = 'ABBR'
|
||||
const symbolSecond = 'ABBB'
|
||||
const decimals = 5
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken(addressFirst, symbolFirst, decimals)
|
||||
const tokensFirstAddress = preferencesController.getTokens()
|
||||
|
||||
await preferencesController.setSelectedAddress('0xda22le')
|
||||
await preferencesController.addToken(addressSecond, symbolSecond, decimals)
|
||||
const tokensSeconAddress = preferencesController.getTokens()
|
||||
|
||||
assert.notEqual(tokensFirstAddress, tokensSeconAddress, 'add different tokens for two account and tokens are equal')
|
||||
})
|
||||
|
||||
it('should add token per network', async function () {
|
||||
const addressFirst = '0xabcdef1234567'
|
||||
const addressSecond = '0xabcdef1234568'
|
||||
const symbolFirst = 'ABBR'
|
||||
const symbolSecond = 'ABBB'
|
||||
const decimals = 5
|
||||
|
||||
network.providerStore.updateState({ type: 'mainnet' })
|
||||
await preferencesController.addToken(addressFirst, symbolFirst, decimals)
|
||||
const tokensFirstAddress = preferencesController.getTokens()
|
||||
|
||||
network.providerStore.updateState({ type: 'rinkeby' })
|
||||
await preferencesController.addToken(addressSecond, symbolSecond, decimals)
|
||||
const tokensSeconAddress = preferencesController.getTokens()
|
||||
|
||||
assert.notEqual(tokensFirstAddress, tokensSeconAddress, 'add different tokens for two networks and tokens are equal')
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeToken', function () {
|
||||
@ -182,6 +246,98 @@ describe('preferences controller', function () {
|
||||
const [token1] = tokens
|
||||
assert.deepEqual(token1, {address: '0xb', symbol: 'B', decimals: 5})
|
||||
})
|
||||
|
||||
it('should remove a token from its state on corresponding address', async function () {
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken('0xa', 'A', 4)
|
||||
await preferencesController.addToken('0xb', 'B', 5)
|
||||
await preferencesController.setSelectedAddress('0x7e57e3')
|
||||
await preferencesController.addToken('0xa', 'A', 4)
|
||||
await preferencesController.addToken('0xb', 'B', 5)
|
||||
const initialTokensSecond = preferencesController.getTokens()
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.removeToken('0xa')
|
||||
|
||||
const tokensFirst = preferencesController.getTokens()
|
||||
assert.equal(tokensFirst.length, 1, 'one token removed in account')
|
||||
|
||||
const [token1] = tokensFirst
|
||||
assert.deepEqual(token1, {address: '0xb', symbol: 'B', decimals: 5})
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e3')
|
||||
const tokensSecond = preferencesController.getTokens()
|
||||
assert.deepEqual(tokensSecond, initialTokensSecond, 'token deleted for account')
|
||||
})
|
||||
|
||||
it('should remove a token from its state on corresponding network', async function () {
|
||||
network.providerStore.updateState({ type: 'mainnet' })
|
||||
await preferencesController.addToken('0xa', 'A', 4)
|
||||
await preferencesController.addToken('0xb', 'B', 5)
|
||||
network.providerStore.updateState({ type: 'rinkeby' })
|
||||
await preferencesController.addToken('0xa', 'A', 4)
|
||||
await preferencesController.addToken('0xb', 'B', 5)
|
||||
const initialTokensSecond = preferencesController.getTokens()
|
||||
network.providerStore.updateState({ type: 'mainnet' })
|
||||
await preferencesController.removeToken('0xa')
|
||||
|
||||
const tokensFirst = preferencesController.getTokens()
|
||||
assert.equal(tokensFirst.length, 1, 'one token removed in network')
|
||||
|
||||
const [token1] = tokensFirst
|
||||
assert.deepEqual(token1, {address: '0xb', symbol: 'B', decimals: 5})
|
||||
|
||||
network.providerStore.updateState({ type: 'rinkeby' })
|
||||
const tokensSecond = preferencesController.getTokens()
|
||||
assert.deepEqual(tokensSecond, initialTokensSecond, 'token deleted for network')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on setSelectedAddress', function () {
|
||||
it('should update tokens from its state on corresponding address', async function () {
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken('0xa', 'A', 4)
|
||||
await preferencesController.addToken('0xb', 'B', 5)
|
||||
await preferencesController.setSelectedAddress('0x7e57e3')
|
||||
await preferencesController.addToken('0xa', 'C', 4)
|
||||
await preferencesController.addToken('0xb', 'D', 5)
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
const initialTokensFirst = preferencesController.getTokens()
|
||||
await preferencesController.setSelectedAddress('0x7e57e3')
|
||||
const initialTokensSecond = preferencesController.getTokens()
|
||||
|
||||
assert.notDeepEqual(initialTokensFirst, initialTokensSecond, 'tokens not equal for different accounts and tokens')
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
const tokensFirst = preferencesController.getTokens()
|
||||
await preferencesController.setSelectedAddress('0x7e57e3')
|
||||
const tokensSecond = preferencesController.getTokens()
|
||||
|
||||
assert.deepEqual(tokensFirst, initialTokensFirst, 'tokens equal for same account')
|
||||
assert.deepEqual(tokensSecond, initialTokensSecond, 'tokens equal for same account')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on updateStateNetworkType', function () {
|
||||
it('should remove a token from its state on corresponding network', async function () {
|
||||
network.providerStore.updateState({ type: 'mainnet' })
|
||||
await preferencesController.addToken('0xa', 'A', 4)
|
||||
await preferencesController.addToken('0xb', 'B', 5)
|
||||
const initialTokensFirst = preferencesController.getTokens()
|
||||
network.providerStore.updateState({ type: 'rinkeby' })
|
||||
await preferencesController.addToken('0xa', 'C', 4)
|
||||
await preferencesController.addToken('0xb', 'D', 5)
|
||||
const initialTokensSecond = preferencesController.getTokens()
|
||||
|
||||
assert.notDeepEqual(initialTokensFirst, initialTokensSecond, 'tokens not equal for different networks and tokens')
|
||||
|
||||
network.providerStore.updateState({ type: 'mainnet' })
|
||||
const tokensFirst = preferencesController.getTokens()
|
||||
network.providerStore.updateState({ type: 'rinkeby' })
|
||||
const tokensSecond = preferencesController.getTokens()
|
||||
assert.deepEqual(tokensFirst, initialTokensFirst, 'tokens equal for same network')
|
||||
assert.deepEqual(tokensSecond, initialTokensSecond, 'tokens equal for same network')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1486,11 +1486,12 @@ function showAccountDetail (address) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
log.debug(`background.setSelectedAddress`)
|
||||
background.setSelectedAddress(address, (err) => {
|
||||
background.setSelectedAddress(address, (err, tokens) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
dispatch(updateTokens(tokens))
|
||||
dispatch({
|
||||
type: actions.SHOW_ACCOUNT_DETAIL,
|
||||
value: address,
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user