Merge branch 'NewUI' of github.com:MetaMask/metamask-plugin into NewUI

feature/default_network_editable
Kevin Serrano 7 years ago
commit 81982d01c0
No known key found for this signature in database
GPG Key ID: BF999DEFC7371BA1
  1. 2
      CHANGELOG.md
  2. 12
      app/home.html
  3. 5
      app/popup.html
  4. 3
      package.json
  5. 115
      test/unit/responsive/components/dropdown-test.js
  6. 123
      ui/app/account-detail.js
  7. 91
      ui/app/accounts/account-list-item.js
  8. 164
      ui/app/accounts/index.js
  9. 4
      ui/app/actions.js
  10. 309
      ui/app/app.js
  11. 235
      ui/app/components/account-dropdowns.js
  12. 41
      ui/app/components/account-info-link.js
  13. 59
      ui/app/components/drop-menu-item.js
  14. 92
      ui/app/components/dropdown.js
  15. 7
      ui/app/components/editable-label.js
  16. 48
      ui/app/components/loading.js
  17. 1
      ui/app/components/network.js
  18. 10
      ui/app/components/shapeshift-form.js
  19. 1
      ui/app/components/tab-bar.js
  20. 42
      ui/app/components/token-list.js
  21. 18
      ui/app/components/transaction-list.js
  22. 48
      ui/app/conf-tx.js
  23. 53
      ui/app/css/index.css
  24. 5
      ui/app/css/lib.css
  25. 26
      ui/app/info.js
  26. 2
      ui/app/keychains/hd/create-vault-complete.js
  27. 7
      ui/app/keychains/hd/recover-seed/confirmation.js
  28. 1
      ui/lib/tx-helper.js

@ -2,6 +2,8 @@
## Current Master ## Current Master
- Replace account scren with an account drop-down menu.
- Replace confusing buttons with an new account-specific drop-down menu.
- Continuously update blacklist for known phishing sites in background. - Continuously update blacklist for known phishing sites in background.
- Automatically detect suspicious URLs too similar to common phishing targets, and blacklist them. - Automatically detect suspicious URLs too similar to common phishing targets, and blacklist them.

@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
<title>MetaMask Plugin</title>
</head>
<body>
<div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

@ -2,10 +2,11 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
<title>MetaMask Plugin</title> <title>MetaMask Plugin</title>
</head> </head>
<body> <body style="width:357px; height:500px;">
<div id="app-content"></div> <div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body> </body>
</html> </html>

@ -91,7 +91,7 @@
"inject-css": "^0.1.1", "inject-css": "^0.1.1",
"jazzicon": "^1.2.0", "jazzicon": "^1.2.0",
"loglevel": "^1.4.1", "loglevel": "^1.4.1",
"menu-droppo": "^1.1.0", "menu-droppo": "2.0.1",
"metamask-logo": "^2.1.2", "metamask-logo": "^2.1.2",
"mississippi": "^1.2.0", "mississippi": "^1.2.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
@ -109,7 +109,6 @@
"pumpify": "^1.3.4", "pumpify": "^1.3.4",
"qrcode-npm": "0.0.3", "qrcode-npm": "0.0.3",
"react": "^15.0.2", "react": "^15.0.2",
"react-addons-css-transition-group": "^15.0.2",
"react-dom": "^15.5.4", "react-dom": "^15.5.4",
"react-hyperscript": "^2.2.2", "react-hyperscript": "^2.2.2",
"react-markdown": "^2.3.0", "react-markdown": "^2.3.0",

@ -0,0 +1,115 @@
var assert = require('assert');
const additions = require('react-testutils-additions');
const h = require('react-hyperscript');
const ReactTestUtils = require('react-addons-test-utils');
const sinon = require('sinon');
const path = require('path');
const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'dropdown.js')).Dropdown;
const DropdownMenuItem = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'dropdown.js')).DropdownMenuItem;
describe('Dropdown components', function () {
let onClickOutside;
let closeMenu;
let onClick;
let dropdownComponentProps;
const renderer = ReactTestUtils.createRenderer()
beforeEach(function () {
onClickOutside = sinon.spy();
closeMenu = sinon.spy();
onClick = sinon.spy();
dropdownComponentProps = {
isOpen: true,
zIndex: 11,
onClickOutside,
style: {
position: 'absolute',
right: 0,
top: '36px',
},
innerStyle: {},
}
});
it('can render two items', function () {
const dropdownComponent = h(
Dropdown,
dropdownComponentProps,
[
h('style', `
.drop-menu-item:hover { background:rgb(235, 235, 235); }
.drop-menu-item i { margin: 11px; }
`),
h(DropdownMenuItem, {
closeMenu,
onClick,
}, 'Item 1'),
h(DropdownMenuItem, {
closeMenu,
onClick,
}, 'Item 2'),
]
)
const component = additions.renderIntoDocument(dropdownComponent);
renderer.render(dropdownComponent);
const items = additions.find(component, 'li');
assert.equal(items.length, 2);
});
it('closes when item clicked', function() {
const dropdownComponent = h(
Dropdown,
dropdownComponentProps,
[
h('style', `
.drop-menu-item:hover { background:rgb(235, 235, 235); }
.drop-menu-item i { margin: 11px; }
`),
h(DropdownMenuItem, {
closeMenu,
onClick,
}, 'Item 1'),
h(DropdownMenuItem, {
closeMenu,
onClick,
}, 'Item 2'),
]
)
const component = additions.renderIntoDocument(dropdownComponent);
renderer.render(dropdownComponent);
const items = additions.find(component, 'li');
const node = items[0];
ReactTestUtils.Simulate.click(node);
assert.equal(closeMenu.calledOnce, true);
});
it('invokes click handler when item clicked', function() {
const dropdownComponent = h(
Dropdown,
dropdownComponentProps,
[
h('style', `
.drop-menu-item:hover { background:rgb(235, 235, 235); }
.drop-menu-item i { margin: 11px; }
`),
h(DropdownMenuItem, {
closeMenu,
onClick,
}, 'Item 1'),
h(DropdownMenuItem, {
closeMenu,
onClick,
}, 'Item 2'),
]
)
const component = additions.renderIntoDocument(dropdownComponent);
renderer.render(dropdownComponent);
const items = additions.find(component, 'li');
const node = items[0];
ReactTestUtils.Simulate.click(node);
assert.equal(onClick.calledOnce, true);
});
});

@ -3,21 +3,17 @@ const extend = require('xtend')
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const CopyButton = require('./components/copyButton')
const AccountInfoLink = require('./components/account-info-link')
const actions = require('./actions') const actions = require('./actions')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const valuesFor = require('./util').valuesFor const valuesFor = require('./util').valuesFor
const Identicon = require('./components/identicon') const Identicon = require('./components/identicon')
const EthBalance = require('./components/eth-balance') const EthBalance = require('./components/eth-balance')
const TransactionList = require('./components/transaction-list') const TransactionList = require('./components/transaction-list')
const ExportAccountView = require('./components/account-export') const ExportAccountView = require('./components/account-export')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const EditableLabel = require('./components/editable-label') const EditableLabel = require('./components/editable-label')
const Tooltip = require('./components/tooltip')
const TabBar = require('./components/tab-bar') const TabBar = require('./components/tab-bar')
const TokenList = require('./components/token-list') const TokenList = require('./components/token-list')
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
module.exports = connect(mapStateToProps)(AccountDetailScreen) module.exports = connect(mapStateToProps)(AccountDetailScreen)
@ -54,12 +50,13 @@ AccountDetailScreen.prototype.render = function () {
return ( return (
h('.account-detail-section', [ h('.account-detail-section.full-flex-height', [
// identicon, label, balance, etc // identicon, label, balance, etc
h('.account-data-subsection', { h('.account-data-subsection', {
style: { style: {
margin: '0 20px', margin: '0 20px',
flex: '1 0 auto',
}, },
}, [ }, [
@ -84,6 +81,7 @@ AccountDetailScreen.prototype.render = function () {
style: { style: {
lineHeight: '10px', lineHeight: '10px',
marginLeft: '15px', marginLeft: '15px',
width: '100%',
}, },
}, [ }, [
h(EditableLabel, { h(EditableLabel, {
@ -98,7 +96,42 @@ AccountDetailScreen.prototype.render = function () {
// What is shown when not editing + edit text: // What is shown when not editing + edit text:
h('label.editing-label', [h('.edit-text', 'edit')]), h('label.editing-label', [h('.edit-text', 'edit')]),
h('h2.font-medium.color-forest', {name: 'edit'}, identity && identity.name), h(
'div',
{
style: {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
},
},
[
h(
'h2.font-medium.color-forest',
{
name: 'edit',
style: {
},
},
[
identity && identity.name,
]
),
h(
AccountDropdowns,
{
style: {
marginRight: '8px',
marginLeft: 'auto',
cursor: 'pointer',
},
selected,
network,
identities: props.identities,
},
),
]
),
]), ]),
h('.flex-row', { h('.flex-row', {
style: { style: {
@ -124,56 +157,6 @@ AccountDetailScreen.prototype.render = function () {
color: '#AEAEAE', color: '#AEAEAE',
}, },
}, checksumAddress), }, checksumAddress),
// copy and export
h('.flex-row', {
style: {
justifyContent: 'flex-end',
},
}, [
h(AccountInfoLink, { selected, network }),
h(CopyButton, {
value: checksumAddress,
}),
h(Tooltip, {
title: 'QR Code',
}, [
h('i.fa.fa-qrcode.pointer.pop-hover', {
onClick: () => props.dispatch(actions.showQrView(selected, identity ? identity.name : '')),
style: {
fontSize: '18px',
position: 'relative',
color: 'rgb(247, 134, 28)',
top: '5px',
marginLeft: '3px',
marginRight: '3px',
},
}),
]),
h(Tooltip, {
title: 'Export Private Key',
}, [
h('div', {
style: {
display: 'flex',
alignItems: 'center',
},
}, [
h('img.cursor-pointer.color-orange', {
src: 'images/key-32.png',
onClick: () => this.requestAccountExport(selected),
style: {
height: '19px',
},
}),
]),
]),
]),
]), ]),
// account ballence // account ballence
@ -197,14 +180,11 @@ AccountDetailScreen.prototype.render = function () {
}, },
}), }),
h('.flex-grow'),
h('button', { h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)), onClick: () => props.dispatch(actions.buyEthView(selected)),
style: { style: { marginRight: '10px' },
marginBottom: '20px',
marginRight: '8px',
position: 'absolute',
left: '219px',
},
}, 'BUY'), }, 'BUY'),
h('button', { h('button', {
@ -219,14 +199,7 @@ AccountDetailScreen.prototype.render = function () {
]), ]),
// subview (tx history, pk export confirm, buy eth warning) // subview (tx history, pk export confirm, buy eth warning)
h(ReactCSSTransitionGroup, { this.subview(),
className: 'css-transition-group',
transitionName: 'main',
transitionEnterTimeout: 300,
transitionLeaveTimeout: 300,
}, [
this.subview(),
]),
]) ])
) )
@ -254,7 +227,7 @@ AccountDetailScreen.prototype.subview = function () {
AccountDetailScreen.prototype.tabSections = function () { AccountDetailScreen.prototype.tabSections = function () {
const { currentAccountTab } = this.props const { currentAccountTab } = this.props
return h('section.tabSection', [ return h('section.tabSection.full-flex-height.grow-tenx', [
h(TabBar, { h(TabBar, {
tabs: [ tabs: [
@ -305,7 +278,3 @@ AccountDetailScreen.prototype.transactionList = function () {
}, },
}) })
} }
AccountDetailScreen.prototype.requestAccountExport = function () {
this.props.dispatch(actions.requestExportAccount())
}

@ -1,91 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const ethUtil = require('ethereumjs-util')
const EthBalance = require('../components/eth-balance')
const CopyButton = require('../components/copyButton')
const Identicon = require('../components/identicon')
module.exports = AccountListItem
inherits(AccountListItem, Component)
function AccountListItem () {
Component.call(this)
}
AccountListItem.prototype.render = function () {
const { identity, selectedAddress, accounts, onShowDetail,
conversionRate, currentCurrency } = this.props
const checksumAddress = identity && identity.address && ethUtil.toChecksumAddress(identity.address)
const isSelected = selectedAddress === identity.address
const account = accounts[identity.address]
const selectedClass = isSelected ? '.selected' : ''
return (
h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, {
key: `account-panel-${identity.address}`,
onClick: (event) => onShowDetail(identity.address, event),
}, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [
this.pendingOrNot(),
this.indicateIfLoose(),
h(Identicon, {
address: identity.address,
imageify: true,
}),
]),
// account address, balance
h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', {
style: {
width: '200px',
},
}, [
h('span', identity.name),
h('span.font-small', {
style: {
overflow: 'hidden',
textOverflow: 'ellipsis',
},
}, checksumAddress),
h(EthBalance, {
value: account && account.balance,
currentCurrency,
conversionRate,
style: {
lineHeight: '7px',
marginTop: '10px',
},
}),
]),
// copy button
h('.identity-copy.flex-column', {
style: {
margin: '0 20px',
},
}, [
h(CopyButton, {
value: checksumAddress,
}),
]),
])
)
}
AccountListItem.prototype.indicateIfLoose = function () {
try { // Sometimes keyrings aren't loaded yet:
const type = this.props.keyring.type
const isLoose = type !== 'HD Key Tree'
return isLoose ? h('.keyring-label', 'LOOSE') : null
} catch (e) { return }
}
AccountListItem.prototype.pendingOrNot = function () {
const pending = this.props.pending
if (pending.length === 0) return null
return h('.pending-dot', pending.length)
}

@ -1,164 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../actions')
const valuesFor = require('../util').valuesFor
const findDOMNode = require('react-dom').findDOMNode
const AccountListItem = require('./account-list-item')
module.exports = connect(mapStateToProps)(AccountsScreen)
function mapStateToProps (state) {
const pendingTxs = valuesFor(state.metamask.unapprovedTxs)
.filter(txMeta => txMeta.metamaskNetworkId === state.metamask.network)
const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs)
const pending = pendingTxs.concat(pendingMsgs)
return {
accounts: state.metamask.accounts,
identities: state.metamask.identities,
unapprovedTxs: state.metamask.unapprovedTxs,
selectedAddress: state.metamask.selectedAddress,
scrollToBottom: state.appState.scrollToBottom,
pending,
keyrings: state.metamask.keyrings,
conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency,
}
}
inherits(AccountsScreen, Component)
function AccountsScreen () {
Component.call(this)
}
AccountsScreen.prototype.render = function () {
const props = this.props
const { keyrings, conversionRate, currentCurrency } = props
const identityList = valuesFor(props.identities)
const unapprovedTxList = valuesFor(props.unapprovedTxs)
return (
h('.accounts-section.flex-grow', [
// subtitle and nav
h('.section-title.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: this.goHome.bind(this),
}),
h('h2.page-subtitle', 'Select Account'),
]),
h('hr.horizontal-line'),
// identity selection
h('section.identity-section', {
style: {
height: '418px',
overflowY: 'auto',
overflowX: 'hidden',
},
},
[
identityList.map((identity) => {
const pending = this.props.pending.filter((txOrMsg) => {
if ('txParams' in txOrMsg) {
return txOrMsg.txParams.from === identity.address
} else if ('msgParams' in txOrMsg) {
return txOrMsg.msgParams.from === identity.address
} else {
return false
}
})
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) ||
kr.accounts.includes(identity.address)
})
return h(AccountListItem, {
key: `acct-panel-${identity.address}`,
identity,
selectedAddress: this.props.selectedAddress,
conversionRate,
currentCurrency,
accounts: this.props.accounts,
onShowDetail: this.onShowDetail.bind(this),
pending,
keyring,
})
}),
h('hr.horizontal-line'),
h('div.footer.hover-white.pointer', {
key: 'reveal-account-bar',
onClick: () => {
this.addNewAccount()
},
style: {
display: 'flex',
height: '40px',
padding: '10px',
justifyContent: 'center',
alignItems: 'center',
},
}, [
h('i.fa.fa-plus.fa-lg', {key: ''}),
]),
h('hr.horizontal-line'),
]),
unapprovedTxList.length ? (
h('.unconftx-link.flex-row.flex-center', {
onClick: this.navigateToConfTx.bind(this),
}, [
h('span', 'Unconfirmed Txs'),
h('i.fa.fa-arrow-right.fa-lg'),
])
) : (
null
),
])
)
}
// If a new account was revealed, scroll to the bottom
AccountsScreen.prototype.componentDidUpdate = function () {
const scrollToBottom = this.props.scrollToBottom
if (scrollToBottom) {
var container = findDOMNode(this)
var scrollable = container.querySelector('.identity-section')
scrollable.scrollTop = scrollable.scrollHeight
}
}
AccountsScreen.prototype.navigateToConfTx = function () {
event.stopPropagation()
this.props.dispatch(actions.showConfTxPage())
}
AccountsScreen.prototype.onShowDetail = function (address, event) {
event.stopPropagation()
this.props.dispatch(actions.showAccountDetail(address))
}
AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.addNewAccount(0))
}
/* An optional view proposed in this design:
* https://consensys.quip.com/zZVrAysM5znY
AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.navigateToNewAccountScreen())
}
*/
AccountsScreen.prototype.goHome = function () {
this.props.dispatch(actions.goHome())
}

@ -706,7 +706,7 @@ function markAccountsFound () {
// //
// default rpc target refers to localhost:8545 in this instance. // default rpc target refers to localhost:8545 in this instance.
function setDefaultRpcTarget (rpcList) { function setDefaultRpcTarget () {
log.debug(`background.setDefaultRpcTarget`) log.debug(`background.setDefaultRpcTarget`)
return (dispatch) => { return (dispatch) => {
background.setDefaultRpc((err, result) => { background.setDefaultRpc((err, result) => {
@ -719,7 +719,7 @@ function setDefaultRpcTarget (rpcList) {
} }
function setRpcTarget (newRpc) { function setRpcTarget (newRpc) {
log.debug(`background.setRpcTarget`) log.debug(`background.setRpcTarget: ${newRpc}`)
return (dispatch) => { return (dispatch) => {
background.setCustomRpc(newRpc, (err, result) => { background.setCustomRpc(newRpc, (err, result) => {
if (err) { if (err) {

@ -3,14 +3,12 @@ const Component = require('react').Component
const connect = require('react-redux').connect const connect = require('react-redux').connect
const h = require('react-hyperscript') const h = require('react-hyperscript')
const actions = require('./actions') const actions = require('./actions')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
// init // init
const InitializeMenuScreen = require('./first-time/init-menu') const InitializeMenuScreen = require('./first-time/init-menu')
const NewKeyChainScreen = require('./new-keychain') const NewKeyChainScreen = require('./new-keychain')
// unlock // unlock
const UnlockScreen = require('./unlock') const UnlockScreen = require('./unlock')
// accounts // accounts
const AccountsScreen = require('./accounts')
const AccountDetailScreen = require('./account-detail') const AccountDetailScreen = require('./account-detail')
const SendTransactionScreen = require('./send') const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx') const ConfirmTxScreen = require('./conf-tx')
@ -24,10 +22,9 @@ const Import = require('./accounts/import')
const InfoScreen = require('./info') const InfoScreen = require('./info')
const Loading = require('./components/loading') const Loading = require('./components/loading')
const SandwichExpando = require('sandwich-expando') const SandwichExpando = require('sandwich-expando')
const MenuDroppo = require('menu-droppo') const Dropdown = require('./components/dropdown').Dropdown
const DropMenuItem = require('./components/drop-menu-item') const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkIndicator = require('./components/network') const NetworkIndicator = require('./components/network')
const Tooltip = require('./components/tooltip')
const BuyView = require('./components/buy-button-subview') const BuyView = require('./components/buy-button-subview')
const QrView = require('./components/qr-code') const QrView = require('./components/qr-code')
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete') const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
@ -69,16 +66,16 @@ App.prototype.render = function () {
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ? const loadMessage = loadingMessage || isLoadingNetwork ?
`Connecting to ${this.getNetworkName()}` : null `Connecting to ${this.getNetworkName()}` : null
log.debug('Main ui render function') log.debug('Main ui render function')
return ( return (
h('.flex-column.flex-grow.full-height', { h('.flex-column.full-height', {
style: { style: {
// Windows was showing a vertical scroll bar: // Windows was showing a vertical scroll bar:
overflow: 'hidden', overflow: 'hidden',
position: 'relative', position: 'relative',
alignItems: 'center',
}, },
}, [ }, [
@ -93,20 +90,12 @@ App.prototype.render = function () {
}), }),
// panel content // panel content
h('.app-primary.flex-grow' + (transForward ? '.from-right' : '.from-left'), { h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
style: { style: {
height: '380px', maxWidth: '850px',
width: '360px',
}, },
}, [ }, [
h(ReactCSSTransitionGroup, { this.renderPrimary(),
className: 'css-transition-group',
transitionName: 'main',
transitionEnterTimeout: 300,
transitionLeaveTimeout: 300,
}, [
this.renderPrimary(),
]),
]), ]),
]) ])
) )
@ -123,14 +112,16 @@ App.prototype.renderAppBar = function () {
return ( return (
h('div', [ h('.full-width', {
height: '38px',
}, [
h('.app-header.flex-row.flex-space-between', { h('.app-header.flex-row.flex-space-between', {
style: { style: {
alignItems: 'center', alignItems: 'center',
visibility: props.isUnlocked ? 'visible' : 'none', visibility: props.isUnlocked ? 'visible' : 'none',
background: props.isUnlocked ? 'white' : 'none', background: props.isUnlocked ? 'white' : 'none',
height: '36px', height: '38px',
position: 'relative', position: 'relative',
zIndex: 12, zIndex: 12,
}, },
@ -178,21 +169,6 @@ App.prototype.renderAppBar = function () {
}, },
}, [ }, [
// small accounts nav
props.isUnlocked && h(Tooltip, { title: 'Switch Accounts' }, [
h('img.cursor-pointer.color-orange', {
src: 'images/switch_acc.svg',
style: {
width: '23.5px',
marginRight: '8px',
},
onClick: (event) => {
event.stopPropagation()
this.props.dispatch(actions.showAccountsPage())
},
}),
]),
// hamburger // hamburger
props.isUnlocked && h(SandwichExpando, { props.isUnlocked && h(SandwichExpando, {
width: 16, width: 16,
@ -214,84 +190,122 @@ App.prototype.renderAppBar = function () {
App.prototype.renderNetworkDropdown = function () { App.prototype.renderNetworkDropdown = function () {
const props = this.props const props = this.props
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
const rpcList = props.frequentRpcList const rpcList = props.frequentRpcList
const state = this.state || {} const state = this.state || {}
const isOpen = state.isNetworkMenuOpen const isOpen = state.isNetworkMenuOpen
return h(MenuDroppo, { return h(Dropdown, {
isOpen, isOpen,
onClickOutside: (event) => { onClickOutside: (event) => {
this.setState({ isNetworkMenuOpen: !isOpen }) 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, zIndex: 11,
style: { style: {
position: 'absolute', position: 'absolute',
left: 0, left: '2px',
top: '36px', top: '36px',
}, },
innerStyle: { innerStyle: {
background: 'white', padding: '2px 16px 2px 0px',
boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
}, },
}, [ // DROP MENU ITEMS }, [
h('style', `
.drop-menu-item:hover { background:rgb(235, 235, 235); } h(
.drop-menu-item i { margin: 11px; } DropdownMenuItem,
`), {
key: 'main',
h(DropMenuItem, { closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
label: 'Main Ethereum Network', onClick: () => props.dispatch(actions.setProviderType('mainnet')),
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), },
action: () => props.dispatch(actions.setProviderType('mainnet')), [
icon: h('.menu-icon.diamond'), h('.menu-icon.diamond'),
activeNetworkRender: props.network, 'Main Ethereum Network',
provider: props.provider, providerType === 'mainnet' ? h('.check', '✓') : null,
}), ]
),
h(DropMenuItem, {
label: 'Ropsten Test Network', h(
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), DropdownMenuItem,
action: () => props.dispatch(actions.setProviderType('ropsten')), {
icon: h('.menu-icon.red-dot'), key: 'ropsten',
activeNetworkRender: props.network, closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
provider: props.provider, onClick: () => props.dispatch(actions.setProviderType('ropsten')),
}), },
[
h(DropMenuItem, { h('.menu-icon.red-dot'),
label: 'Kovan Test Network', 'Ropsten Test Network',
closeMenu: () => this.setState({ isNetworkMenuOpen: false}), providerType === 'ropsten' ? h('.check', '✓') : null,
action: () => props.dispatch(actions.setProviderType('kovan')), ]
icon: h('.menu-icon.hollow-diamond'), ),
activeNetworkRender: props.network,
provider: props.provider, h(
}), DropdownMenuItem,
{
h(DropMenuItem, { key: 'kovan',
label: 'Rinkeby Test Network', closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
closeMenu: () => this.setState({ isNetworkMenuOpen: false}), onClick: () => props.dispatch(actions.setProviderType('kovan')),
action: () => props.dispatch(actions.setProviderType('rinkeby')), },
icon: h('.menu-icon.golden-square'), [
activeNetworkRender: props.network, h('.menu-icon.hollow-diamond'),
provider: props.provider, 'Kovan Test Network',
}), providerType === 'kovan' ? h('.check', '✓') : null,
]
h(DropMenuItem, { ),
label: 'Localhost 8545',
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), h(
action: () => props.dispatch(actions.setDefaultRpcTarget(rpcList)), DropdownMenuItem,
icon: h('i.fa.fa-question-circle.fa-lg'), {
activeNetworkRender: props.provider.rpcTarget, key: 'rinkeby',
}), closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
},
[
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.setDefaultRpcTarget()),
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
'Localhost 8545',
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
]
),
this.renderCustomOption(props.provider), this.renderCustomOption(props.provider),
this.renderCommonRpc(rpcList, props.provider), this.renderCommonRpc(rpcList, props.provider),
h(DropMenuItem, { h(
label: 'Custom RPC', DropdownMenuItem,
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), {
action: () => this.props.dispatch(actions.showConfigPage()), closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
icon: h('i.fa.fa-question-circle.fa-lg'), onClick: () => this.props.dispatch(actions.showConfigPage()),
}), },
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
'Custom RPC',
activeNetwork === 'custom' ? h('.check', '✓') : null,
]
),
]) ])
} }
@ -300,54 +314,37 @@ App.prototype.renderDropdown = function () {
const state = this.state || {} const state = this.state || {}
const isOpen = state.isMainMenuOpen const isOpen = state.isMainMenuOpen
return h(MenuDroppo, { return h(Dropdown, {
isOpen: isOpen, isOpen: isOpen,
zIndex: 11, zIndex: 11,
onClickOutside: (event) => { onClickOutside: (event) => {
this.setState({ isMainMenuOpen: !isOpen }) const { classList } = event.target
const isNotToggleElement = !classList.contains('sandwich-expando')
if (isNotToggleElement) {
this.setState({ isMainMenuOpen: false })
}
}, },
style: { style: {
position: 'absolute', position: 'absolute',
right: 0, right: '2px',
top: '36px', top: '38px',
},
innerStyle: {
background: 'white',
boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
}, },
}, [ // DROP MENU ITEMS innerStyle: {},
h('style', ` }, [
.drop-menu-item:hover { background:rgb(235, 235, 235); } h(DropdownMenuItem, {
.drop-menu-item i { margin: 11px; }
`),
h(DropMenuItem, {
label: 'Settings',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.showConfigPage()), onClick: () => { this.props.dispatch(actions.showConfigPage()) },
icon: h('i.fa.fa-gear.fa-lg'), }, 'Settings'),
}),
h(DropMenuItem, { h(DropdownMenuItem, {
label: 'Import Account',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.showImportPage()), onClick: () => { this.props.dispatch(actions.lockMetamask()) },
icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'), }, 'Lock'),
}),
h(DropMenuItem, { h(DropdownMenuItem, {
label: 'Lock',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.lockMetamask()), onClick: () => { this.props.dispatch(actions.showInfoPage()) },
icon: h('i.fa.fa-lock.fa-lg'), }, 'Info/Help'),
}),
h(DropMenuItem, {
label: 'Info/Help',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.showInfoPage()),
icon: h('i.fa.fa-question.fa-lg'),
}),
]) ])
} }
@ -433,10 +430,6 @@ App.prototype.renderPrimary = function () {
// show current view // show current view
switch (props.currentView.name) { switch (props.currentView.name) {
case 'accounts':
log.debug('rendering accounts screen')
return h(AccountsScreen, {key: 'accounts'})
case 'accountDetail': case 'accountDetail':
log.debug('rendering account detail screen') log.debug('rendering account detail screen')
return h(AccountDetailScreen, {key: 'account-detail'}) return h(AccountDetailScreen, {key: 'account-detail'})
@ -525,6 +518,8 @@ App.prototype.toggleMetamaskActive = function () {
App.prototype.renderCustomOption = function (provider) { App.prototype.renderCustomOption = function (provider) {
const { rpcTarget, type } = provider const { rpcTarget, type } = provider
const props = this.props
if (type !== 'rpc') return null if (type !== 'rpc') return null
// Concatenate long URLs // Concatenate long URLs
@ -539,13 +534,19 @@ App.prototype.renderCustomOption = function (provider) {
return null return null
default: default:
return h(DropMenuItem, { return h(
label, DropdownMenuItem,
key: rpcTarget, {
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), key: rpcTarget,
icon: h('i.fa.fa-question-circle.fa-lg'), onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
activeNetworkRender: 'custom', closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
}) },
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
label,
h('.check', '✓'),
]
)
} }
} }
@ -571,21 +572,27 @@ App.prototype.getNetworkName = function () {
} }
App.prototype.renderCommonRpc = function (rpcList, provider) { App.prototype.renderCommonRpc = function (rpcList, provider) {
const { rpcTarget } = provider
const props = this.props const props = this.props
const rpcTarget = provider.rpcTarget
return rpcList.map((rpc) => { return rpcList.map((rpc) => {
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
return null return null
} else { } else {
return h(DropMenuItem, {
label: rpc, return h(
key: rpc, DropdownMenuItem,
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), {
action: () => props.dispatch(actions.setRpcTarget(rpc)), key: `common${rpc}`,
icon: h('i.fa.fa-question-circle.fa-lg'), closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
activeNetworkRender: rpc, onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
}) },
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
rpc,
rpcTarget === rpc ? h('.check', '✓') : null,
]
)
} }
}) })
} }

@ -0,0 +1,235 @@
const Component = require('react').Component
const PropTypes = require('react').PropTypes
const h = require('react-hyperscript')
const actions = require('../actions')
const genAccountLink = require('../../lib/account-link.js')
const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('./identicon')
const ethUtil = require('ethereumjs-util')
const copyToClipboard = require('copy-to-clipboard')
class AccountDropdowns extends Component {
constructor (props) {
super(props)
this.state = {
accountSelectorActive: false,
optionsMenuActive: false,
}
this.accountSelectorToggleClassName = 'fa-angle-down'
this.optionsMenuToggleClassName = 'fa-ellipsis-h'
}
renderAccounts () {
const { identities, selected } = this.props
return Object.keys(identities).map((key) => {
const identity = identities[key]
const isSelected = identity.address === selected
return h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
this.props.actions.showAccountDetail(identity.address)
},
},
[
h(
Identicon,
{
address: identity.address,
diameter: 16,
},
),
h('span', { style: { marginLeft: '10px' } }, identity.name || ''),
h('span', { style: { marginLeft: '10px' } }, isSelected ? h('.check', '✓') : null),
]
)
})
}
renderAccountSelector () {
const { actions } = this.props
const { accountSelectorActive } = this.state
return h(
Dropdown,
{
style: {
marginLeft: '-125px',
minWidth: '180px',
overflowY: 'auto',
maxHeight: '300px',
},
isOpen: accountSelectorActive,
onClickOutside: (event) => {
const { classList } = event.target
const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName)
if (accountSelectorActive && isNotToggleElement) {
this.setState({ accountSelectorActive: false })
}
},
},
[
...this.renderAccounts(),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => actions.addNewAccount(),
},
[
h(
Identicon,
{
diameter: 16,
},
),
h('span', { style: { marginLeft: '10px' } }, 'Create Account'),
],
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => actions.showImportPage(),
},
[
h(
Identicon,
{
diameter: 16,
},
),
h('span', { style: { marginLeft: '10px' } }, 'Import Account'),
]
),
]
)
}
renderAccountOptions () {
const { actions } = this.props
const { optionsMenuActive } = this.state
return h(
Dropdown,
{
style: {
marginLeft: '-162px',
minWidth: '180px',
},
isOpen: optionsMenuActive,
onClickOutside: () => {
const { classList } = event.target
const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName)
if (optionsMenuActive && isNotToggleElement) {
this.setState({ optionsMenuActive: false })
}
},
},
[
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
const { selected, network } = this.props
const url = genAccountLink(selected, network)
global.platform.openWindow({ url })
},
},
'View account on Etherscan',
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
const { selected } = this.props
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
copyToClipboard(checkSumAddress)
},
},
'Copy Address to clipboard',
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
actions.requestAccountExport()
},
},
'Export Private Key',
),
]
)
}
render () {
const { style } = this.props
const { optionsMenuActive, accountSelectorActive } = this.state
return h(
'span',
{
style: style,
},
[
h(
'i.fa.fa-angle-down',
{
style: {},
onClick: (event) => {
event.stopPropagation()
this.setState({
accountSelectorActive: !accountSelectorActive,
optionsMenuActive: false,
})
},
},
this.renderAccountSelector(),
),
h(
'i.fa.fa-ellipsis-h',
{
style: { 'marginLeft': '10px'},
onClick: (event) => {
event.stopPropagation()
this.setState({
accountSelectorActive: false,
optionsMenuActive: !optionsMenuActive,
})
},
},
this.renderAccountOptions()
),
]
)
}
}
AccountDropdowns.propTypes = {
identities: PropTypes.objectOf(PropTypes.object),
selected: PropTypes.string,
}
const mapDispatchToProps = (dispatch) => {
return {
actions: {
showConfigPage: () => dispatch(actions.showConfigPage()),
requestAccountExport: () => dispatch(actions.requestExportAccount()),
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
addNewAccount: () => dispatch(actions.addNewAccount()),
showImportPage: () => dispatch(actions.showImportPage()),
},
}
}
module.exports = {
AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns),
}

@ -1,41 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Tooltip = require('./tooltip')
const genAccountLink = require('../../lib/account-link')
module.exports = AccountInfoLink
inherits(AccountInfoLink, Component)
function AccountInfoLink () {
Component.call(this)
}
AccountInfoLink.prototype.render = function () {
const { selected, network } = this.props
const title = 'View account on Etherscan'
const url = genAccountLink(selected, network)
if (!url) {
return null
}
return h('.account-info-link', {
style: {
display: 'flex',
alignItems: 'center',
},
}, [
h(Tooltip, {
title,
}, [
h('i.fa.fa-info-circle.cursor-pointer.color-orange', {
style: {
margin: '5px',
},
onClick () { global.platform.openWindow({ url }) },
}),
]),
])
}

@ -1,59 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
module.exports = DropMenuItem
inherits(DropMenuItem, Component)
function DropMenuItem () {
Component.call(this)
}
DropMenuItem.prototype.render = function () {
return h('li.drop-menu-item', {
onClick: () => {
this.props.closeMenu()
this.props.action()
},
style: {
listStyle: 'none',
padding: '6px 16px 6px 5px',
fontFamily: 'Montserrat Regular',
color: 'rgb(125, 128, 130)',
cursor: 'pointer',
display: 'flex',
justifyContent: 'flex-start',
},
}, [
this.props.icon,
this.props.label,
this.activeNetworkRender(),
])
}
DropMenuItem.prototype.activeNetworkRender = function () {
const activeNetwork = this.props.activeNetworkRender
const { provider } = this.props
const providerType = provider ? provider.type : null
if (activeNetwork === undefined) return
switch (this.props.label) {
case 'Main Ethereum Network':
if (providerType === 'mainnet') return h('.check', '✓')
break
case 'Ropsten Test Network':
if (providerType === 'ropsten') return h('.check', '✓')
break
case 'Kovan Test Network':
if (providerType === 'kovan') return h('.check', '✓')
break
case 'Rinkeby Test Network':
if (providerType === 'rinkeby') return h('.check', '✓')
break
case 'Localhost 8545':
if (activeNetwork === 'http://localhost:8545') return h('.check', '✓')
break
default:
if (activeNetwork === 'custom') return h('.check', '✓')
}
}

@ -0,0 +1,92 @@
const Component = require('react').Component
const PropTypes = require('react').PropTypes
const h = require('react-hyperscript')
const MenuDroppo = require('menu-droppo')
const extend = require('xtend')
const noop = () => {}
class Dropdown extends Component {
render () {
const { isOpen, onClickOutside, style, innerStyle, children } = this.props
const innerStyleDefaults = extend({
borderRadius: '4px',
padding: '8px 16px',
background: 'rgba(0, 0, 0, 0.8)',
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
}, innerStyle)
return h(
MenuDroppo,
{
isOpen,
zIndex: 11,
onClickOutside,
style,
innerStyle: innerStyleDefaults,
},
[
h(
'style',
`
li.dropdown-menu-item:hover { color:rgb(225, 225, 225); }
li.dropdown-menu-item { color: rgb(185, 185, 185); }
`
),
...children,
]
)
}
}
Dropdown.defaultProps = {
isOpen: false,
onClick: noop,
}
Dropdown.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
children: PropTypes.node,
style: PropTypes.object.isRequired,
}
class DropdownMenuItem extends Component {
render () {
const { onClick, closeMenu, children } = this.props
return h(
'li.dropdown-menu-item',
{
onClick: () => {
onClick()
closeMenu()
},
style: {
listStyle: 'none',
padding: '8px 0px 8px 0px',
fontSize: '12px',
fontStyle: 'normal',
fontFamily: 'Montserrat Regular',
cursor: 'pointer',
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
},
},
children
)
}
}
DropdownMenuItem.propTypes = {
closeMenu: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
children: PropTypes.node,
}
module.exports = {
Dropdown,
DropdownMenuItem,
}

@ -30,7 +30,12 @@ EditableLabel.prototype.render = function () {
} else { } else {
return h('div.name-label', { return h('div.name-label', {
onClick: (event) => { onClick: (event) => {
this.setState({ isEditingLabel: true }) const nameAttribute = event.target.getAttribute('name')
// checks for class to handle smaller CTA above the account name
const classAttribute = event.target.getAttribute('class')
if (nameAttribute === 'edit' || classAttribute === 'edit-text') {
this.setState({ isEditingLabel: true })
}
}, },
}, this.props.children) }, this.props.children)
} }

@ -1,7 +1,6 @@
const inherits = require('util').inherits const inherits = require('util').inherits
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
inherits(LoadingIndicator, Component) inherits(LoadingIndicator, Component)
@ -15,35 +14,28 @@ LoadingIndicator.prototype.render = function () {
const { isLoading, loadingMessage } = this.props const { isLoading, loadingMessage } = this.props
return ( return (
h(ReactCSSTransitionGroup, { isLoading ? h('.full-flex-height', {
className: 'css-transition-group', style: {
transitionName: 'loader', left: '0px',
transitionEnterTimeout: 150, zIndex: 10,
transitionLeaveTimeout: 150, position: 'absolute',
flexDirection: 'column',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
width: '100%',
background: 'rgba(255, 255, 255, 0.8)',
},
}, [ }, [
h('img', {
src: 'images/loading.svg',
}),
isLoading ? h('div', { h('br'),
style: {
zIndex: 10, showMessageIfAny(loadingMessage),
position: 'absolute', ]) : null
flexDirection: 'column',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
width: '100%',
background: 'rgba(255, 255, 255, 0.8)',
},
}, [
h('img', {
src: 'images/loading.svg',
}),
h('br'),
showMessageIfAny(loadingMessage),
]) : null,
])
) )
} }

@ -39,7 +39,6 @@ Network.prototype.render = function () {
}), }),
h('i.fa.fa-sort-desc'), h('i.fa.fa-sort-desc'),
]) ])
} else if (providerName === 'mainnet') { } else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network' hoverText = 'Main Ethereum Network'
iconName = 'ethereum-network' iconName = 'ethereum-network'

@ -2,7 +2,6 @@ const PersistentForm = require('../../lib/persistent-form')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const connect = require('react-redux').connect const connect = require('react-redux').connect
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const actions = require('../actions') const actions = require('../actions')
const Qr = require('./qr-code') const Qr = require('./qr-code')
const isValidAddress = require('../util').isValidAddress const isValidAddress = require('../util').isValidAddress
@ -24,14 +23,7 @@ function ShapeshiftForm () {
} }
ShapeshiftForm.prototype.render = function () { ShapeshiftForm.prototype.render = function () {
return h(ReactCSSTransitionGroup, { return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain()
className: 'css-transition-group',
transitionName: 'main',
transitionEnterTimeout: 300,
transitionLeaveTimeout: 300,
}, [
this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain(),
])
} }
ShapeshiftForm.prototype.renderMain = function () { ShapeshiftForm.prototype.renderMain = function () {

@ -21,6 +21,7 @@ TabBar.prototype.render = function () {
background: '#EBEBEB', background: '#EBEBEB',
color: '#AEAEAE', color: '#AEAEAE',
paddingTop: '4px', paddingTop: '4px',
minHeight: '30px',
}, },
}, tabs.map((tab) => { }, tabs.map((tab) => {
const { key, content } = tab const { key, content } = tab

@ -47,10 +47,11 @@ TokenList.prototype.render = function () {
return h(TokenCell, tokenData) return h(TokenCell, tokenData)
}) })
return h('div', [ return h('.full-flex-height', [
h('ol', { this.renderTokenStatusBar(),
h('ol.full-flex-height.flex-column', {
style: { style: {
height: '260px',
overflowY: 'auto', overflowY: 'auto',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@ -63,6 +64,7 @@ TokenList.prototype.render = function () {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
padding: 10px; padding: 10px;
min-height: 50px;
} }
li.token-cell > h3 { li.token-cell > h3 {
@ -76,17 +78,37 @@ TokenList.prototype.render = function () {
`), `),
...tokenViews, ...tokenViews,
tokenViews.length ? null : this.message('No Tokens Found.'), h('.flex-grow'),
]), ]),
this.addTokenButtonElement(),
]) ])
} }
TokenList.prototype.addTokenButtonElement = function () { TokenList.prototype.renderTokenStatusBar = function () {
return h('div', [ const { tokens } = this.state
h('div.footer.hover-white.pointer', {
let msg
if (tokens.length === 1) {
msg = `You own 1 token`
} else if (tokens.length === 1) {
msg = `You own ${tokens.length} tokens`
} else {
msg = `No tokens found`
}
return h('div', {
style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
minHeight: '70px',
padding: '10px',
},
}, [
h('span', msg),
h('button', {
key: 'reveal-account-bar', key: 'reveal-account-bar',
onClick: () => { onClick: (event) => {
event.preventDefault()
this.props.addToken() this.props.addToken()
}, },
style: { style: {
@ -97,7 +119,7 @@ TokenList.prototype.addTokenButtonElement = function () {
alignItems: 'center', alignItems: 'center',
}, },
}, [ }, [
h('i.fa.fa-plus.fa-lg'), 'ADD TOKEN',
]), ]),
]) ])
} }

@ -24,7 +24,11 @@ TransactionList.prototype.render = function () {
return ( return (
h('section.transaction-list', [ h('section.transaction-list.full-flex-height', {
style: {
justifyContent: 'center',
},
}, [
h('style', ` h('style', `
.transaction-list .transaction-list-item:not(:last-of-type) { .transaction-list .transaction-list-item:not(:last-of-type) {
@ -39,7 +43,7 @@ TransactionList.prototype.render = function () {
h('.tx-list', { h('.tx-list', {
style: { style: {
overflowY: 'auto', overflowY: 'auto',
height: '300px', height: '100%',
padding: '0 20px', padding: '0 20px',
textAlign: 'center', textAlign: 'center',
}, },
@ -64,13 +68,17 @@ TransactionList.prototype.render = function () {
}, },
}) })
}) })
: h('.flex-center', { : h('.flex-center.full-flex-height', {
style: { style: {
flexDirection: 'column', flexDirection: 'column',
height: '100%', justifyContent: 'center',
}, },
}, [ }, [
'No transaction history.', h('p', {
style: {
marginTop: '50px',
},
}, 'No transaction history.'),
]), ]),
]), ]),
]) ])

@ -1,6 +1,5 @@
const inherits = require('util').inherits const inherits = require('util').inherits
const Component = require('react').Component const Component = require('react').Component
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('./actions') const actions = require('./actions')
@ -92,34 +91,25 @@ ConfirmTxScreen.prototype.render = function () {
warningIfExists(props.warning), warningIfExists(props.warning),
h(ReactCSSTransitionGroup, { currentTxView({
className: 'css-transition-group', // Properties
transitionName: 'main', txData: txData,
transitionEnterTimeout: 300, key: txData.id,
transitionLeaveTimeout: 300, selectedAddress: props.selectedAddress,
}, [ accounts: props.accounts,
identities: props.identities,
currentTxView({ conversionRate,
// Properties currentCurrency,
txData: txData, blockGasLimit,
key: txData.id, // Actions
selectedAddress: props.selectedAddress, buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
accounts: props.accounts, sendTransaction: this.sendTransaction.bind(this),
identities: props.identities, cancelTransaction: this.cancelTransaction.bind(this, txData),
conversionRate, signMessage: this.signMessage.bind(this, txData),
currentCurrency, signPersonalMessage: this.signPersonalMessage.bind(this, txData),
blockGasLimit, cancelMessage: this.cancelMessage.bind(this, txData),
// Actions cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), }),
sendTransaction: this.sendTransaction.bind(this),
cancelTransaction: this.cancelTransaction.bind(this, txData),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
}),
]),
]) ])
) )
} }

@ -19,17 +19,54 @@ html, body {
font-weight: 300; font-weight: 300;
line-height: 1.4em; line-height: 1.4em;
background: #F7F7F7; background: #F7F7F7;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
html {
min-height: 500px;
}
.app-root {
overflow: hidden;
position: relative
}
.app-primary {
display: flex;
} }
input:focus, textarea:focus { input:focus, textarea:focus {
outline: none; outline: none;
} }
.full-size {
height: 100%;
width: 100%;
}
.full-width {
width: 100%;
}
.full-height {
height: 100%;
}
.full-flex-height {
display: flex;
flex: 1 1 auto;
flex-direction: column;
}
#app-content { #app-content {
overflow-x: hidden; overflow-x: hidden;
min-width: 357px; min-width: 357px;
width: 360px; height: 100%;
height: 500px; display: flex;
flex-direction: column;
} }
button, input[type="submit"] { button, input[type="submit"] {
@ -130,10 +167,6 @@ h2.page-subtitle {
margin: 12px; margin: 12px;
} }
.app-primary {
}
.app-footer { .app-footer {
padding-bottom: 10px; padding-bottom: 10px;
align-items: center; align-items: center;
@ -403,8 +436,16 @@ input.large-input {
/* account detail screen */ /* account detail screen */
.account-detail-section { .account-detail-section {
display: flex;
flex-wrap: wrap;
overflow-y: auto;
flex-direction: inherit;
}
.grow-tenx {
flex-grow: 10;
} }
.name-label{ .name-label{
} }

@ -232,6 +232,10 @@ hr.horizontal-line {
align-items: center; align-items: center;
} }
.tabSection {
min-width: 350px;
}
.menu-icon { .menu-icon {
display: inline-block; display: inline-block;
height: 9px; height: 9px;
@ -266,3 +270,4 @@ hr.horizontal-line {
margin-top: 20px; margin-top: 20px;
color: red; color: red;
} }

@ -20,7 +20,11 @@ InfoScreen.prototype.render = function () {
const version = global.platform.getVersion() const version = global.platform.getVersion()
return ( return (
h('.flex-column.flex-grow', [ h('.flex-column.flex-grow', {
style: {
maxWidth: '400px',
},
}, [
// subtitle and nav // subtitle and nav
h('.section-title.flex-row.flex-center', [ h('.section-title.flex-row.flex-center', [
@ -103,12 +107,7 @@ InfoScreen.prototype.render = function () {
target: '_blank', target: '_blank',
}, 'Visit our Support Center'), }, 'Visit our Support Center'),
]), ]),
h('div.fa.fa-github', [
h('a.info', {
href: 'https://github.com/MetaMask/metamask-extension/issues/new',
target: '_blank',
}, 'Found a bug? Report it!'),
]),
h('div', [ h('div', [
h('a', { h('a', {
href: 'https://metamask.io/', href: 'https://metamask.io/',
@ -126,6 +125,7 @@ InfoScreen.prototype.render = function () {
h('div.info', 'Visit our web site'), h('div.info', 'Visit our web site'),
]), ]),
]), ]),
h('div.fa.fa-slack', [ h('div.fa.fa-slack', [
h('a.info', { h('a.info', {
href: 'http://slack.metamask.io', href: 'http://slack.metamask.io',
@ -133,11 +133,13 @@ InfoScreen.prototype.render = function () {
}, 'Join the conversation on Slack'), }, 'Join the conversation on Slack'),
]), ]),
h('div.fa.fa-twitter', [ h('div', [
h('a.info', { h('.fa.fa-twitter', [
href: 'https://twitter.com/metamask_io', h('a.info', {
target: '_blank', href: 'https://twitter.com/metamask_io',
}, 'Follow us on Twitter'), target: '_blank',
}, 'Follow us on Twitter'),
]),
]), ]),
h('div.fa.fa-envelope', [ h('div.fa.fa-envelope', [

@ -47,8 +47,6 @@ CreateVaultCompleteScreen.prototype.render = function () {
h('div', { h('div', {
style: { style: {
width: '360px',
height: '78px',
fontSize: '1em', fontSize: '1em',
marginTop: '10px', marginTop: '10px',
textAlign: 'center', textAlign: 'center',

@ -23,7 +23,9 @@ RevealSeedConfirmation.prototype.render = function () {
return ( return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [ h('.initialize-screen.flex-column.flex-center.flex-grow', {
style: { maxWidth: '420px' },
}, [
h('h3.flex-center.text-transform-uppercase', { h('h3.flex-center.text-transform-uppercase', {
style: { style: {
@ -61,7 +63,7 @@ RevealSeedConfirmation.prototype.render = function () {
}, },
}), }),
h('.flex-row.flex-space-between', { h('.flex-row.flex-start', {
style: { style: {
marginTop: 30, marginTop: 30,
width: '50%', width: '50%',
@ -74,6 +76,7 @@ RevealSeedConfirmation.prototype.render = function () {
// submit // submit
h('button.primary', { h('button.primary', {
style: { marginLeft: '10px' },
onClick: this.revealSeedWords.bind(this), onClick: this.revealSeedWords.bind(this),
}, 'OK'), }, 'OK'),

@ -18,4 +18,3 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network)
return allValues return allValues
} }

Loading…
Cancel
Save