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

Merge pull request #4888 from whymarrh/suggest-new-ui

Add old UI component to suggest new UI
This commit is contained in:
Whymarrh Whitby 2018-07-31 14:32:21 -02:30 committed by GitHub
commit 4f02726fd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 860 additions and 590 deletions

View File

@ -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
View 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)

View File

@ -14,6 +14,7 @@ const NewKeyChainScreen = require('./new-keychain')
const UnlockScreen = require('./unlock')
// accounts
const AccountDetailScreen = require('./account-detail')
const AccountQrScreen = require('./account-qr')
const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx')
// notice
@ -24,17 +25,13 @@ const ConfigScreen = require('./config')
const AddTokenScreen = require('./add-token')
const Import = require('./accounts/import')
const InfoScreen = require('./info')
const NewUiAnnouncement = require('./new-ui-annoucement')
const AppBar = require('./components/app-bar')
const Loading = require('./components/loading')
const SandwichExpando = require('sandwich-expando')
const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkIndicator = require('./components/network')
const BuyView = require('./components/buy-button-subview')
const QrView = require('./components/qr-code')
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
module.exports = connect(mapStateToProps)(App)
@ -86,13 +83,29 @@ function mapStateToProps (state) {
}
App.prototype.render = function () {
var props = this.props
const { isLoading, loadingMessage, transForward, network } = props
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
`Connecting to ${this.getNetworkName()}` : null
const {
currentView,
dispatch,
isLoading,
loadingMessage,
transForward,
network,
featureFlags,
} = this.props
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork
? `Connecting to ${this.getNetworkName()}`
: null
log.debug('Main ui render function')
if (!featureFlags.skipAnnounceBetaUI) {
return (
h(NewUiAnnouncement, {
dispatch,
})
)
}
return (
h('.flex-column.full-height', {
style: {
@ -102,12 +115,9 @@ App.prototype.render = function () {
alignItems: 'center',
},
}, [
// app bar
this.renderAppBar(),
this.renderNetworkDropdown(),
this.renderDropdown(),
h(AppBar, {
...this.props,
}),
this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
// panel content
@ -121,299 +131,6 @@ App.prototype.render = function () {
])
)
}
App.prototype.renderAppBar = function () {
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
const props = this.props
const state = this.state || {}
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
const {isMascara, isOnboarding} = props
// Do not render header if user is in mascara onboarding
if (isMascara && isOnboarding) {
return null
}
// Do not render header if user is in mascara buy ether
if (isMascara && props.currentView.name === 'buyEth') {
return null
}
return (
h('.full-width', {
height: '38px',
}, [
h('.app-header.flex-row.flex-space-between', {
style: {
alignItems: 'center',
visibility: props.isUnlocked ? 'visible' : 'none',
background: props.isUnlocked ? 'white' : 'none',
height: '38px',
position: 'relative',
zIndex: 12,
},
}, [
h('div.left-menu-section', {
style: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
}, [
// mini logo
h('img', {
height: 24,
width: 24,
src: './images/icon-128.png',
}),
h(NetworkIndicator, {
network: this.props.network,
provider: this.props.provider,
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
},
}),
]),
props.isUnlocked && h('div', {
style: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
}, [
props.isUnlocked && h(AccountDropdowns, {
style: {},
enableAccountsSelector: true,
identities: this.props.identities,
selected: this.props.selectedAddress,
network: this.props.network,
keyrings: this.props.keyrings,
}, []),
// hamburger
props.isUnlocked && h(SandwichExpando, {
className: 'sandwich-expando',
width: 16,
barHeight: 2,
padding: 0,
isOpen: state.isMainMenuOpen,
color: 'rgb(247,146,30)',
onClick: () => {
this.setState({
isMainMenuOpen: !state.isMainMenuOpen,
})
},
}),
]),
]),
])
)
}
App.prototype.renderNetworkDropdown = function () {
const props = this.props
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
const rpcList = props.frequentRpcList
const state = this.state || {}
const isOpen = state.isNetworkMenuOpen
return h(Dropdown, {
useCssTransition: true,
isOpen,
onClickOutside: (event) => {
const { classList } = event.target
const isNotToggleElement = [
classList.contains('menu-icon'),
classList.contains('network-name'),
classList.contains('network-indicator'),
].filter(bool => bool).length === 0
// classes from three constituent nodes of the toggle element
if (isNotToggleElement) {
this.setState({ isNetworkMenuOpen: false })
}
},
zIndex: 11,
style: {
position: 'absolute',
left: '2px',
top: '36px',
},
innerStyle: {
padding: '2px 16px 2px 0px',
},
}, [
h(
DropdownMenuItem,
{
key: 'main',
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
style: {
fontSize: '18px',
},
},
[
h('.menu-icon.diamond'),
'Main Ethereum Network',
providerType === 'mainnet' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
key: 'ropsten',
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
style: {
fontSize: '18px',
},
},
[
h('.menu-icon.red-dot'),
'Ropsten Test Network',
providerType === 'ropsten' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
key: 'kovan',
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('kovan')),
style: {
fontSize: '18px',
},
},
[
h('.menu-icon.hollow-diamond'),
'Kovan Test Network',
providerType === 'kovan' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
key: 'rinkeby',
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
style: {
fontSize: '18px',
},
},
[
h('.menu-icon.golden-square'),
'Rinkeby Test Network',
providerType === 'rinkeby' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
key: 'default',
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('localhost')),
style: {
fontSize: '18px',
},
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
'Localhost 8545',
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
]
),
this.renderCustomOption(props.provider),
this.renderCommonRpc(rpcList, props.provider),
h(
DropdownMenuItem,
{
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => this.props.dispatch(actions.showConfigPage()),
style: {
fontSize: '18px',
},
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
'Custom RPC',
activeNetwork === 'custom' ? h('.check', '✓') : null,
]
),
])
}
App.prototype.renderDropdown = function () {
const state = this.state || {}
const isOpen = state.isMainMenuOpen
return h(Dropdown, {
useCssTransition: true,
isOpen: isOpen,
zIndex: 11,
onClickOutside: (event) => {
const classList = event.target.classList
const parentClassList = event.target.parentElement.classList
const isToggleElement = classList.contains('sandwich-expando') ||
parentClassList.contains('sandwich-expando')
if (isOpen && !isToggleElement) {
this.setState({ isMainMenuOpen: false })
}
},
style: {
position: 'absolute',
right: '2px',
top: '38px',
},
innerStyle: {},
}, [
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.showConfigPage()) },
}, 'Settings'),
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
}, 'Log Out'),
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.showInfoPage()) },
}, 'Info/Help'),
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => {
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
},
}, 'Try Beta!'),
])
}
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
const { isMascara } = this.props
@ -425,25 +142,6 @@ App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork,
})
}
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
return (
h('.flex-row', {
key: 'leftArrow',
style: style,
onClick: () => props.dispatch(actions.goBackToInitView()),
}, [
h('i.fa.fa-arrow-left.cursor-pointer'),
justArrow ? null : h('div.cursor-pointer', {
style: {
marginLeft: '3px',
},
onClick: () => props.dispatch(actions.goBackToInitView()),
}, 'BACK'),
])
)
}
App.prototype.renderPrimary = function () {
log.debug('rendering primary')
var props = this.props
@ -465,22 +163,6 @@ App.prototype.renderPrimary = function () {
key: 'NoticeScreen',
onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
}),
!props.isInitialized && h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: () => {
global.platform.openExtensionInBrowser()
props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
},
style: {
fontSize: '0.8em',
color: '#aeaeae',
textDecoration: 'underline',
marginTop: '32px',
},
}, 'Try Beta Version'),
]),
])
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
log.debug('rendering notice screen for lost accounts view.')
@ -580,31 +262,10 @@ App.prototype.renderPrimary = function () {
case 'qr':
log.debug('rendering show qr screen')
return h('div', {
style: {
position: 'absolute',
height: '100%',
top: '0px',
left: '0px',
},
}, [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
onClick: () => props.dispatch(actions.backToAccountDetail(props.selectedAddress)),
style: {
marginLeft: '10px',
marginTop: '50px',
},
}),
h('div', {
style: {
position: 'absolute',
left: '44px',
width: '285px',
},
}, [
h(QrView, {key: 'qr'}),
]),
])
return h(AccountQrScreen, {
key: 'account-qr',
selectedAddress: props.selectedAddress,
})
default:
log.debug('rendering default, account detail screen')
@ -623,41 +284,6 @@ App.prototype.toggleMetamaskActive = function () {
this.props.dispatch(actions.lockMetamask(false))
}
}
App.prototype.renderCustomOption = function (provider) {
const { rpcTarget, type } = provider
const props = this.props
if (type !== 'rpc') return null
// Concatenate long URLs
let label = rpcTarget
if (rpcTarget.length > 31) {
label = label.substr(0, 34) + '...'
}
switch (rpcTarget) {
case 'http://localhost:8545':
return null
default:
return h(
DropdownMenuItem,
{
key: rpcTarget,
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
label,
h('.check', '✓'),
]
)
}
}
App.prototype.getNetworkName = function () {
const { provider } = this.props
const providerName = provider.type
@ -678,28 +304,3 @@ App.prototype.getNetworkName = function () {
return name
}
App.prototype.renderCommonRpc = function (rpcList, provider) {
const props = this.props
const rpcTarget = provider.rpcTarget
return rpcList.map((rpc) => {
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
return null
} else {
return h(
DropdownMenuItem,
{
key: `common${rpc}`,
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
rpc,
rpcTarget === rpc ? h('.check', '✓') : null,
]
)
}
})
}

View 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: '&nbsp;',
},
})
)
}
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(),
])
}
}

View File

@ -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
}

View File

@ -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 () {

View File

@ -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;
}
}

View 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: '&nbsp;',
},
}),
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'),
]),
])
)
}
}

View File

@ -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 * 2)
})
it('choose Create Account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click()
await delay(regularDelayMs)

View File

@ -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()
@ -389,6 +383,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/))

View File

@ -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)

View File

@ -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')