aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkumavis <aaron@kumavis.me>2017-08-08 01:52:08 +0800
committerkumavis <aaron@kumavis.me>2017-08-08 01:52:08 +0800
commit78aa957e5ab5bd3e7f7fb4628c82de8ae312750f (patch)
treeffe2ece4164b9d6c70196dd3ab608244bdf95281
parent82ba2c95ee98487dd465e5aabd45f18d46e89727 (diff)
parent57abc58d623b66a091987a944d8c45737f4feabe (diff)
downloadtangerine-wallet-browser-78aa957e5ab5bd3e7f7fb4628c82de8ae312750f.tar.gz
tangerine-wallet-browser-78aa957e5ab5bd3e7f7fb4628c82de8ae312750f.tar.zst
tangerine-wallet-browser-78aa957e5ab5bd3e7f7fb4628c82de8ae312750f.zip
Merge branch 'master' of github.com:MetaMask/metamask-extension into greenkeeper/initial
-rw-r--r--CHANGELOG.md28
-rw-r--r--app/home.html12
-rw-r--r--app/manifest.json2
-rw-r--r--app/popup.html5
-rw-r--r--app/scripts/controllers/blacklist.js5
-rw-r--r--app/scripts/lib/nodeify.js3
-rw-r--r--package.json5
-rw-r--r--test/integration/index.js34
-rw-r--r--test/integration/lib/first-time.js8
-rw-r--r--test/unit/actions/tx_test.js10
-rw-r--r--test/unit/nodeify-test.js11
-rw-r--r--test/unit/responsive/components/dropdown-test.js115
-rw-r--r--ui/app/account-detail.js125
-rw-r--r--ui/app/accounts/account-list-item.js91
-rw-r--r--ui/app/accounts/index.js164
-rw-r--r--ui/app/actions.js13
-rw-r--r--ui/app/app.js359
-rw-r--r--ui/app/components/account-dropdowns.js289
-rw-r--r--ui/app/components/account-export.js2
-rw-r--r--ui/app/components/account-info-link.js41
-rw-r--r--ui/app/components/drop-menu-item.js59
-rw-r--r--ui/app/components/dropdown.js94
-rw-r--r--ui/app/components/editable-label.js7
-rw-r--r--ui/app/components/loading.js48
-rw-r--r--ui/app/components/menu-droppo.js130
-rw-r--r--ui/app/components/network.js1
-rw-r--r--ui/app/components/notice.js6
-rw-r--r--ui/app/components/shapeshift-form.js10
-rw-r--r--ui/app/components/tab-bar.js1
-rw-r--r--ui/app/components/token-list.js42
-rw-r--r--ui/app/components/transaction-list.js18
-rw-r--r--ui/app/conf-tx.js48
-rw-r--r--ui/app/css/index.css53
-rw-r--r--ui/app/css/lib.css41
-rw-r--r--ui/app/info.js26
-rw-r--r--ui/app/keychains/hd/create-vault-complete.js2
-rw-r--r--ui/app/keychains/hd/recover-seed/confirmation.js7
-rw-r--r--ui/app/unlock.js6
-rw-r--r--ui/lib/tx-helper.js1
39 files changed, 1178 insertions, 744 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66c95a0c3..0f0151e6d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,20 @@
## Current Master
+- Replace account screen with an account drop-down menu.
+- Replace confusing buttons with a new account-specific drop-down menu.
+
+## 3.9.5 2017-8-04
+
+- Improved phishing detection configuration update rate
+
+## 3.9.4 2017-8-03
+
+- Fixed bug that prevented transactions from being rejected.
+
+## 3.9.3 2017-8-03
+
+- Add support for EGO ujo token
- Continuously update blacklist for known phishing sites in background.
- Automatically detect suspicious URLs too similar to common phishing targets, and blacklist them.
@@ -75,7 +89,7 @@
## 3.7.8 2017-6-12
-- Add a `ethereum:` prefix to the QR code address
+- Add an `ethereum:` prefix to the QR code address
- The default network on installation is now MainNet
- Fix currency API URL from cryptonator.
- Update gasLimit params with every new block seen.
@@ -231,7 +245,7 @@
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)
- Fix unapproved messages not being included in extension badge.
-- Fix rendering bug where the Confirm transaction view would lets you approve transactions when the account has insufficient balance.
+- Fix rendering bug where the Confirm transaction view would let you approve transactions when the account has insufficient balance.
## 3.1.2 2017-1-24
@@ -254,8 +268,8 @@
## 3.0.0 2017-1-16
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
-- Fix Bug where you see a empty transaction flash by on the confirm transaction view.
-- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed.
+- Fix Bug where you see an empty transaction flash by on the confirm transaction view.
+- Create visible difference in transaction history between an approved but not yet included in a block transaction and a transaction who has been confirmed.
- Fix memory leak in RPC Cache
- Override RPC commands eth_syncing and web3_clientVersion
- Remove certain non-essential permissions from certain builds.
@@ -310,7 +324,7 @@
- Fix bug where gas estimate would sometimes be very high.
- Increased our gas estimate from 100k gas to 20% of estimate.
-- Fix github link on info page to point at current repository.
+- Fix GitHub link on info page to point at current repository.
## 2.13.6 2016-10-26
@@ -386,7 +400,7 @@ popup notification opens up.
- Block negative values from transactions.
- Fixed a memory leak.
- MetaMask logo now renders as super lightweight SVG, improving compatibility and performance.
-- Now showing loading indication during vault unlocking, to clarify behavior for users who are experience slow unlocks.
+- Now showing loading indication during vault unlocking, to clarify behavior for users who are experiencing slow unlocks.
- Now only initially creates one wallet when restoring a vault, to reduce some users' confusion.
## 2.10.2 2016-09-02
@@ -418,7 +432,7 @@ popup notification opens up.
- Added info link on account screen that visits Etherscan.
- Fixed bug where a message signing request would be lost if the vault was locked.
- Added shortcut to open MetaMask (Ctrl+Alt+M or Cmd+Opt/Alt+M)
-- Prevent API calls in tests.
+- Prevent API calls in tests.
- Fixed bug where sign message confirmation would sometimes render blank.
## 2.9.0 2016-08-22
diff --git a/app/home.html b/app/home.html
new file mode 100644
index 000000000..cfb4b00a0
--- /dev/null
+++ b/app/home.html
@@ -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>
diff --git a/app/manifest.json b/app/manifest.json
index 1eaf6f26a..3b9194eb9 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
- "version": "3.9.2",
+ "version": "3.9.5",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
diff --git a/app/popup.html b/app/popup.html
index 6d85a9811..d09b09315 100644
--- a/app/popup.html
+++ b/app/popup.html
@@ -2,10 +2,11 @@
<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>
+ <body style="width:357px; height:500px;">
<div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
index 7e01fa386..dd671943f 100644
--- a/app/scripts/controllers/blacklist.js
+++ b/app/scripts/controllers/blacklist.js
@@ -4,8 +4,8 @@ const PhishingDetector = require('eth-phishing-detect/src/detector')
// compute phishing lists
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
-// every ten minutes
-const POLLING_INTERVAL = 10 * 60 * 1000
+// every four minutes
+const POLLING_INTERVAL = 4 * 60 * 1000
class BlacklistController {
@@ -41,6 +41,7 @@ class BlacklistController {
scheduleUpdates () {
if (this._phishingUpdateIntervalRef) return
+ this.updatePhishingList()
this._phishingUpdateIntervalRef = setInterval(() => {
this.updatePhishingList()
}, POLLING_INTERVAL)
diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js
index 299bfe624..832d6c6d3 100644
--- a/app/scripts/lib/nodeify.js
+++ b/app/scripts/lib/nodeify.js
@@ -1,9 +1,10 @@
const promiseToCallback = require('promise-to-callback')
-module.exports = function(fn, context) {
+module.exports = function nodeify (fn, context) {
return function(){
const args = [].slice.call(arguments)
const callback = args.pop()
+ if (typeof callback !== 'function') throw new Error('callback is not a function')
promiseToCallback(fn.apply(context, args))(callback)
}
}
diff --git a/package.json b/package.json
index 2743c4e52..11e3d3128 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
"eth-bin-to-ops": "^1.0.1",
"eth-contract-metadata": "^1.1.4",
"eth-hd-keyring": "^1.1.1",
- "eth-phishing-detect": "^1.0.2",
+ "eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.2.2",
"eth-simple-keyring": "^1.1.1",
@@ -92,7 +92,6 @@
"inject-css": "^0.1.1",
"jazzicon": "^1.2.0",
"loglevel": "^1.4.1",
- "menu-droppo": "^1.1.0",
"metamask-logo": "^2.1.2",
"mississippi": "^1.2.0",
"mkdirp": "^0.5.1",
@@ -110,7 +109,7 @@
"pumpify": "^1.3.4",
"qrcode-npm": "0.0.3",
"react": "^15.0.2",
- "react-addons-css-transition-group": "^15.0.2",
+ "react-addons-css-transition-group": "^15.6.0",
"react-dom": "^15.5.4",
"react-hyperscript": "^3.0.0",
"react-markdown": "^2.3.0",
diff --git a/test/integration/index.js b/test/integration/index.js
index 85f91d92b..e089fc39b 100644
--- a/test/integration/index.js
+++ b/test/integration/index.js
@@ -1,23 +1,19 @@
-var fs = require('fs')
-var path = require('path')
-var browserify = require('browserify')
-var tests = fs.readdirSync(path.join(__dirname, 'lib'))
-var bundlePath = path.join(__dirname, 'bundle.js')
+const fs = require('fs')
+const path = require('path')
+const browserify = require('browserify')
+const tests = fs.readdirSync(path.join(__dirname, 'lib'))
+const bundlePath = path.join(__dirname, 'bundle.js')
-var b = browserify()
+const b = browserify()
-// Remove old bundle
-try {
- fs.unlinkSync(bundlePath)
+const writeStream = fs.createWriteStream(bundlePath)
- var writeStream = fs.createWriteStream(bundlePath)
-
- tests.forEach(function (fileName) {
- b.add(path.join(__dirname, 'lib', fileName))
- })
-
- b.bundle().pipe(writeStream)
-} catch (e) {
- console.error('Integration build failure', e)
-}
+tests.forEach(function (fileName) {
+ b.add(path.join(__dirname, 'lib', fileName))
+})
+b.bundle()
+.pipe(writeStream)
+.on('error', (err) => {
+ throw err
+})
diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js
index 6c8cedbac..0e4b802da 100644
--- a/test/integration/lib/first-time.js
+++ b/test/integration/lib/first-time.js
@@ -90,7 +90,13 @@ QUnit.test('render init screen', function (assert) {
return wait()
}).then(function (){
- var qrButton = app.find('.fa.fa-qrcode')[0]
+ var qrButton = app.find('.fa.fa-ellipsis-h')[0] // open account settings dropdown
+ qrButton.click()
+
+ return wait(1000)
+ }).then(function (){
+
+ var qrButton = app.find('.dropdown-menu-item')[1] // qr code item
qrButton.click()
return wait(1000)
diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js
index 0ea1bfdc7..67c72e9a5 100644
--- a/test/unit/actions/tx_test.js
+++ b/test/unit/actions/tx_test.js
@@ -45,13 +45,15 @@ describe('tx confirmation screen', function () {
before(function (done) {
actions._setBackgroundConnection({
approveTransaction (txId, cb) { cb('An error!') },
- cancelTransaction (txId) { /* noop */ },
+ cancelTransaction (txId, cb) { cb() },
clearSeedWordCache (cb) { cb() },
})
- const action = actions.cancelTx({value: firstTxId})
- result = reducers(initialState, action)
- done()
+ actions.cancelTx({value: firstTxId})((action) => {
+ result = reducers(initialState, action)
+ done()
+ })
+
})
it('should transition to the account detail view', function () {
diff --git a/test/unit/nodeify-test.js b/test/unit/nodeify-test.js
index 06241334d..537dae605 100644
--- a/test/unit/nodeify-test.js
+++ b/test/unit/nodeify-test.js
@@ -17,4 +17,15 @@ describe('nodeify', function () {
done()
})
})
+
+ it('should throw if the last argument is not a function', function (done) {
+ const nodified = nodeify(obj.promiseFunc, obj)
+ try {
+ nodified('baz')
+ done(new Error('should have thrown if the last argument is not a function'))
+ } catch (err) {
+ assert.equal(err.message, 'callback is not a function')
+ done()
+ }
+ })
})
diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js
new file mode 100644
index 000000000..3ad2c390e
--- /dev/null
+++ b/test/unit/responsive/components/dropdown-test.js
@@ -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);
+ });
+});
diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js
index bed05a7fb..22a883096 100644
--- a/ui/app/account-detail.js
+++ b/ui/app/account-detail.js
@@ -3,21 +3,17 @@ const extend = require('xtend')
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
-const CopyButton = require('./components/copyButton')
-const AccountInfoLink = require('./components/account-info-link')
const actions = require('./actions')
-const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const valuesFor = require('./util').valuesFor
-
const Identicon = require('./components/identicon')
const EthBalance = require('./components/eth-balance')
const TransactionList = require('./components/transaction-list')
const ExportAccountView = require('./components/account-export')
const ethUtil = require('ethereumjs-util')
const EditableLabel = require('./components/editable-label')
-const Tooltip = require('./components/tooltip')
const TabBar = require('./components/tab-bar')
const TokenList = require('./components/token-list')
+const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
module.exports = connect(mapStateToProps)(AccountDetailScreen)
@@ -54,12 +50,13 @@ AccountDetailScreen.prototype.render = function () {
return (
- h('.account-detail-section', [
+ h('.account-detail-section.full-flex-height', [
- // identicon, label, balance, etc
+ // identicon, label, balance, etc
h('.account-data-subsection', {
style: {
margin: '0 20px',
+ flex: '1 0 auto',
},
}, [
@@ -84,6 +81,7 @@ AccountDetailScreen.prototype.render = function () {
style: {
lineHeight: '10px',
marginLeft: '15px',
+ width: '100%',
},
}, [
h(EditableLabel, {
@@ -98,7 +96,43 @@ AccountDetailScreen.prototype.render = function () {
// What is shown when not editing + edit text:
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,
+ enableAccountOptions: true,
+ },
+ ),
+ ]
+ ),
]),
h('.flex-row', {
style: {
@@ -119,61 +153,10 @@ AccountDetailScreen.prototype.render = function () {
fontSize: '13px',
fontFamily: 'Montserrat Light',
textRendering: 'geometricPrecision',
- marginTop: '10px',
marginBottom: '15px',
color: '#AEAEAE',
},
}, 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
@@ -197,14 +180,11 @@ AccountDetailScreen.prototype.render = function () {
},
}),
+ h('.flex-grow'),
+
h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)),
- style: {
- marginBottom: '20px',
- marginRight: '8px',
- position: 'absolute',
- left: '219px',
- },
+ style: { marginRight: '10px' },
}, 'BUY'),
h('button', {
@@ -219,14 +199,7 @@ AccountDetailScreen.prototype.render = function () {
]),
// subview (tx history, pk export confirm, buy eth warning)
- h(ReactCSSTransitionGroup, {
- className: 'css-transition-group',
- transitionName: 'main',
- transitionEnterTimeout: 300,
- transitionLeaveTimeout: 300,
- }, [
- this.subview(),
- ]),
+ this.subview(),
])
)
@@ -254,7 +227,7 @@ AccountDetailScreen.prototype.subview = function () {
AccountDetailScreen.prototype.tabSections = function () {
const { currentAccountTab } = this.props
- return h('section.tabSection', [
+ return h('section.tabSection.full-flex-height.grow-tenx', [
h(TabBar, {
tabs: [
@@ -305,7 +278,3 @@ AccountDetailScreen.prototype.transactionList = function () {
},
})
}
-
-AccountDetailScreen.prototype.requestAccountExport = function () {
- this.props.dispatch(actions.requestExportAccount())
-}
diff --git a/ui/app/accounts/account-list-item.js b/ui/app/accounts/account-list-item.js
deleted file mode 100644
index 10a0b6cc7..000000000
--- a/ui/app/accounts/account-list-item.js
+++ /dev/null
@@ -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)
-}
diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js
deleted file mode 100644
index ac2615cd7..000000000
--- a/ui/app/accounts/index.js
+++ /dev/null
@@ -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())
-}
diff --git a/ui/app/actions.js b/ui/app/actions.js
index d99291e46..eafd04b4c 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -462,9 +462,12 @@ function cancelPersonalMsg (msgData) {
}
function cancelTx (txData) {
- log.debug(`background.cancelTransaction`)
- background.cancelTransaction(txData.id)
- return actions.completedTx(txData.id)
+ return (dispatch) => {
+ log.debug(`background.cancelTransaction`)
+ background.cancelTransaction(txData.id, () => {
+ dispatch(actions.completedTx(txData.id))
+ })
+ }
}
//
@@ -706,7 +709,7 @@ function markAccountsFound () {
//
// default rpc target refers to localhost:8545 in this instance.
-function setDefaultRpcTarget (rpcList) {
+function setDefaultRpcTarget () {
log.debug(`background.setDefaultRpcTarget`)
return (dispatch) => {
background.setDefaultRpc((err, result) => {
@@ -719,7 +722,7 @@ function setDefaultRpcTarget (rpcList) {
}
function setRpcTarget (newRpc) {
- log.debug(`background.setRpcTarget`)
+ log.debug(`background.setRpcTarget: ${newRpc}`)
return (dispatch) => {
background.setCustomRpc(newRpc, (err, result) => {
if (err) {
diff --git a/ui/app/app.js b/ui/app/app.js
index 1a63002e1..4565bdd37 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -3,14 +3,12 @@ const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('./actions')
-const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
// init
const InitializeMenuScreen = require('./first-time/init-menu')
const NewKeyChainScreen = require('./new-keychain')
// unlock
const UnlockScreen = require('./unlock')
// accounts
-const AccountsScreen = require('./accounts')
const AccountDetailScreen = require('./account-detail')
const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx')
@@ -24,15 +22,15 @@ const Import = require('./accounts/import')
const InfoScreen = require('./info')
const Loading = require('./components/loading')
const SandwichExpando = require('sandwich-expando')
-const MenuDroppo = require('menu-droppo')
-const DropMenuItem = require('./components/drop-menu-item')
+const Dropdown = require('./components/dropdown').Dropdown
+const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkIndicator = require('./components/network')
-const Tooltip = require('./components/tooltip')
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)
@@ -40,6 +38,13 @@ inherits(App, Component)
function App () { Component.call(this) }
function mapStateToProps (state) {
+ const {
+ identities,
+ accounts,
+ address,
+ } = state.metamask
+ const selected = address || Object.keys(accounts)[0]
+
return {
// state from plugin
isLoading: state.appState.isLoading,
@@ -60,6 +65,10 @@ function mapStateToProps (state) {
lastUnreadNotice: state.metamask.lastUnreadNotice,
lostAccounts: state.metamask.lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
+
+ // state needed to get account dropdown temporarily rendering from app bar
+ identities,
+ selected,
}
}
@@ -69,16 +78,16 @@ App.prototype.render = function () {
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
`Connecting to ${this.getNetworkName()}` : null
-
log.debug('Main ui render function')
return (
- h('.flex-column.flex-grow.full-height', {
+ h('.flex-column.full-height', {
style: {
// Windows was showing a vertical scroll bar:
overflow: 'hidden',
position: 'relative',
+ alignItems: 'center',
},
}, [
@@ -93,20 +102,12 @@ App.prototype.render = function () {
}),
// panel content
- h('.app-primary.flex-grow' + (transForward ? '.from-right' : '.from-left'), {
+ h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
style: {
- height: '380px',
- width: '360px',
+ width: '100%',
},
}, [
- h(ReactCSSTransitionGroup, {
- className: 'css-transition-group',
- transitionName: 'main',
- transitionEnterTimeout: 300,
- transitionLeaveTimeout: 300,
- }, [
- this.renderPrimary(),
- ]),
+ this.renderPrimary(),
]),
])
)
@@ -123,14 +124,16 @@ App.prototype.renderAppBar = function () {
return (
- h('div', [
+ 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: '36px',
+ height: '38px',
position: 'relative',
zIndex: 12,
},
@@ -178,32 +181,26 @@ 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())
- },
- }),
- ]),
+ props.isUnlocked && h(AccountDropdowns, {
+ style: {},
+ enableAccountsSelector: true,
+ identities: this.props.identities,
+ selected: this.props.selected,
+ network: this.props.network,
+ }, []),
// hamburger
props.isUnlocked && h(SandwichExpando, {
+ className: 'sandwich-expando',
width: 16,
barHeight: 2,
padding: 0,
isOpen: state.isMainMenuOpen,
color: 'rgb(247,146,30)',
- onClick: (event) => {
- event.preventDefault()
- event.stopPropagation()
- this.setState({ isMainMenuOpen: !state.isMainMenuOpen })
+ onClick: () => {
+ this.setState({
+ isMainMenuOpen: !state.isMainMenuOpen,
+ })
},
}),
]),
@@ -214,84 +211,141 @@ App.prototype.renderAppBar = function () {
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(MenuDroppo, {
+ return h(Dropdown, {
+ useCssTransition: true,
isOpen,
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,
style: {
position: 'absolute',
- left: 0,
+ left: '2px',
top: '36px',
},
innerStyle: {
- background: 'white',
- boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
+ padding: '2px 16px 2px 0px',
},
- }, [ // DROP MENU ITEMS
- h('style', `
- .drop-menu-item:hover { background:rgb(235, 235, 235); }
- .drop-menu-item i { margin: 11px; }
- `),
-
- h(DropMenuItem, {
- label: 'Main Ethereum Network',
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- action: () => props.dispatch(actions.setProviderType('mainnet')),
- icon: h('.menu-icon.diamond'),
- activeNetworkRender: props.network,
- provider: props.provider,
- }),
-
- h(DropMenuItem, {
- label: 'Ropsten Test Network',
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- action: () => props.dispatch(actions.setProviderType('ropsten')),
- icon: h('.menu-icon.red-dot'),
- activeNetworkRender: props.network,
- provider: props.provider,
- }),
-
- h(DropMenuItem, {
- label: 'Kovan Test Network',
- closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
- action: () => props.dispatch(actions.setProviderType('kovan')),
- icon: h('.menu-icon.hollow-diamond'),
- activeNetworkRender: props.network,
- provider: props.provider,
- }),
-
- h(DropMenuItem, {
- label: 'Rinkeby Test Network',
- closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
- action: () => props.dispatch(actions.setProviderType('rinkeby')),
- icon: h('.menu-icon.golden-square'),
- activeNetworkRender: props.network,
- provider: props.provider,
- }),
-
- h(DropMenuItem, {
- label: 'Localhost 8545',
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- action: () => props.dispatch(actions.setDefaultRpcTarget(rpcList)),
- icon: h('i.fa.fa-question-circle.fa-lg'),
- activeNetworkRender: props.provider.rpcTarget,
- }),
+ }, [
+
+ 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.setDefaultRpcTarget()),
+ 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(DropMenuItem, {
- label: 'Custom RPC',
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- action: () => this.props.dispatch(actions.showConfigPage()),
- icon: h('i.fa.fa-question-circle.fa-lg'),
- }),
+ 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,
+ ]
+ ),
])
}
@@ -300,54 +354,42 @@ App.prototype.renderDropdown = function () {
const state = this.state || {}
const isOpen = state.isMainMenuOpen
- return h(MenuDroppo, {
+ return h(Dropdown, {
+ useCssTransition: true,
isOpen: isOpen,
zIndex: 11,
onClickOutside: (event) => {
- this.setState({ isMainMenuOpen: !isOpen })
+ 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: 0,
- top: '36px',
- },
- innerStyle: {
- background: 'white',
- boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
+ right: '2px',
+ top: '38px',
},
- }, [ // DROP MENU ITEMS
- h('style', `
- .drop-menu-item:hover { background:rgb(235, 235, 235); }
- .drop-menu-item i { margin: 11px; }
- `),
-
- h(DropMenuItem, {
- label: 'Settings',
- closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
- action: () => this.props.dispatch(actions.showConfigPage()),
- icon: h('i.fa.fa-gear.fa-lg'),
- }),
-
- h(DropMenuItem, {
- label: 'Import Account',
+ innerStyle: {},
+ }, [
+ h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
- action: () => this.props.dispatch(actions.showImportPage()),
- icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'),
- }),
+ onClick: () => { this.props.dispatch(actions.showConfigPage()) },
+ }, 'Settings'),
- h(DropMenuItem, {
- label: 'Lock',
+ h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
- action: () => this.props.dispatch(actions.lockMetamask()),
- icon: h('i.fa.fa-lock.fa-lg'),
- }),
+ onClick: () => { this.props.dispatch(actions.lockMetamask()) },
+ }, 'Lock'),
- h(DropMenuItem, {
- label: 'Info/Help',
+ h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
- action: () => this.props.dispatch(actions.showInfoPage()),
- icon: h('i.fa.fa-question.fa-lg'),
- }),
+ onClick: () => { this.props.dispatch(actions.showInfoPage()) },
+ }, 'Info/Help'),
])
}
@@ -433,10 +475,6 @@ App.prototype.renderPrimary = function () {
// show current view
switch (props.currentView.name) {
- case 'accounts':
- log.debug('rendering accounts screen')
- return h(AccountsScreen, {key: 'accounts'})
-
case 'accountDetail':
log.debug('rendering account detail screen')
return h(AccountDetailScreen, {key: 'account-detail'})
@@ -525,6 +563,8 @@ App.prototype.toggleMetamaskActive = function () {
App.prototype.renderCustomOption = function (provider) {
const { rpcTarget, type } = provider
+ const props = this.props
+
if (type !== 'rpc') return null
// Concatenate long URLs
@@ -539,13 +579,19 @@ App.prototype.renderCustomOption = function (provider) {
return null
default:
- return h(DropMenuItem, {
- label,
- key: rpcTarget,
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- icon: h('i.fa.fa-question-circle.fa-lg'),
- activeNetworkRender: 'custom',
- })
+ 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', '✓'),
+ ]
+ )
}
}
@@ -571,21 +617,26 @@ App.prototype.getNetworkName = function () {
}
App.prototype.renderCommonRpc = function (rpcList, provider) {
- const { rpcTarget } = 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(DropMenuItem, {
- label: rpc,
- key: rpc,
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- action: () => props.dispatch(actions.setRpcTarget(rpc)),
- icon: h('i.fa.fa-question-circle.fa-lg'),
- activeNetworkRender: rpc,
- })
+ 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,
+ ]
+ )
}
})
}
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js
new file mode 100644
index 000000000..b23600e9b
--- /dev/null
+++ b/ui/app/components/account-dropdowns.js
@@ -0,0 +1,289 @@
+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 = 'accounts-selector'
+ this.optionsMenuToggleClassName = 'fa-ellipsis-h'
+ }
+
+ renderAccounts () {
+ const { identities, selected } = this.props
+
+ return Object.keys(identities).map((key, index) => {
+ const identity = identities[key]
+ const isSelected = identity.address === selected
+
+ return h(
+ DropdownMenuItem,
+ {
+ closeMenu: () => {},
+ onClick: () => {
+ this.props.actions.showAccountDetail(identity.address)
+ },
+ style: {
+ marginTop: index === 0 ? '5px' : '',
+ fontSize: '24px',
+ },
+ },
+ [
+ h(
+ Identicon,
+ {
+ address: identity.address,
+ diameter: 32,
+ style: {
+ marginLeft: '10px',
+ },
+ },
+ ),
+ h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, identity.name || ''),
+ h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
+ ]
+ )
+ })
+ }
+
+ renderAccountSelector () {
+ const { actions } = this.props
+ const { accountSelectorActive } = this.state
+
+ return h(
+ Dropdown,
+ {
+ useCssTransition: true, // Hardcoded because account selector is temporarily in app-header
+ style: {
+ marginLeft: '-238px',
+ marginTop: '38px',
+ minWidth: '180px',
+ overflowY: 'auto',
+ maxHeight: '300px',
+ width: '300px',
+ },
+ innerStyle: {
+ padding: '8px 25px',
+ },
+ 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,
+ {
+ style: {
+ marginLeft: '10px',
+ },
+ diameter: 32,
+ },
+ ),
+ h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, 'Create Account'),
+ ],
+ ),
+ h(
+ DropdownMenuItem,
+ {
+ closeMenu: () => {},
+ onClick: () => actions.showImportPage(),
+ },
+ [
+ h(
+ Identicon,
+ {
+ style: {
+ marginLeft: '10px',
+ },
+ diameter: 32,
+ },
+ ),
+ h('span', {
+ style: {
+ marginLeft: '20px',
+ fontSize: '24px',
+ marginBottom: '5px',
+ },
+ }, 'Import Account'),
+ ]
+ ),
+ ]
+ )
+ }
+
+ renderAccountOptions () {
+ const { actions } = this.props
+ const { optionsMenuActive } = this.state
+
+ return h(
+ Dropdown,
+ {
+ style: {
+ marginLeft: '-215px',
+ 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, identities } = this.props
+ var identity = identities[selected]
+ actions.showQrView(selected, identity ? identity.name : '')
+ },
+ },
+ 'Show QR Code',
+ ),
+ 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, enableAccountsSelector, enableAccountOptions } = this.props
+ const { optionsMenuActive, accountSelectorActive } = this.state
+
+ return h(
+ 'span',
+ {
+ style: style,
+ },
+ [
+ enableAccountsSelector && h(
+ // 'i.fa.fa-angle-down',
+ 'div.cursor-pointer.color-orange.accounts-selector',
+ {
+ style: {
+ // fontSize: '1.8em',
+ background: 'url(images/switch_acc.svg) white center center no-repeat',
+ height: '25px',
+ width: '25px',
+ transform: 'scale(0.75)',
+ marginRight: '3px',
+ },
+ onClick: (event) => {
+ event.stopPropagation()
+ this.setState({
+ accountSelectorActive: !accountSelectorActive,
+ optionsMenuActive: false,
+ })
+ },
+ },
+ this.renderAccountSelector(),
+ ),
+ enableAccountOptions && h(
+ 'i.fa.fa-ellipsis-h',
+ {
+ style: {
+ marginRight: '0.5em',
+ fontSize: '1.8em',
+ },
+ onClick: (event) => {
+ event.stopPropagation()
+ this.setState({
+ accountSelectorActive: false,
+ optionsMenuActive: !optionsMenuActive,
+ })
+ },
+ },
+ this.renderAccountOptions()
+ ),
+ ]
+ )
+ }
+}
+
+AccountDropdowns.defaultProps = {
+ enableAccountsSelector: false,
+ enableAccountOptions: false,
+}
+
+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()),
+ showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
+ },
+ }
+}
+
+module.exports = {
+ AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns),
+}
diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js
index 394d878f7..330f73805 100644
--- a/ui/app/components/account-export.js
+++ b/ui/app/components/account-export.js
@@ -100,7 +100,7 @@ ExportAccountView.prototype.render = function () {
textOverflow: 'ellipsis',
overflow: 'hidden',
webkitUserSelect: 'text',
- width: '100%',
+ maxWidth: '275px',
},
onClick: function (event) {
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
diff --git a/ui/app/components/account-info-link.js b/ui/app/components/account-info-link.js
deleted file mode 100644
index 6526ab502..000000000
--- a/ui/app/components/account-info-link.js
+++ /dev/null
@@ -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 }) },
- }),
- ]),
- ])
-}
diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js
deleted file mode 100644
index e42948209..000000000
--- a/ui/app/components/drop-menu-item.js
+++ /dev/null
@@ -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', '✓')
- }
-}
diff --git a/ui/app/components/dropdown.js b/ui/app/components/dropdown.js
new file mode 100644
index 000000000..34c7149ee
--- /dev/null
+++ b/ui/app/components/dropdown.js
@@ -0,0 +1,94 @@
+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, useCssTransition } = 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,
+ {
+ useCssTransition,
+ 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,
+ useCssTransition: false,
+}
+
+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, style } = this.props
+
+ return h(
+ 'li.dropdown-menu-item',
+ {
+ onClick: () => {
+ onClick()
+ closeMenu()
+ },
+ style: Object.assign({
+ listStyle: 'none',
+ padding: '8px 0px 8px 0px',
+ fontSize: '18px',
+ fontStyle: 'normal',
+ fontFamily: 'Montserrat Regular',
+ cursor: 'pointer',
+ display: 'flex',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ }, style),
+ },
+ children
+ )
+ }
+}
+
+DropdownMenuItem.propTypes = {
+ closeMenu: PropTypes.func.isRequired,
+ onClick: PropTypes.func.isRequired,
+ children: PropTypes.node,
+}
+
+module.exports = {
+ Dropdown,
+ DropdownMenuItem,
+}
diff --git a/ui/app/components/editable-label.js b/ui/app/components/editable-label.js
index 41936f5e0..167be7eaf 100644
--- a/ui/app/components/editable-label.js
+++ b/ui/app/components/editable-label.js
@@ -30,7 +30,12 @@ EditableLabel.prototype.render = function () {
} else {
return h('div.name-label', {
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)
}
diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js
index 87d6f5d20..163792584 100644
--- a/ui/app/components/loading.js
+++ b/ui/app/components/loading.js
@@ -1,7 +1,6 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
-const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
inherits(LoadingIndicator, Component)
@@ -15,35 +14,28 @@ LoadingIndicator.prototype.render = function () {
const { isLoading, loadingMessage } = this.props
return (
- h(ReactCSSTransitionGroup, {
- className: 'css-transition-group',
- transitionName: 'loader',
- transitionEnterTimeout: 150,
- transitionLeaveTimeout: 150,
+ isLoading ? h('.full-flex-height', {
+ style: {
+ left: '0px',
+ zIndex: 10,
+ 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', {
- style: {
- zIndex: 10,
- 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',
- }),
-
- h('br'),
-
- showMessageIfAny(loadingMessage),
- ]) : null,
- ])
+ h('br'),
+
+ showMessageIfAny(loadingMessage),
+ ]) : null
)
}
diff --git a/ui/app/components/menu-droppo.js b/ui/app/components/menu-droppo.js
new file mode 100644
index 000000000..66ab18954
--- /dev/null
+++ b/ui/app/components/menu-droppo.js
@@ -0,0 +1,130 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const findDOMNode = require('react-dom').findDOMNode
+const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
+
+module.exports = MenuDroppoComponent
+
+
+inherits(MenuDroppoComponent, Component)
+function MenuDroppoComponent () {
+ Component.call(this)
+}
+
+MenuDroppoComponent.prototype.render = function () {
+ const speed = this.props.speed || '300ms'
+ const useCssTransition = this.props.useCssTransition
+ const zIndex = ('zIndex' in this.props) ? this.props.zIndex : 0
+
+ this.manageListeners()
+
+ let style = this.props.style || {}
+ if (!('position' in style)) {
+ style.position = 'fixed'
+ }
+ style.zIndex = zIndex
+
+ return (
+ h('.menu-droppo-container', {
+ style,
+ }, [
+ h('style', `
+ .menu-droppo-enter {
+ transition: transform ${speed} ease-in-out;
+ transform: translateY(-200%);
+ }
+
+ .menu-droppo-enter.menu-droppo-enter-active {
+ transition: transform ${speed} ease-in-out;
+ transform: translateY(0%);
+ }
+
+ .menu-droppo-leave {
+ transition: transform ${speed} ease-in-out;
+ transform: translateY(0%);
+ }
+
+ .menu-droppo-leave.menu-droppo-leave-active {
+ transition: transform ${speed} ease-in-out;
+ transform: translateY(-200%);
+ }
+ `),
+
+ useCssTransition
+ ? h(ReactCSSTransitionGroup, {
+ className: 'css-transition-group',
+ transitionName: 'menu-droppo',
+ transitionEnterTimeout: parseInt(speed),
+ transitionLeaveTimeout: parseInt(speed),
+ }, this.renderPrimary())
+ : this.renderPrimary(),
+ ])
+ )
+}
+
+MenuDroppoComponent.prototype.renderPrimary = function () {
+ const isOpen = this.props.isOpen
+ if (!isOpen) {
+ return null
+ }
+
+ const innerStyle = this.props.innerStyle || {}
+
+ return (
+ h('.menu-droppo', {
+ key: 'menu-droppo-drawer',
+ style: innerStyle,
+ },
+ [ this.props.children ])
+ )
+}
+
+MenuDroppoComponent.prototype.manageListeners = function () {
+ const isOpen = this.props.isOpen
+ const onClickOutside = this.props.onClickOutside
+
+ if (isOpen) {
+ this.outsideClickHandler = onClickOutside
+ } else if (isOpen) {
+ this.outsideClickHandler = null
+ }
+}
+
+MenuDroppoComponent.prototype.componentDidMount = function () {
+ if (this && document.body) {
+ this.globalClickHandler = this.globalClickOccurred.bind(this)
+ document.body.addEventListener('click', this.globalClickHandler)
+ var container = findDOMNode(this)
+ this.container = container
+ }
+}
+
+MenuDroppoComponent.prototype.componentWillUnmount = function () {
+ if (this && document.body) {
+ document.body.removeEventListener('click', this.globalClickHandler)
+ }
+}
+
+MenuDroppoComponent.prototype.globalClickOccurred = function (event) {
+ const target = event.target
+ const container = findDOMNode(this)
+
+ if (target !== container &&
+ !isDescendant(this.container, event.target) &&
+ this.outsideClickHandler) {
+ this.outsideClickHandler(event)
+ }
+}
+
+function isDescendant (parent, child) {
+ var node = child.parentNode
+ while (node !== null) {
+ if (node === parent) {
+ return true
+ }
+ node = node.parentNode
+ }
+
+ return false
+}
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index d5d3e18cd..698a0bbb9 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -39,7 +39,6 @@ Network.prototype.render = function () {
}),
h('i.fa.fa-sort-desc'),
])
-
} else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network'
iconName = 'ethereum-network'
diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js
index d9f0067cd..c26505193 100644
--- a/ui/app/components/notice.js
+++ b/ui/app/components/notice.js
@@ -19,7 +19,11 @@ Notice.prototype.render = function () {
const disabled = state.disclaimerDisabled
return (
- h('.flex-column.flex-center.flex-grow', [
+ h('.flex-column.flex-center.flex-grow', {
+ style: {
+ width: '100%',
+ },
+ }, [
h('h3.flex-center.text-transform-uppercase.terms-header', {
style: {
background: '#EBEBEB',
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index e0a720426..76a265d63 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -2,7 +2,6 @@ const PersistentForm = require('../../lib/persistent-form')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const actions = require('../actions')
const Qr = require('./qr-code')
const isValidAddress = require('../util').isValidAddress
@@ -24,14 +23,7 @@ function ShapeshiftForm () {
}
ShapeshiftForm.prototype.render = function () {
- return h(ReactCSSTransitionGroup, {
- className: 'css-transition-group',
- transitionName: 'main',
- transitionEnterTimeout: 300,
- transitionLeaveTimeout: 300,
- }, [
- this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain(),
- ])
+ return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain()
}
ShapeshiftForm.prototype.renderMain = function () {
diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js
index 6295e7dd9..bef444a48 100644
--- a/ui/app/components/tab-bar.js
+++ b/ui/app/components/tab-bar.js
@@ -21,6 +21,7 @@ TabBar.prototype.render = function () {
background: '#EBEBEB',
color: '#AEAEAE',
paddingTop: '4px',
+ minHeight: '30px',
},
}, tabs.map((tab) => {
const { key, content } = tab
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 20cfa897e..5ea31ae8d 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -47,10 +47,11 @@ TokenList.prototype.render = function () {
return h(TokenCell, tokenData)
})
- return h('div', [
- h('ol', {
+ return h('.full-flex-height', [
+ this.renderTokenStatusBar(),
+
+ h('ol.full-flex-height.flex-column', {
style: {
- height: '260px',
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
@@ -63,6 +64,7 @@ TokenList.prototype.render = function () {
flex-direction: row;
align-items: center;
padding: 10px;
+ min-height: 50px;
}
li.token-cell > h3 {
@@ -76,17 +78,37 @@ TokenList.prototype.render = function () {
`),
...tokenViews,
- tokenViews.length ? null : this.message('No Tokens Found.'),
+ h('.flex-grow'),
]),
- this.addTokenButtonElement(),
])
}
-TokenList.prototype.addTokenButtonElement = function () {
- return h('div', [
- h('div.footer.hover-white.pointer', {
+TokenList.prototype.renderTokenStatusBar = function () {
+ const { tokens } = this.state
+
+ 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',
- onClick: () => {
+ onClick: (event) => {
+ event.preventDefault()
this.props.addToken()
},
style: {
@@ -97,7 +119,7 @@ TokenList.prototype.addTokenButtonElement = function () {
alignItems: 'center',
},
}, [
- h('i.fa.fa-plus.fa-lg'),
+ 'ADD TOKEN',
]),
])
}
diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js
index 3b4ba741e..69b72614c 100644
--- a/ui/app/components/transaction-list.js
+++ b/ui/app/components/transaction-list.js
@@ -24,7 +24,11 @@ TransactionList.prototype.render = function () {
return (
- h('section.transaction-list', [
+ h('section.transaction-list.full-flex-height', {
+ style: {
+ justifyContent: 'center',
+ },
+ }, [
h('style', `
.transaction-list .transaction-list-item:not(:last-of-type) {
@@ -39,7 +43,7 @@ TransactionList.prototype.render = function () {
h('.tx-list', {
style: {
overflowY: 'auto',
- height: '300px',
+ height: '100%',
padding: '0 20px',
textAlign: 'center',
},
@@ -64,13 +68,17 @@ TransactionList.prototype.render = function () {
},
})
})
- : h('.flex-center', {
+ : h('.flex-center.full-flex-height', {
style: {
flexDirection: 'column',
- height: '100%',
+ justifyContent: 'center',
},
}, [
- 'No transaction history.',
+ h('p', {
+ style: {
+ marginTop: '50px',
+ },
+ }, 'No transaction history.'),
]),
]),
])
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 747d3ce2b..34727ff78 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -1,6 +1,5 @@
const inherits = require('util').inherits
const Component = require('react').Component
-const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
@@ -92,34 +91,25 @@ ConfirmTxScreen.prototype.render = function () {
warningIfExists(props.warning),
- h(ReactCSSTransitionGroup, {
- className: 'css-transition-group',
- transitionName: 'main',
- transitionEnterTimeout: 300,
- transitionLeaveTimeout: 300,
- }, [
-
- currentTxView({
- // Properties
- txData: txData,
- key: txData.id,
- selectedAddress: props.selectedAddress,
- accounts: props.accounts,
- identities: props.identities,
- conversionRate,
- currentCurrency,
- blockGasLimit,
- // Actions
- 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),
- }),
-
- ]),
+ currentTxView({
+ // Properties
+ txData: txData,
+ key: txData.id,
+ selectedAddress: props.selectedAddress,
+ accounts: props.accounts,
+ identities: props.identities,
+ conversionRate,
+ currentCurrency,
+ blockGasLimit,
+ // Actions
+ 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),
+ }),
])
)
}
diff --git a/ui/app/css/index.css b/ui/app/css/index.css
index 808aafb4c..49b432a1f 100644
--- a/ui/app/css/index.css
+++ b/ui/app/css/index.css
@@ -19,17 +19,52 @@ html, body {
font-weight: 300;
line-height: 1.4em;
background: #F7F7F7;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ min-height: 500px;
+}
+
+.app-root {
+ overflow: hidden;
+ position: relative
+}
+
+.app-primary {
+ display: flex;
}
input:focus, textarea:focus {
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 {
overflow-x: hidden;
min-width: 357px;
- width: 360px;
- height: 500px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
}
button, input[type="submit"] {
@@ -130,10 +165,6 @@ h2.page-subtitle {
margin: 12px;
}
-.app-primary {
-
-}
-
.app-footer {
padding-bottom: 10px;
align-items: center;
@@ -170,7 +201,7 @@ textarea.twelve-word-phrase {
}
.check {
- margin-left: 7px;
+ margin-left: 12px;
color: #F7861C;
flex: 1 0 auto;
display: flex;
@@ -403,8 +434,16 @@ input.large-input {
/* account detail screen */
.account-detail-section {
+ display: flex;
+ flex-wrap: wrap;
+ overflow-y: auto;
+ flex-direction: inherit;
+}
+.grow-tenx {
+ flex-grow: 10;
}
+
.name-label{
}
diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css
index 910a24ee2..81647d1c1 100644
--- a/ui/app/css/lib.css
+++ b/ui/app/css/lib.css
@@ -232,12 +232,21 @@ hr.horizontal-line {
align-items: center;
}
+.tabSection {
+ min-width: 350px;
+}
+
.menu-icon {
display: inline-block;
- height: 9px;
- min-width: 9px;
+ height: 12px;
+ min-width: 12px;
margin: 13px;
}
+
+i.fa.fa-question-circle.fa-lg.menu-icon {
+ font-size: 18px;
+}
+
.ether-icon {
background: rgb(0, 163, 68);
border-radius: 20px;
@@ -266,3 +275,31 @@ hr.horizontal-line {
margin-top: 20px;
color: red;
}
+
+/*
+ Hacky breakpoint fix for account + tab sections
+ Resolves issue from @frankiebee in
+ https://github.com/MetaMask/metamask-extension/pull/1835
+ Please remove this when integrating new designs
+ */
+
+@media screen and (min-width: 575px) and (max-width: 800px) {
+ .account-data-subsection {
+ flex: 0 0 auto !important; // reset flex
+ margin-left: 10px !important; // create additional horizontal space
+ margin-right: 10px !important;
+ width: 40%;
+ }
+
+ .tabSection {
+ flex: 0 0 auto !important;
+ margin-left: 10px !important;
+ margin-right: 10px !important;
+ min-width: 285px;
+ width: 49%;
+ }
+
+ .name-label {
+ width: 80%;
+ }
+}
diff --git a/ui/app/info.js b/ui/app/info.js
index cb2e41f5b..899841c83 100644
--- a/ui/app/info.js
+++ b/ui/app/info.js
@@ -20,7 +20,11 @@ InfoScreen.prototype.render = function () {
const version = global.platform.getVersion()
return (
- h('.flex-column.flex-grow', [
+ h('.flex-column.flex-grow', {
+ style: {
+ maxWidth: '400px',
+ },
+ }, [
// subtitle and nav
h('.section-title.flex-row.flex-center', [
@@ -103,12 +107,7 @@ InfoScreen.prototype.render = function () {
target: '_blank',
}, '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('a', {
href: 'https://metamask.io/',
@@ -126,6 +125,7 @@ InfoScreen.prototype.render = function () {
h('div.info', 'Visit our web site'),
]),
]),
+
h('div.fa.fa-slack', [
h('a.info', {
href: 'http://slack.metamask.io',
@@ -133,11 +133,13 @@ InfoScreen.prototype.render = function () {
}, 'Join the conversation on Slack'),
]),
- h('div.fa.fa-twitter', [
- h('a.info', {
- href: 'https://twitter.com/metamask_io',
- target: '_blank',
- }, 'Follow us on Twitter'),
+ h('div', [
+ h('.fa.fa-twitter', [
+ h('a.info', {
+ href: 'https://twitter.com/metamask_io',
+ target: '_blank',
+ }, 'Follow us on Twitter'),
+ ]),
]),
h('div.fa.fa-envelope', [
diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js
index a318a9b50..c32751fff 100644
--- a/ui/app/keychains/hd/create-vault-complete.js
+++ b/ui/app/keychains/hd/create-vault-complete.js
@@ -47,8 +47,6 @@ CreateVaultCompleteScreen.prototype.render = function () {
h('div', {
style: {
- width: '360px',
- height: '78px',
fontSize: '1em',
marginTop: '10px',
textAlign: 'center',
diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js
index 4ccbec9fc..4335186a5 100644
--- a/ui/app/keychains/hd/recover-seed/confirmation.js
+++ b/ui/app/keychains/hd/recover-seed/confirmation.js
@@ -23,7 +23,9 @@ RevealSeedConfirmation.prototype.render = function () {
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', {
style: {
@@ -61,7 +63,7 @@ RevealSeedConfirmation.prototype.render = function () {
},
}),
- h('.flex-row.flex-space-between', {
+ h('.flex-row.flex-start', {
style: {
marginTop: 30,
width: '50%',
@@ -74,6 +76,7 @@ RevealSeedConfirmation.prototype.render = function () {
// submit
h('button.primary', {
+ style: { marginLeft: '10px' },
onClick: this.revealSeedWords.bind(this),
}, 'OK'),
diff --git a/ui/app/unlock.js b/ui/app/unlock.js
index 1aee3c5d0..9bacd5124 100644
--- a/ui/app/unlock.js
+++ b/ui/app/unlock.js
@@ -26,7 +26,11 @@ UnlockScreen.prototype.render = function () {
const state = this.props
const warning = state.warning
return (
- h('.flex-column', [
+ h('.flex-column', {
+ style: {
+ width: 'inherit',
+ },
+ }, [
h('.unlock-screen.flex-column.flex-center.flex-grow', [
h(Mascot, {
diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js
index afc62e7b6..5def23e51 100644
--- a/ui/lib/tx-helper.js
+++ b/ui/lib/tx-helper.js
@@ -18,4 +18,3 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network)
return allValues
}
-