mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #1835 from MetaMask/NewUI
Begin UI Refactor with Responsiveness & New Menus
This commit is contained in:
commit
57abc58d62
17
CHANGELOG.md
17
CHANGELOG.md
@ -2,6 +2,9 @@
|
||||
|
||||
## 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
|
||||
@ -86,7 +89,7 @@
|
||||
|
||||
## 3.7.8 2017-6-12
|
||||
|
||||
- Add a `ethereum:` prefix to the QR code address
|
||||
- Add an `ethereum:` prefix to the QR code address
|
||||
- The default network on installation is now MainNet
|
||||
- Fix currency API URL from cryptonator.
|
||||
- Update gasLimit params with every new block seen.
|
||||
@ -242,7 +245,7 @@
|
||||
|
||||
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)
|
||||
- Fix unapproved messages not being included in extension badge.
|
||||
- Fix rendering bug where the Confirm transaction view would lets you approve transactions when the account has insufficient balance.
|
||||
- Fix rendering bug where the Confirm transaction view would let you approve transactions when the account has insufficient balance.
|
||||
|
||||
## 3.1.2 2017-1-24
|
||||
|
||||
@ -265,8 +268,8 @@
|
||||
## 3.0.0 2017-1-16
|
||||
|
||||
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
|
||||
- Fix Bug where you see a empty transaction flash by on the confirm transaction view.
|
||||
- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed.
|
||||
- Fix Bug where you see an empty transaction flash by on the confirm transaction view.
|
||||
- Create visible difference in transaction history between an approved but not yet included in a block transaction and a transaction who has been confirmed.
|
||||
- Fix memory leak in RPC Cache
|
||||
- Override RPC commands eth_syncing and web3_clientVersion
|
||||
- Remove certain non-essential permissions from certain builds.
|
||||
@ -321,7 +324,7 @@
|
||||
|
||||
- Fix bug where gas estimate would sometimes be very high.
|
||||
- Increased our gas estimate from 100k gas to 20% of estimate.
|
||||
- Fix github link on info page to point at current repository.
|
||||
- Fix GitHub link on info page to point at current repository.
|
||||
|
||||
## 2.13.6 2016-10-26
|
||||
|
||||
@ -397,7 +400,7 @@ popup notification opens up.
|
||||
- Block negative values from transactions.
|
||||
- Fixed a memory leak.
|
||||
- MetaMask logo now renders as super lightweight SVG, improving compatibility and performance.
|
||||
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experience slow unlocks.
|
||||
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experiencing slow unlocks.
|
||||
- Now only initially creates one wallet when restoring a vault, to reduce some users' confusion.
|
||||
|
||||
## 2.10.2 2016-09-02
|
||||
@ -429,7 +432,7 @@ popup notification opens up.
|
||||
- Added info link on account screen that visits Etherscan.
|
||||
- Fixed bug where a message signing request would be lost if the vault was locked.
|
||||
- Added shortcut to open MetaMask (Ctrl+Alt+M or Cmd+Opt/Alt+M)
|
||||
- Prevent API calls in tests.
|
||||
- Prevent API calls in tests.
|
||||
- Fixed bug where sign message confirmation would sometimes render blank.
|
||||
|
||||
## 2.9.0 2016-08-22
|
||||
|
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>
|
@ -2,10 +2,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
|
||||
<title>MetaMask Plugin</title>
|
||||
</head>
|
||||
<body>
|
||||
<body style="width:357px; height:500px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -91,7 +91,6 @@
|
||||
"inject-css": "^0.1.1",
|
||||
"jazzicon": "^1.2.0",
|
||||
"loglevel": "^1.4.1",
|
||||
"menu-droppo": "^1.1.0",
|
||||
"metamask-logo": "^2.1.2",
|
||||
"mississippi": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
@ -109,7 +108,7 @@
|
||||
"pumpify": "^1.3.4",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"react": "^15.0.2",
|
||||
"react-addons-css-transition-group": "^15.0.2",
|
||||
"react-addons-css-transition-group": "^15.6.0",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-hyperscript": "^2.2.2",
|
||||
"react-markdown": "^2.3.0",
|
||||
|
@ -1,23 +1,19 @@
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var browserify = require('browserify')
|
||||
var tests = fs.readdirSync(path.join(__dirname, 'lib'))
|
||||
var bundlePath = path.join(__dirname, 'bundle.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const browserify = require('browserify')
|
||||
const tests = fs.readdirSync(path.join(__dirname, 'lib'))
|
||||
const bundlePath = path.join(__dirname, 'bundle.js')
|
||||
|
||||
var b = browserify()
|
||||
const b = browserify()
|
||||
|
||||
// Remove old bundle
|
||||
try {
|
||||
fs.unlinkSync(bundlePath)
|
||||
const writeStream = fs.createWriteStream(bundlePath)
|
||||
|
||||
var writeStream = fs.createWriteStream(bundlePath)
|
||||
|
||||
tests.forEach(function (fileName) {
|
||||
b.add(path.join(__dirname, 'lib', fileName))
|
||||
})
|
||||
|
||||
b.bundle().pipe(writeStream)
|
||||
} catch (e) {
|
||||
console.error('Integration build failure', e)
|
||||
}
|
||||
tests.forEach(function (fileName) {
|
||||
b.add(path.join(__dirname, 'lib', fileName))
|
||||
})
|
||||
|
||||
b.bundle()
|
||||
.pipe(writeStream)
|
||||
.on('error', (err) => {
|
||||
throw err
|
||||
})
|
||||
|
@ -90,7 +90,13 @@ QUnit.test('render init screen', function (assert) {
|
||||
return wait()
|
||||
}).then(function (){
|
||||
|
||||
var qrButton = app.find('.fa.fa-qrcode')[0]
|
||||
var qrButton = app.find('.fa.fa-ellipsis-h')[0] // open account settings dropdown
|
||||
qrButton.click()
|
||||
|
||||
return wait(1000)
|
||||
}).then(function (){
|
||||
|
||||
var qrButton = app.find('.dropdown-menu-item')[1] // qr code item
|
||||
qrButton.click()
|
||||
|
||||
return wait(1000)
|
||||
|
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 h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const CopyButton = require('./components/copyButton')
|
||||
const AccountInfoLink = require('./components/account-info-link')
|
||||
const actions = require('./actions')
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
const valuesFor = require('./util').valuesFor
|
||||
|
||||
const Identicon = require('./components/identicon')
|
||||
const EthBalance = require('./components/eth-balance')
|
||||
const TransactionList = require('./components/transaction-list')
|
||||
const ExportAccountView = require('./components/account-export')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const EditableLabel = require('./components/editable-label')
|
||||
const Tooltip = require('./components/tooltip')
|
||||
const TabBar = require('./components/tab-bar')
|
||||
const TokenList = require('./components/token-list')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountDetailScreen)
|
||||
|
||||
@ -54,12 +50,13 @@ AccountDetailScreen.prototype.render = function () {
|
||||
|
||||
return (
|
||||
|
||||
h('.account-detail-section', [
|
||||
h('.account-detail-section.full-flex-height', [
|
||||
|
||||
// identicon, label, balance, etc
|
||||
// identicon, label, balance, etc
|
||||
h('.account-data-subsection', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
}, [
|
||||
|
||||
@ -84,6 +81,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
style: {
|
||||
lineHeight: '10px',
|
||||
marginLeft: '15px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h(EditableLabel, {
|
||||
@ -98,7 +96,43 @@ AccountDetailScreen.prototype.render = function () {
|
||||
|
||||
// What is shown when not editing + edit text:
|
||||
h('label.editing-label', [h('.edit-text', 'edit')]),
|
||||
h('h2.font-medium.color-forest', {name: 'edit'}, identity && identity.name),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
'h2.font-medium.color-forest',
|
||||
{
|
||||
name: 'edit',
|
||||
style: {
|
||||
},
|
||||
},
|
||||
[
|
||||
identity && identity.name,
|
||||
]
|
||||
),
|
||||
h(
|
||||
AccountDropdowns,
|
||||
{
|
||||
style: {
|
||||
marginRight: '8px',
|
||||
marginLeft: 'auto',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
selected,
|
||||
network,
|
||||
identities: props.identities,
|
||||
enableAccountOptions: true,
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
@ -119,61 +153,10 @@ AccountDetailScreen.prototype.render = function () {
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
marginTop: '10px',
|
||||
marginBottom: '15px',
|
||||
color: '#AEAEAE',
|
||||
},
|
||||
}, checksumAddress),
|
||||
|
||||
// copy and export
|
||||
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(AccountInfoLink, { selected, network }),
|
||||
|
||||
h(CopyButton, {
|
||||
value: checksumAddress,
|
||||
}),
|
||||
|
||||
h(Tooltip, {
|
||||
title: 'QR Code',
|
||||
}, [
|
||||
h('i.fa.fa-qrcode.pointer.pop-hover', {
|
||||
onClick: () => props.dispatch(actions.showQrView(selected, identity ? identity.name : '')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
position: 'relative',
|
||||
color: 'rgb(247, 134, 28)',
|
||||
top: '5px',
|
||||
marginLeft: '3px',
|
||||
marginRight: '3px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h(Tooltip, {
|
||||
title: 'Export Private Key',
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h('img.cursor-pointer.color-orange', {
|
||||
src: 'images/key-32.png',
|
||||
onClick: () => this.requestAccountExport(selected),
|
||||
style: {
|
||||
height: '19px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
// account ballence
|
||||
@ -197,14 +180,11 @@ AccountDetailScreen.prototype.render = function () {
|
||||
},
|
||||
}),
|
||||
|
||||
h('.flex-grow'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.buyEthView(selected)),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
marginRight: '8px',
|
||||
position: 'absolute',
|
||||
left: '219px',
|
||||
},
|
||||
style: { marginRight: '10px' },
|
||||
}, 'BUY'),
|
||||
|
||||
h('button', {
|
||||
@ -219,14 +199,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
]),
|
||||
|
||||
// subview (tx history, pk export confirm, buy eth warning)
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
this.subview(),
|
||||
]),
|
||||
this.subview(),
|
||||
|
||||
])
|
||||
)
|
||||
@ -254,7 +227,7 @@ AccountDetailScreen.prototype.subview = function () {
|
||||
AccountDetailScreen.prototype.tabSections = function () {
|
||||
const { currentAccountTab } = this.props
|
||||
|
||||
return h('section.tabSection', [
|
||||
return h('section.tabSection.full-flex-height.grow-tenx', [
|
||||
|
||||
h(TabBar, {
|
||||
tabs: [
|
||||
@ -305,7 +278,3 @@ AccountDetailScreen.prototype.transactionList = function () {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.requestAccountExport = function () {
|
||||
this.props.dispatch(actions.requestExportAccount())
|
||||
}
|
||||
|
@ -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.
|
||||
function setDefaultRpcTarget (rpcList) {
|
||||
function setDefaultRpcTarget () {
|
||||
log.debug(`background.setDefaultRpcTarget`)
|
||||
return (dispatch) => {
|
||||
background.setDefaultRpc((err, result) => {
|
||||
@ -722,7 +722,7 @@ function setDefaultRpcTarget (rpcList) {
|
||||
}
|
||||
|
||||
function setRpcTarget (newRpc) {
|
||||
log.debug(`background.setRpcTarget`)
|
||||
log.debug(`background.setRpcTarget: ${newRpc}`)
|
||||
return (dispatch) => {
|
||||
background.setCustomRpc(newRpc, (err, result) => {
|
||||
if (err) {
|
||||
|
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 h = require('react-hyperscript')
|
||||
const actions = require('./actions')
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
// init
|
||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||
const NewKeyChainScreen = require('./new-keychain')
|
||||
// unlock
|
||||
const UnlockScreen = require('./unlock')
|
||||
// accounts
|
||||
const AccountsScreen = require('./accounts')
|
||||
const AccountDetailScreen = require('./account-detail')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
@ -24,15 +22,15 @@ const Import = require('./accounts/import')
|
||||
const InfoScreen = require('./info')
|
||||
const Loading = require('./components/loading')
|
||||
const SandwichExpando = require('sandwich-expando')
|
||||
const MenuDroppo = require('menu-droppo')
|
||||
const DropMenuItem = require('./components/drop-menu-item')
|
||||
const Dropdown = require('./components/dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const Tooltip = require('./components/tooltip')
|
||||
const BuyView = require('./components/buy-button-subview')
|
||||
const QrView = require('./components/qr-code')
|
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
|
||||
@ -40,6 +38,13 @@ inherits(App, Component)
|
||||
function App () { Component.call(this) }
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const {
|
||||
identities,
|
||||
accounts,
|
||||
address,
|
||||
} = state.metamask
|
||||
const selected = address || Object.keys(accounts)[0]
|
||||
|
||||
return {
|
||||
// state from plugin
|
||||
isLoading: state.appState.isLoading,
|
||||
@ -60,6 +65,10 @@ function mapStateToProps (state) {
|
||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
||||
lostAccounts: state.metamask.lostAccounts,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
|
||||
// state needed to get account dropdown temporarily rendering from app bar
|
||||
identities,
|
||||
selected,
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,16 +78,16 @@ App.prototype.render = function () {
|
||||
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
|
||||
const loadMessage = loadingMessage || isLoadingNetwork ?
|
||||
`Connecting to ${this.getNetworkName()}` : null
|
||||
|
||||
log.debug('Main ui render function')
|
||||
|
||||
return (
|
||||
|
||||
h('.flex-column.flex-grow.full-height', {
|
||||
h('.flex-column.full-height', {
|
||||
style: {
|
||||
// Windows was showing a vertical scroll bar:
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
@ -93,20 +102,12 @@ App.prototype.render = function () {
|
||||
}),
|
||||
|
||||
// panel content
|
||||
h('.app-primary.flex-grow' + (transForward ? '.from-right' : '.from-left'), {
|
||||
h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
|
||||
style: {
|
||||
height: '380px',
|
||||
width: '360px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
this.renderPrimary(),
|
||||
]),
|
||||
this.renderPrimary(),
|
||||
]),
|
||||
])
|
||||
)
|
||||
@ -123,14 +124,16 @@ App.prototype.renderAppBar = function () {
|
||||
|
||||
return (
|
||||
|
||||
h('div', [
|
||||
h('.full-width', {
|
||||
height: '38px',
|
||||
}, [
|
||||
|
||||
h('.app-header.flex-row.flex-space-between', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
visibility: props.isUnlocked ? 'visible' : 'none',
|
||||
background: props.isUnlocked ? 'white' : 'none',
|
||||
height: '36px',
|
||||
height: '38px',
|
||||
position: 'relative',
|
||||
zIndex: 12,
|
||||
},
|
||||
@ -178,32 +181,26 @@ App.prototype.renderAppBar = function () {
|
||||
},
|
||||
}, [
|
||||
|
||||
// small accounts nav
|
||||
props.isUnlocked && h(Tooltip, { title: 'Switch Accounts' }, [
|
||||
h('img.cursor-pointer.color-orange', {
|
||||
src: 'images/switch_acc.svg',
|
||||
style: {
|
||||
width: '23.5px',
|
||||
marginRight: '8px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.props.dispatch(actions.showAccountsPage())
|
||||
},
|
||||
}),
|
||||
]),
|
||||
props.isUnlocked && h(AccountDropdowns, {
|
||||
style: {},
|
||||
enableAccountsSelector: true,
|
||||
identities: this.props.identities,
|
||||
selected: this.props.selected,
|
||||
network: this.props.network,
|
||||
}, []),
|
||||
|
||||
// hamburger
|
||||
props.isUnlocked && h(SandwichExpando, {
|
||||
className: 'sandwich-expando',
|
||||
width: 16,
|
||||
barHeight: 2,
|
||||
padding: 0,
|
||||
isOpen: state.isMainMenuOpen,
|
||||
color: 'rgb(247,146,30)',
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isMainMenuOpen: !state.isMainMenuOpen })
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
isMainMenuOpen: !state.isMainMenuOpen,
|
||||
})
|
||||
},
|
||||
}),
|
||||
]),
|
||||
@ -214,84 +211,141 @@ App.prototype.renderAppBar = function () {
|
||||
|
||||
App.prototype.renderNetworkDropdown = function () {
|
||||
const props = this.props
|
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
||||
const rpcList = props.frequentRpcList
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isNetworkMenuOpen
|
||||
|
||||
return h(MenuDroppo, {
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
this.setState({ isNetworkMenuOpen: !isOpen })
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = [
|
||||
classList.contains('menu-icon'),
|
||||
classList.contains('network-name'),
|
||||
classList.contains('network-indicator'),
|
||||
].filter(bool => bool).length === 0
|
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) {
|
||||
this.setState({ isNetworkMenuOpen: false })
|
||||
}
|
||||
},
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
left: '2px',
|
||||
top: '36px',
|
||||
},
|
||||
innerStyle: {
|
||||
background: 'white',
|
||||
boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
|
||||
padding: '2px 16px 2px 0px',
|
||||
},
|
||||
}, [ // DROP MENU ITEMS
|
||||
h('style', `
|
||||
.drop-menu-item:hover { background:rgb(235, 235, 235); }
|
||||
.drop-menu-item i { margin: 11px; }
|
||||
`),
|
||||
}, [
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Main Ethereum Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
icon: h('.menu-icon.diamond'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'main',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.diamond'),
|
||||
'Main Ethereum Network',
|
||||
providerType === 'mainnet' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Ropsten Test Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
icon: h('.menu-icon.red-dot'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.red-dot'),
|
||||
'Ropsten Test Network',
|
||||
providerType === 'ropsten' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Kovan Test Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
|
||||
action: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
icon: h('.menu-icon.hollow-diamond'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
'Kovan Test Network',
|
||||
providerType === 'kovan' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Rinkeby Test Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
|
||||
action: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
icon: h('.menu-icon.golden-square'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.golden-square'),
|
||||
'Rinkeby Test Network',
|
||||
providerType === 'rinkeby' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Localhost 8545',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setDefaultRpcTarget(rpcList)),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: props.provider.rpcTarget,
|
||||
}),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'default',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setDefaultRpcTarget()),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Localhost 8545',
|
||||
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
}),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => this.props.dispatch(actions.showConfigPage()),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Custom RPC',
|
||||
activeNetwork === 'custom' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
])
|
||||
}
|
||||
@ -300,54 +354,42 @@ App.prototype.renderDropdown = function () {
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isMainMenuOpen
|
||||
|
||||
return h(MenuDroppo, {
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen: isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside: (event) => {
|
||||
this.setState({ isMainMenuOpen: !isOpen })
|
||||
const classList = event.target.classList
|
||||
const parentClassList = event.target.parentElement.classList
|
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') ||
|
||||
parentClassList.contains('sandwich-expando')
|
||||
|
||||
if (isOpen && !isToggleElement) {
|
||||
this.setState({ isMainMenuOpen: false })
|
||||
}
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: '36px',
|
||||
right: '2px',
|
||||
top: '38px',
|
||||
},
|
||||
innerStyle: {
|
||||
background: 'white',
|
||||
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, {
|
||||
label: 'Settings',
|
||||
innerStyle: {},
|
||||
}, [
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-gear.fa-lg'),
|
||||
}),
|
||||
onClick: () => { this.props.dispatch(actions.showConfigPage()) },
|
||||
}, 'Settings'),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Import Account',
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showImportPage()),
|
||||
icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'),
|
||||
}),
|
||||
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
|
||||
}, 'Lock'),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Lock',
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.lockMetamask()),
|
||||
icon: h('i.fa.fa-lock.fa-lg'),
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Info/Help',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showInfoPage()),
|
||||
icon: h('i.fa.fa-question.fa-lg'),
|
||||
}),
|
||||
onClick: () => { this.props.dispatch(actions.showInfoPage()) },
|
||||
}, 'Info/Help'),
|
||||
])
|
||||
}
|
||||
|
||||
@ -433,10 +475,6 @@ App.prototype.renderPrimary = function () {
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'accounts':
|
||||
log.debug('rendering accounts screen')
|
||||
return h(AccountsScreen, {key: 'accounts'})
|
||||
|
||||
case 'accountDetail':
|
||||
log.debug('rendering account detail screen')
|
||||
return h(AccountDetailScreen, {key: 'account-detail'})
|
||||
@ -525,6 +563,8 @@ App.prototype.toggleMetamaskActive = function () {
|
||||
|
||||
App.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
const props = this.props
|
||||
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
// Concatenate long URLs
|
||||
@ -539,13 +579,19 @@ App.prototype.renderCustomOption = function (provider) {
|
||||
return null
|
||||
|
||||
default:
|
||||
return h(DropMenuItem, {
|
||||
label,
|
||||
key: rpcTarget,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: 'custom',
|
||||
})
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: rpcTarget,
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
label,
|
||||
h('.check', '✓'),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -571,21 +617,26 @@ App.prototype.getNetworkName = function () {
|
||||
}
|
||||
|
||||
App.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
const { rpcTarget } = provider
|
||||
const props = this.props
|
||||
const rpcTarget = provider.rpcTarget
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(DropMenuItem, {
|
||||
label: rpc,
|
||||
key: rpc,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: rpc,
|
||||
})
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
rpcTarget === rpc ? h('.check', '✓') : null,
|
||||
]
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
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',
|
||||
overflow: 'hidden',
|
||||
webkitUserSelect: 'text',
|
||||
width: '100%',
|
||||
maxWidth: '275px',
|
||||
},
|
||||
onClick: function (event) {
|
||||
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 {
|
||||
return h('div.name-label', {
|
||||
onClick: (event) => {
|
||||
this.setState({ isEditingLabel: true })
|
||||
const nameAttribute = event.target.getAttribute('name')
|
||||
// checks for class to handle smaller CTA above the account name
|
||||
const classAttribute = event.target.getAttribute('class')
|
||||
if (nameAttribute === 'edit' || classAttribute === 'edit-text') {
|
||||
this.setState({ isEditingLabel: true })
|
||||
}
|
||||
},
|
||||
}, this.props.children)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
|
||||
|
||||
inherits(LoadingIndicator, Component)
|
||||
@ -15,35 +14,28 @@ LoadingIndicator.prototype.render = function () {
|
||||
const { isLoading, loadingMessage } = this.props
|
||||
|
||||
return (
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'loader',
|
||||
transitionEnterTimeout: 150,
|
||||
transitionLeaveTimeout: 150,
|
||||
isLoading ? h('.full-flex-height', {
|
||||
style: {
|
||||
left: '0px',
|
||||
zIndex: 10,
|
||||
position: 'absolute',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
src: 'images/loading.svg',
|
||||
}),
|
||||
|
||||
isLoading ? h('div', {
|
||||
style: {
|
||||
zIndex: 10,
|
||||
position: 'absolute',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
src: 'images/loading.svg',
|
||||
}),
|
||||
h('br'),
|
||||
|
||||
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'),
|
||||
])
|
||||
|
||||
} else if (providerName === 'mainnet') {
|
||||
hoverText = 'Main Ethereum Network'
|
||||
iconName = 'ethereum-network'
|
||||
|
@ -19,7 +19,11 @@ Notice.prototype.render = function () {
|
||||
const disabled = state.disclaimerDisabled
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-center.flex-grow', [
|
||||
h('.flex-column.flex-center.flex-grow', {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h('h3.flex-center.text-transform-uppercase.terms-header', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
|
@ -2,7 +2,6 @@ const PersistentForm = require('../../lib/persistent-form')
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
const actions = require('../actions')
|
||||
const Qr = require('./qr-code')
|
||||
const isValidAddress = require('../util').isValidAddress
|
||||
@ -24,14 +23,7 @@ function ShapeshiftForm () {
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.render = function () {
|
||||
return h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain(),
|
||||
])
|
||||
return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain()
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.renderMain = function () {
|
||||
|
@ -21,6 +21,7 @@ TabBar.prototype.render = function () {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
paddingTop: '4px',
|
||||
minHeight: '30px',
|
||||
},
|
||||
}, tabs.map((tab) => {
|
||||
const { key, content } = tab
|
||||
|
@ -47,10 +47,11 @@ TokenList.prototype.render = function () {
|
||||
return h(TokenCell, tokenData)
|
||||
})
|
||||
|
||||
return h('div', [
|
||||
h('ol', {
|
||||
return h('.full-flex-height', [
|
||||
this.renderTokenStatusBar(),
|
||||
|
||||
h('ol.full-flex-height.flex-column', {
|
||||
style: {
|
||||
height: '260px',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@ -63,6 +64,7 @@ TokenList.prototype.render = function () {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
li.token-cell > h3 {
|
||||
@ -76,17 +78,37 @@ TokenList.prototype.render = function () {
|
||||
|
||||
`),
|
||||
...tokenViews,
|
||||
tokenViews.length ? null : this.message('No Tokens Found.'),
|
||||
h('.flex-grow'),
|
||||
]),
|
||||
this.addTokenButtonElement(),
|
||||
])
|
||||
}
|
||||
|
||||
TokenList.prototype.addTokenButtonElement = function () {
|
||||
return h('div', [
|
||||
h('div.footer.hover-white.pointer', {
|
||||
TokenList.prototype.renderTokenStatusBar = function () {
|
||||
const { tokens } = this.state
|
||||
|
||||
let msg
|
||||
if (tokens.length === 1) {
|
||||
msg = `You own 1 token`
|
||||
} else if (tokens.length === 1) {
|
||||
msg = `You own ${tokens.length} tokens`
|
||||
} else {
|
||||
msg = `No tokens found`
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
minHeight: '70px',
|
||||
padding: '10px',
|
||||
},
|
||||
}, [
|
||||
h('span', msg),
|
||||
h('button', {
|
||||
key: 'reveal-account-bar',
|
||||
onClick: () => {
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
this.props.addToken()
|
||||
},
|
||||
style: {
|
||||
@ -97,7 +119,7 @@ TokenList.prototype.addTokenButtonElement = function () {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-plus.fa-lg'),
|
||||
'ADD TOKEN',
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
@ -24,7 +24,11 @@ TransactionList.prototype.render = function () {
|
||||
|
||||
return (
|
||||
|
||||
h('section.transaction-list', [
|
||||
h('section.transaction-list.full-flex-height', {
|
||||
style: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('style', `
|
||||
.transaction-list .transaction-list-item:not(:last-of-type) {
|
||||
@ -39,7 +43,7 @@ TransactionList.prototype.render = function () {
|
||||
h('.tx-list', {
|
||||
style: {
|
||||
overflowY: 'auto',
|
||||
height: '300px',
|
||||
height: '100%',
|
||||
padding: '0 20px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
@ -64,13 +68,17 @@ TransactionList.prototype.render = function () {
|
||||
},
|
||||
})
|
||||
})
|
||||
: h('.flex-center', {
|
||||
: h('.flex-center.full-flex-height', {
|
||||
style: {
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
'No transaction history.',
|
||||
h('p', {
|
||||
style: {
|
||||
marginTop: '50px',
|
||||
},
|
||||
}, 'No transaction history.'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
|
@ -1,6 +1,5 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
@ -92,34 +91,25 @@ ConfirmTxScreen.prototype.render = function () {
|
||||
|
||||
warningIfExists(props.warning),
|
||||
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
|
||||
currentTxView({
|
||||
// Properties
|
||||
txData: txData,
|
||||
key: txData.id,
|
||||
selectedAddress: props.selectedAddress,
|
||||
accounts: props.accounts,
|
||||
identities: props.identities,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
blockGasLimit,
|
||||
// Actions
|
||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
||||
sendTransaction: this.sendTransaction.bind(this),
|
||||
cancelTransaction: this.cancelTransaction.bind(this, txData),
|
||||
signMessage: this.signMessage.bind(this, txData),
|
||||
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
|
||||
cancelMessage: this.cancelMessage.bind(this, txData),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
|
||||
}),
|
||||
|
||||
]),
|
||||
currentTxView({
|
||||
// Properties
|
||||
txData: txData,
|
||||
key: txData.id,
|
||||
selectedAddress: props.selectedAddress,
|
||||
accounts: props.accounts,
|
||||
identities: props.identities,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
blockGasLimit,
|
||||
// Actions
|
||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
||||
sendTransaction: this.sendTransaction.bind(this),
|
||||
cancelTransaction: this.cancelTransaction.bind(this, txData),
|
||||
signMessage: this.signMessage.bind(this, txData),
|
||||
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
|
||||
cancelMessage: this.cancelMessage.bind(this, txData),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
|
||||
}),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
@ -19,17 +19,52 @@ html, body {
|
||||
font-weight: 300;
|
||||
line-height: 1.4em;
|
||||
background: #F7F7F7;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.app-root {
|
||||
overflow: hidden;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.app-primary {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.full-size {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-flex-height {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#app-content {
|
||||
overflow-x: hidden;
|
||||
min-width: 357px;
|
||||
width: 360px;
|
||||
height: 500px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
button, input[type="submit"] {
|
||||
@ -130,10 +165,6 @@ h2.page-subtitle {
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.app-primary {
|
||||
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
padding-bottom: 10px;
|
||||
align-items: center;
|
||||
@ -170,7 +201,7 @@ textarea.twelve-word-phrase {
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-left: 7px;
|
||||
margin-left: 12px;
|
||||
color: #F7861C;
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
@ -403,8 +434,16 @@ input.large-input {
|
||||
/* account detail screen */
|
||||
|
||||
.account-detail-section {
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
flex-direction: inherit;
|
||||
}
|
||||
|
||||
.grow-tenx {
|
||||
flex-grow: 10;
|
||||
}
|
||||
|
||||
.name-label{
|
||||
|
||||
}
|
||||
|
@ -232,12 +232,21 @@ hr.horizontal-line {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tabSection {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
display: inline-block;
|
||||
height: 9px;
|
||||
min-width: 9px;
|
||||
height: 12px;
|
||||
min-width: 12px;
|
||||
margin: 13px;
|
||||
}
|
||||
|
||||
i.fa.fa-question-circle.fa-lg.menu-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ether-icon {
|
||||
background: rgb(0, 163, 68);
|
||||
border-radius: 20px;
|
||||
@ -266,3 +275,31 @@ hr.horizontal-line {
|
||||
margin-top: 20px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
/*
|
||||
Hacky breakpoint fix for account + tab sections
|
||||
Resolves issue from @frankiebee in
|
||||
https://github.com/MetaMask/metamask-extension/pull/1835
|
||||
Please remove this when integrating new designs
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 575px) and (max-width: 800px) {
|
||||
.account-data-subsection {
|
||||
flex: 0 0 auto !important; // reset flex
|
||||
margin-left: 10px !important; // create additional horizontal space
|
||||
margin-right: 10px !important;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.tabSection {
|
||||
flex: 0 0 auto !important;
|
||||
margin-left: 10px !important;
|
||||
margin-right: 10px !important;
|
||||
min-width: 285px;
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.name-label {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,11 @@ InfoScreen.prototype.render = function () {
|
||||
const version = global.platform.getVersion()
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-grow', [
|
||||
h('.flex-column.flex-grow', {
|
||||
style: {
|
||||
maxWidth: '400px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
@ -103,12 +107,7 @@ InfoScreen.prototype.render = function () {
|
||||
target: '_blank',
|
||||
}, 'Visit our Support Center'),
|
||||
]),
|
||||
h('div.fa.fa-github', [
|
||||
h('a.info', {
|
||||
href: 'https://github.com/MetaMask/metamask-extension/issues/new',
|
||||
target: '_blank',
|
||||
}, 'Found a bug? Report it!'),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('a', {
|
||||
href: 'https://metamask.io/',
|
||||
@ -126,6 +125,7 @@ InfoScreen.prototype.render = function () {
|
||||
h('div.info', 'Visit our web site'),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('div.fa.fa-slack', [
|
||||
h('a.info', {
|
||||
href: 'http://slack.metamask.io',
|
||||
@ -133,11 +133,13 @@ InfoScreen.prototype.render = function () {
|
||||
}, 'Join the conversation on Slack'),
|
||||
]),
|
||||
|
||||
h('div.fa.fa-twitter', [
|
||||
h('a.info', {
|
||||
href: 'https://twitter.com/metamask_io',
|
||||
target: '_blank',
|
||||
}, 'Follow us on Twitter'),
|
||||
h('div', [
|
||||
h('.fa.fa-twitter', [
|
||||
h('a.info', {
|
||||
href: 'https://twitter.com/metamask_io',
|
||||
target: '_blank',
|
||||
}, 'Follow us on Twitter'),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('div.fa.fa-envelope', [
|
||||
|
@ -47,8 +47,6 @@ CreateVaultCompleteScreen.prototype.render = function () {
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
width: '360px',
|
||||
height: '78px',
|
||||
fontSize: '1em',
|
||||
marginTop: '10px',
|
||||
textAlign: 'center',
|
||||
|
@ -23,7 +23,9 @@ RevealSeedConfirmation.prototype.render = function () {
|
||||
|
||||
return (
|
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', {
|
||||
style: { maxWidth: '420px' },
|
||||
}, [
|
||||
|
||||
h('h3.flex-center.text-transform-uppercase', {
|
||||
style: {
|
||||
@ -61,7 +63,7 @@ RevealSeedConfirmation.prototype.render = function () {
|
||||
},
|
||||
}),
|
||||
|
||||
h('.flex-row.flex-space-between', {
|
||||
h('.flex-row.flex-start', {
|
||||
style: {
|
||||
marginTop: 30,
|
||||
width: '50%',
|
||||
@ -74,6 +76,7 @@ RevealSeedConfirmation.prototype.render = function () {
|
||||
|
||||
// submit
|
||||
h('button.primary', {
|
||||
style: { marginLeft: '10px' },
|
||||
onClick: this.revealSeedWords.bind(this),
|
||||
}, 'OK'),
|
||||
|
||||
|
@ -26,7 +26,11 @@ UnlockScreen.prototype.render = function () {
|
||||
const state = this.props
|
||||
const warning = state.warning
|
||||
return (
|
||||
h('.flex-column', [
|
||||
h('.flex-column', {
|
||||
style: {
|
||||
width: 'inherit',
|
||||
},
|
||||
}, [
|
||||
h('.unlock-screen.flex-column.flex-center.flex-grow', [
|
||||
|
||||
h(Mascot, {
|
||||
|
@ -18,4 +18,3 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network)
|
||||
|
||||
return allValues
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user