From c8d45cb4a8a671bad20cd5a5aa1bcbbd88463bdb Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 25 Jul 2018 20:02:12 -0400 Subject: only show retry button on earliest pending tx --- old-ui/app/components/transaction-list-item.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/transaction-list-item.js b/old-ui/app/components/transaction-list-item.js index e9280419a..e12f9cd6a 100644 --- a/old-ui/app/components/transaction-list-item.js +++ b/old-ui/app/components/transaction-list-item.js @@ -39,11 +39,17 @@ TransactionListItem.prototype.showRetryButton = function () { const currentNonce = txParams.nonce const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const currentSubmittedTxs = transactions.filter(tx => tx.status === 'submitted') const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0] const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transaction.id + const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { + if (tx1.id < tx2.id) return tx1 + return tx2 + }) - return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 + const currentTxIsLatest = lastTx.id === transaction.id + return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 && currentTxIsLatest } TransactionListItem.prototype.render = function () { -- cgit From b580f60d749b862fe08adfc7457c726905ebc623 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 25 Jul 2018 20:58:20 -0400 Subject: earliest tx by submittedTime --- old-ui/app/components/transaction-list-item.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/transaction-list-item.js b/old-ui/app/components/transaction-list-item.js index e12f9cd6a..f479ce666 100644 --- a/old-ui/app/components/transaction-list-item.js +++ b/old-ui/app/components/transaction-list-item.js @@ -36,6 +36,7 @@ TransactionListItem.prototype.showRetryButton = function () { return false } + let currentTxIsLatest = false const currentNonce = txParams.nonce const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') @@ -43,12 +44,14 @@ TransactionListItem.prototype.showRetryButton = function () { const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0] const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transaction.id - const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { - if (tx1.id < tx2.id) return tx1 - return tx2 - }) + if (currentSubmittedTxs.length > 0) { + const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { + if (tx1.submittedTime < tx2.submittedTime) return tx1 + return tx2 + }) + currentTxIsLatest = lastTx.id === transaction.id + } - const currentTxIsLatest = lastTx.id === transaction.id return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 && currentTxIsLatest } -- cgit From 1d6227d718c94a2b0b4b3743985929790ffa26a2 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 25 Jul 2018 19:33:27 -0230 Subject: Move the old UI's AppBar into its own component --- old-ui/app/components/app-bar.js | 385 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 old-ui/app/components/app-bar.js (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/app-bar.js b/old-ui/app/components/app-bar.js new file mode 100644 index 000000000..ea04f40f0 --- /dev/null +++ b/old-ui/app/components/app-bar.js @@ -0,0 +1,385 @@ +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, + } + + 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.renderAppBarAppHeader(), + ]) + ) + } + + 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: '36px', + }, + 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: '38px', + }, + 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(), + ]) + } +} -- cgit From 6ae76fee33af8f3079fd650f35df1107e7996ffe Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 25 Jul 2018 23:19:18 -0230 Subject: Add banner suggesting new UI --- old-ui/app/components/app-bar.js | 50 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/app-bar.js b/old-ui/app/components/app-bar.js index ea04f40f0..4dfedc633 100644 --- a/old-ui/app/components/app-bar.js +++ b/old-ui/app/components/app-bar.js @@ -28,6 +28,16 @@ module.exports = class AppBar extends Component { provider: PropTypes.any.isRequired, } + static renderSpace () { + return ( + h('span', { + dangerouslySetInnerHTML: { + __html: ' ', + }, + }) + ) + } + state = { isNetworkMenuOpen: false, } @@ -52,11 +62,47 @@ module.exports = class AppBar extends Component { 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', { + onClick () { + dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL')) + }, + }, [ + '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, @@ -178,7 +224,7 @@ module.exports = class AppBar extends Component { style: { position: 'absolute', left: '2px', - top: '36px', + top: '64px', }, innerStyle: { padding: '2px 16px 2px 0px', @@ -347,7 +393,7 @@ module.exports = class AppBar extends Component { style: { position: 'absolute', right: '2px', - top: '38px', + top: '66px', }, innerStyle: {}, }, [ -- cgit From d16f25fc20c98ac06b2e13be6f2732d60919a862 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 25 Jul 2018 23:49:57 -0230 Subject: Add NewUiAnnouncement component --- old-ui/app/components/app-bar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/app-bar.js b/old-ui/app/components/app-bar.js index 4dfedc633..e65f942ae 100644 --- a/old-ui/app/components/app-bar.js +++ b/old-ui/app/components/app-bar.js @@ -81,8 +81,8 @@ module.exports = class AppBar extends Component { 'Try the New MetaMask', AppBar.renderSpace(), h('span.banner__link', { - onClick () { - dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL')) + async onClick () { + await dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL')) }, }, [ 'Now', -- cgit From 89c74ac4adf7d064e144594537d8e8ee5a0f8082 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 27 Jul 2018 19:43:07 -0230 Subject: Rewrite AccountQrScreen to use flexbox The old QR screen was absolutely positioning everything which broke when the app bar resized for the new UI announcement. This change, while futile*, makes the QR screen less bad. * futile because the old UI is being deprecated --- old-ui/app/components/qr-code.js | 79 -------------------------------- old-ui/app/components/shapeshift-form.js | 4 +- 2 files changed, 1 insertion(+), 82 deletions(-) delete mode 100644 old-ui/app/components/qr-code.js (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/qr-code.js b/old-ui/app/components/qr-code.js deleted file mode 100644 index 06b9aed9b..000000000 --- a/old-ui/app/components/qr-code.js +++ /dev/null @@ -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 -} diff --git a/old-ui/app/components/shapeshift-form.js b/old-ui/app/components/shapeshift-form.js index 97068db0a..14de309ab 100644 --- a/old-ui/app/components/shapeshift-form.js +++ b/old-ui/app/components/shapeshift-form.js @@ -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 () { -- cgit From 0fd8862c5e942778763e170a36aeb7604472c5cd Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 27 Jul 2018 20:05:26 -0230 Subject: Don't show an additional beta UI notification When a user has opted-in to the new UI from the announcement, we don't need an additional notification letting the user know that they've switched. --- old-ui/app/components/app-bar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/app-bar.js b/old-ui/app/components/app-bar.js index e65f942ae..4253c5f28 100644 --- a/old-ui/app/components/app-bar.js +++ b/old-ui/app/components/app-bar.js @@ -82,7 +82,7 @@ module.exports = class AppBar extends Component { AppBar.renderSpace(), h('span.banner__link', { async onClick () { - await dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL')) + await dispatch(actions.setFeatureFlag('betaUI', true)) }, }, [ 'Now', -- cgit From f9871fe60b7c6300e746605948eb690b8e48aecc Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 27 Jul 2018 20:09:31 -0230 Subject: Open UI in browser post-new-UI-switch --- old-ui/app/components/app-bar.js | 1 + 1 file changed, 1 insertion(+) (limited to 'old-ui/app/components') diff --git a/old-ui/app/components/app-bar.js b/old-ui/app/components/app-bar.js index 4253c5f28..8ab647efd 100644 --- a/old-ui/app/components/app-bar.js +++ b/old-ui/app/components/app-bar.js @@ -83,6 +83,7 @@ module.exports = class AppBar extends Component { h('span.banner__link', { async onClick () { await dispatch(actions.setFeatureFlag('betaUI', true)) + global.platform.openExtensionInBrowser() }, }, [ 'Now', -- cgit