mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'master' into transactionControllerRefractor
This commit is contained in:
commit
f3b42f1e33
27
CHANGELOG.md
27
CHANGELOG.md
@ -2,9 +2,20 @@
|
|||||||
|
|
||||||
## Current Master
|
## 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
|
## 3.9.3 2017-8-03
|
||||||
|
|
||||||
- Add support for EGO uport token
|
- Add support for EGO ujo token
|
||||||
- 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.
|
||||||
|
|
||||||
@ -78,7 +89,7 @@
|
|||||||
|
|
||||||
## 3.7.8 2017-6-12
|
## 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
|
- The default network on installation is now MainNet
|
||||||
- Fix currency API URL from cryptonator.
|
- Fix currency API URL from cryptonator.
|
||||||
- Update gasLimit params with every new block seen.
|
- Update gasLimit params with every new block seen.
|
||||||
@ -234,7 +245,7 @@
|
|||||||
|
|
||||||
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)
|
- 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 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
|
## 3.1.2 2017-1-24
|
||||||
|
|
||||||
@ -257,8 +268,8 @@
|
|||||||
## 3.0.0 2017-1-16
|
## 3.0.0 2017-1-16
|
||||||
|
|
||||||
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
|
- 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.
|
- Fix Bug where you see an 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.
|
- 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
|
- Fix memory leak in RPC Cache
|
||||||
- Override RPC commands eth_syncing and web3_clientVersion
|
- Override RPC commands eth_syncing and web3_clientVersion
|
||||||
- Remove certain non-essential permissions from certain builds.
|
- Remove certain non-essential permissions from certain builds.
|
||||||
@ -313,7 +324,7 @@
|
|||||||
|
|
||||||
- Fix bug where gas estimate would sometimes be very high.
|
- Fix bug where gas estimate would sometimes be very high.
|
||||||
- Increased our gas estimate from 100k gas to 20% of estimate.
|
- 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
|
## 2.13.6 2016-10-26
|
||||||
|
|
||||||
@ -389,7 +400,7 @@ popup notification opens up.
|
|||||||
- Block negative values from transactions.
|
- Block negative values from transactions.
|
||||||
- Fixed a memory leak.
|
- Fixed a memory leak.
|
||||||
- MetaMask logo now renders as super lightweight SVG, improving compatibility and performance.
|
- 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.
|
- Now only initially creates one wallet when restoring a vault, to reduce some users' confusion.
|
||||||
|
|
||||||
## 2.10.2 2016-09-02
|
## 2.10.2 2016-09-02
|
||||||
@ -421,7 +432,7 @@ popup notification opens up.
|
|||||||
- Added info link on account screen that visits Etherscan.
|
- Added info link on account screen that visits Etherscan.
|
||||||
- Fixed bug where a message signing request would be lost if the vault was locked.
|
- 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)
|
- 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.
|
- Fixed bug where sign message confirmation would sometimes render blank.
|
||||||
|
|
||||||
## 2.9.0 2016-08-22
|
## 2.9.0 2016-08-22
|
||||||
|
12
app/home.html
Normal file
12
app/home.html
Normal file
@ -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>
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "MetaMask",
|
"name": "MetaMask",
|
||||||
"short_name": "Metamask",
|
"short_name": "Metamask",
|
||||||
"version": "3.9.3",
|
"version": "3.9.5",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "Ethereum Browser Extension",
|
"description": "Ethereum Browser Extension",
|
||||||
|
@ -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>
|
||||||
|
@ -4,8 +4,8 @@ const PhishingDetector = require('eth-phishing-detect/src/detector')
|
|||||||
|
|
||||||
// compute phishing lists
|
// compute phishing lists
|
||||||
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
|
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
|
||||||
// every ten minutes
|
// every four minutes
|
||||||
const POLLING_INTERVAL = 10 * 60 * 1000
|
const POLLING_INTERVAL = 4 * 60 * 1000
|
||||||
|
|
||||||
class BlacklistController {
|
class BlacklistController {
|
||||||
|
|
||||||
@ -41,6 +41,7 @@ class BlacklistController {
|
|||||||
|
|
||||||
scheduleUpdates () {
|
scheduleUpdates () {
|
||||||
if (this._phishingUpdateIntervalRef) return
|
if (this._phishingUpdateIntervalRef) return
|
||||||
|
this.updatePhishingList()
|
||||||
this._phishingUpdateIntervalRef = setInterval(() => {
|
this._phishingUpdateIntervalRef = setInterval(() => {
|
||||||
this.updatePhishingList()
|
this.updatePhishingList()
|
||||||
}, POLLING_INTERVAL)
|
}, POLLING_INTERVAL)
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
"eth-bin-to-ops": "^1.0.1",
|
"eth-bin-to-ops": "^1.0.1",
|
||||||
"eth-contract-metadata": "^1.1.4",
|
"eth-contract-metadata": "^1.1.4",
|
||||||
"eth-hd-keyring": "^1.1.1",
|
"eth-hd-keyring": "^1.1.1",
|
||||||
"eth-phishing-detect": "^1.1.0",
|
"eth-phishing-detect": "^1.1.4",
|
||||||
"eth-query": "^2.1.2",
|
"eth-query": "^2.1.2",
|
||||||
"eth-sig-util": "^1.2.2",
|
"eth-sig-util": "^1.2.2",
|
||||||
"eth-simple-keyring": "^1.1.1",
|
"eth-simple-keyring": "^1.1.1",
|
||||||
@ -92,7 +92,6 @@
|
|||||||
"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",
|
|
||||||
"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",
|
||||||
@ -110,7 +109,7 @@
|
|||||||
"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-addons-css-transition-group": "^15.6.0",
|
||||||
"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",
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
var fs = require('fs')
|
const fs = require('fs')
|
||||||
var path = require('path')
|
const path = require('path')
|
||||||
var browserify = require('browserify')
|
const browserify = require('browserify')
|
||||||
var tests = fs.readdirSync(path.join(__dirname, 'lib'))
|
const tests = fs.readdirSync(path.join(__dirname, 'lib'))
|
||||||
var bundlePath = path.join(__dirname, 'bundle.js')
|
const bundlePath = path.join(__dirname, 'bundle.js')
|
||||||
|
|
||||||
var b = browserify()
|
const b = browserify()
|
||||||
|
|
||||||
// Remove old bundle
|
const writeStream = fs.createWriteStream(bundlePath)
|
||||||
try {
|
|
||||||
fs.unlinkSync(bundlePath)
|
|
||||||
|
|
||||||
var writeStream = fs.createWriteStream(bundlePath)
|
tests.forEach(function (fileName) {
|
||||||
|
b.add(path.join(__dirname, 'lib', fileName))
|
||||||
tests.forEach(function (fileName) {
|
})
|
||||||
b.add(path.join(__dirname, 'lib', fileName))
|
|
||||||
})
|
|
||||||
|
|
||||||
b.bundle().pipe(writeStream)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Integration build failure', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
b.bundle()
|
||||||
|
.pipe(writeStream)
|
||||||
|
.on('error', (err) => {
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
@ -90,7 +90,13 @@ QUnit.test('render init screen', function (assert) {
|
|||||||
return wait()
|
return wait()
|
||||||
}).then(function (){
|
}).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()
|
qrButton.click()
|
||||||
|
|
||||||
return wait(1000)
|
return wait(1000)
|
||||||
|
115
test/unit/responsive/components/dropdown-test.js
Normal file
115
test/unit/responsive/components/dropdown-test.js
Normal file
@ -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,43 @@ 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,
|
||||||
|
enableAccountOptions: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
]),
|
]),
|
||||||
h('.flex-row', {
|
h('.flex-row', {
|
||||||
style: {
|
style: {
|
||||||
@ -119,61 +153,10 @@ AccountDetailScreen.prototype.render = function () {
|
|||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
fontFamily: 'Montserrat Light',
|
fontFamily: 'Montserrat Light',
|
||||||
textRendering: 'geometricPrecision',
|
textRendering: 'geometricPrecision',
|
||||||
marginTop: '10px',
|
|
||||||
marginBottom: '15px',
|
marginBottom: '15px',
|
||||||
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())
|
|
||||||
}
|
|
@ -709,7 +709,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) => {
|
||||||
@ -722,7 +722,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) {
|
||||||
|
349
ui/app/app.js
349
ui/app/app.js
@ -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,15 +22,15 @@ 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')
|
||||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||||
|
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(App)
|
module.exports = connect(mapStateToProps)(App)
|
||||||
|
|
||||||
@ -40,6 +38,13 @@ inherits(App, Component)
|
|||||||
function App () { Component.call(this) }
|
function App () { Component.call(this) }
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
const {
|
||||||
|
identities,
|
||||||
|
accounts,
|
||||||
|
address,
|
||||||
|
} = state.metamask
|
||||||
|
const selected = address || Object.keys(accounts)[0]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// state from plugin
|
// state from plugin
|
||||||
isLoading: state.appState.isLoading,
|
isLoading: state.appState.isLoading,
|
||||||
@ -60,6 +65,10 @@ function mapStateToProps (state) {
|
|||||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
||||||
lostAccounts: state.metamask.lostAccounts,
|
lostAccounts: state.metamask.lostAccounts,
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
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 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 +102,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',
|
width: '100%',
|
||||||
width: '360px',
|
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
h(ReactCSSTransitionGroup, {
|
this.renderPrimary(),
|
||||||
className: 'css-transition-group',
|
|
||||||
transitionName: 'main',
|
|
||||||
transitionEnterTimeout: 300,
|
|
||||||
transitionLeaveTimeout: 300,
|
|
||||||
}, [
|
|
||||||
this.renderPrimary(),
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
@ -123,14 +124,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,32 +181,26 @@ App.prototype.renderAppBar = function () {
|
|||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
|
|
||||||
// small accounts nav
|
props.isUnlocked && h(AccountDropdowns, {
|
||||||
props.isUnlocked && h(Tooltip, { title: 'Switch Accounts' }, [
|
style: {},
|
||||||
h('img.cursor-pointer.color-orange', {
|
enableAccountsSelector: true,
|
||||||
src: 'images/switch_acc.svg',
|
identities: this.props.identities,
|
||||||
style: {
|
selected: this.props.selected,
|
||||||
width: '23.5px',
|
network: this.props.network,
|
||||||
marginRight: '8px',
|
}, []),
|
||||||
},
|
|
||||||
onClick: (event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
this.props.dispatch(actions.showAccountsPage())
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
|
|
||||||
// hamburger
|
// hamburger
|
||||||
props.isUnlocked && h(SandwichExpando, {
|
props.isUnlocked && h(SandwichExpando, {
|
||||||
|
className: 'sandwich-expando',
|
||||||
width: 16,
|
width: 16,
|
||||||
barHeight: 2,
|
barHeight: 2,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
isOpen: state.isMainMenuOpen,
|
isOpen: state.isMainMenuOpen,
|
||||||
color: 'rgb(247,146,30)',
|
color: 'rgb(247,146,30)',
|
||||||
onClick: (event) => {
|
onClick: () => {
|
||||||
event.preventDefault()
|
this.setState({
|
||||||
event.stopPropagation()
|
isMainMenuOpen: !state.isMainMenuOpen,
|
||||||
this.setState({ isMainMenuOpen: !state.isMainMenuOpen })
|
})
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@ -214,84 +211,141 @@ 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, {
|
||||||
|
useCssTransition: true,
|
||||||
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); }
|
|
||||||
.drop-menu-item i { margin: 11px; }
|
|
||||||
`),
|
|
||||||
|
|
||||||
h(DropMenuItem, {
|
h(
|
||||||
label: 'Main Ethereum Network',
|
DropdownMenuItem,
|
||||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
{
|
||||||
action: () => props.dispatch(actions.setProviderType('mainnet')),
|
key: 'main',
|
||||||
icon: h('.menu-icon.diamond'),
|
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||||
activeNetworkRender: props.network,
|
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||||
provider: props.provider,
|
style: {
|
||||||
}),
|
fontSize: '18px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('.menu-icon.diamond'),
|
||||||
|
'Main Ethereum Network',
|
||||||
|
providerType === 'mainnet' ? h('.check', '✓') : null,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
|
||||||
h(DropMenuItem, {
|
h(
|
||||||
label: 'Ropsten Test Network',
|
DropdownMenuItem,
|
||||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
{
|
||||||
action: () => props.dispatch(actions.setProviderType('ropsten')),
|
key: 'ropsten',
|
||||||
icon: h('.menu-icon.red-dot'),
|
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||||
activeNetworkRender: props.network,
|
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||||
provider: props.provider,
|
style: {
|
||||||
}),
|
fontSize: '18px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('.menu-icon.red-dot'),
|
||||||
|
'Ropsten Test Network',
|
||||||
|
providerType === 'ropsten' ? h('.check', '✓') : null,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
|
||||||
h(DropMenuItem, {
|
h(
|
||||||
label: 'Kovan Test Network',
|
DropdownMenuItem,
|
||||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
|
{
|
||||||
action: () => props.dispatch(actions.setProviderType('kovan')),
|
key: 'kovan',
|
||||||
icon: h('.menu-icon.hollow-diamond'),
|
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||||
activeNetworkRender: props.network,
|
onClick: () => props.dispatch(actions.setProviderType('kovan')),
|
||||||
provider: props.provider,
|
style: {
|
||||||
}),
|
fontSize: '18px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('.menu-icon.hollow-diamond'),
|
||||||
|
'Kovan Test Network',
|
||||||
|
providerType === 'kovan' ? h('.check', '✓') : null,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
|
||||||
h(DropMenuItem, {
|
h(
|
||||||
label: 'Rinkeby Test Network',
|
DropdownMenuItem,
|
||||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
|
{
|
||||||
action: () => props.dispatch(actions.setProviderType('rinkeby')),
|
key: 'rinkeby',
|
||||||
icon: h('.menu-icon.golden-square'),
|
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||||
activeNetworkRender: props.network,
|
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||||
provider: props.provider,
|
style: {
|
||||||
}),
|
fontSize: '18px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('.menu-icon.golden-square'),
|
||||||
|
'Rinkeby Test Network',
|
||||||
|
providerType === 'rinkeby' ? h('.check', '✓') : null,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
|
||||||
h(DropMenuItem, {
|
h(
|
||||||
label: 'Localhost 8545',
|
DropdownMenuItem,
|
||||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
{
|
||||||
action: () => props.dispatch(actions.setDefaultRpcTarget(rpcList)),
|
key: 'default',
|
||||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||||
activeNetworkRender: props.provider.rpcTarget,
|
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.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()),
|
||||||
}),
|
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 state = this.state || {}
|
||||||
const isOpen = state.isMainMenuOpen
|
const isOpen = state.isMainMenuOpen
|
||||||
|
|
||||||
return h(MenuDroppo, {
|
return h(Dropdown, {
|
||||||
|
useCssTransition: true,
|
||||||
isOpen: isOpen,
|
isOpen: isOpen,
|
||||||
zIndex: 11,
|
zIndex: 11,
|
||||||
onClickOutside: (event) => {
|
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: {
|
style: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 0,
|
right: '2px',
|
||||||
top: '36px',
|
top: '38px',
|
||||||
},
|
},
|
||||||
innerStyle: {
|
innerStyle: {},
|
||||||
background: 'white',
|
}, [
|
||||||
boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
|
h(DropdownMenuItem, {
|
||||||
},
|
|
||||||
}, [ // 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 }),
|
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 +475,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 +563,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 +579,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 +617,26 @@ 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, {
|
return h(
|
||||||
label: rpc,
|
DropdownMenuItem,
|
||||||
key: rpc,
|
{
|
||||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
key: `common${rpc}`,
|
||||||
action: () => props.dispatch(actions.setRpcTarget(rpc)),
|
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||||
activeNetworkRender: rpc,
|
},
|
||||||
})
|
[
|
||||||
|
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||||
|
rpc,
|
||||||
|
rpcTarget === rpc ? h('.check', '✓') : null,
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
289
ui/app/components/account-dropdowns.js
Normal file
289
ui/app/components/account-dropdowns.js
Normal file
@ -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),
|
||||||
|
}
|
@ -100,7 +100,7 @@ ExportAccountView.prototype.render = function () {
|
|||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
webkitUserSelect: 'text',
|
webkitUserSelect: 'text',
|
||||||
width: '100%',
|
maxWidth: '275px',
|
||||||
},
|
},
|
||||||
onClick: function (event) {
|
onClick: function (event) {
|
||||||
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
|
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
|
||||||
|
@ -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', '✓')
|
|
||||||
}
|
|
||||||
}
|
|
94
ui/app/components/dropdown.js
Normal file
94
ui/app/components/dropdown.js
Normal file
@ -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,
|
||||||
|
}
|
@ -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,
|
|
||||||
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
|
||||||
showMessageIfAny(loadingMessage),
|
|
||||||
]) : null,
|
|
||||||
])
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
130
ui/app/components/menu-droppo.js
Normal file
130
ui/app/components/menu-droppo.js
Normal file
@ -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
|
||||||
|
}
|
@ -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'
|
||||||
|
@ -19,7 +19,11 @@ Notice.prototype.render = function () {
|
|||||||
const disabled = state.disclaimerDisabled
|
const disabled = state.disclaimerDisabled
|
||||||
|
|
||||||
return (
|
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', {
|
h('h3.flex-center.text-transform-uppercase.terms-header', {
|
||||||
style: {
|
style: {
|
||||||
background: '#EBEBEB',
|
background: '#EBEBEB',
|
||||||
|
@ -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,52 @@ html, body {
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
background: #F7F7F7;
|
background: #F7F7F7;
|
||||||
|
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 +165,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;
|
||||||
@ -170,7 +201,7 @@ textarea.twelve-word-phrase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.check {
|
.check {
|
||||||
margin-left: 7px;
|
margin-left: 12px;
|
||||||
color: #F7861C;
|
color: #F7861C;
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -403,8 +434,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,12 +232,21 @@ 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: 12px;
|
||||||
min-width: 9px;
|
min-width: 12px;
|
||||||
margin: 13px;
|
margin: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.fa.fa-question-circle.fa-lg.menu-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.ether-icon {
|
.ether-icon {
|
||||||
background: rgb(0, 163, 68);
|
background: rgb(0, 163, 68);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@ -266,3 +275,31 @@ hr.horizontal-line {
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
color: red;
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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'),
|
||||||
|
|
||||||
|
@ -26,7 +26,11 @@ UnlockScreen.prototype.render = function () {
|
|||||||
const state = this.props
|
const state = this.props
|
||||||
const warning = state.warning
|
const warning = state.warning
|
||||||
return (
|
return (
|
||||||
h('.flex-column', [
|
h('.flex-column', {
|
||||||
|
style: {
|
||||||
|
width: 'inherit',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
h('.unlock-screen.flex-column.flex-center.flex-grow', [
|
h('.unlock-screen.flex-column.flex-center.flex-grow', [
|
||||||
|
|
||||||
h(Mascot, {
|
h(Mascot, {
|
||||||
|
@ -18,4 +18,3 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network)
|
|||||||
|
|
||||||
return allValues
|
return allValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user