diff options
-rw-r--r-- | .babelrc | 4 | ||||
-rw-r--r-- | app/scripts/controllers/transactions.js | 1 | ||||
-rw-r--r-- | development/states/token-list.json | 93 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | ui/app/account-detail.js | 44 | ||||
-rw-r--r-- | ui/app/components/account-export.js | 2 | ||||
-rw-r--r-- | ui/app/components/balance.js | 89 | ||||
-rw-r--r-- | ui/app/components/identicon.js | 10 | ||||
-rw-r--r-- | ui/app/components/token-cell.js | 31 | ||||
-rw-r--r-- | ui/app/components/token-list.js | 72 | ||||
-rw-r--r-- | ui/app/components/transaction-list.js | 11 | ||||
-rw-r--r-- | ui/lib/icon-factory.js | 1 |
12 files changed, 342 insertions, 20 deletions
@@ -1,4 +1,4 @@ { - "presets": ["es2015"], - "plugins": ["transform-runtime"] + "presets": ["es2015", "stage-0"], + "plugins": ["transform-runtime", "transform-async-to-generator"] } diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index faccf1ab1..8be73fad8 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -382,6 +382,7 @@ module.exports = class TransactionController extends EventEmitter { // - `'signed'` the tx is signed // - `'submitted'` the tx is sent to a server // - `'confirmed'` the tx has been included in a block. + // - `'failed'` the tx failed for some reason, included on tx data. _setTxStatus (txId, status) { var txMeta = this.getTx(txId) txMeta.status = status diff --git a/development/states/token-list.json b/development/states/token-list.json new file mode 100644 index 000000000..404f1aedd --- /dev/null +++ b/development/states/token-list.json @@ -0,0 +1,93 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x55e2780588aa5000f464f700d2676fd0a22ee160": { + "address": "0x55e2780588aa5000f464f700d2676fd0a22ee160", + "name": "Account 1" + }, + "0x1c3d6d41dcb245c11a449ec46c9cf9eb7dada4af": { + "address": "0x1c3d6d41dcb245c11a449ec46c9cf9eb7dada4af", + "name": "Account 2" + }, + "0xe34b1ac3074121418152c7a68b4ae6cb7803d725": { + "address": "0xe34b1ac3074121418152c7a68b4ae6cb7803d725", + "name": "Account 3" + } + }, + "unapprovedTxs": {}, + "noActiveNotices": true, + "frequentRpcList": [], + "addressBook": [], + "network": "1", + "accounts": { + "0x55e2780588aa5000f464f700d2676fd0a22ee160": { + "balance": "0x4622f471c28b8a53", + "nonce": "0x17", + "code": "0x", + "address": "0x55e2780588aa5000f464f700d2676fd0a22ee160" + }, + "0x1c3d6d41dcb245c11a449ec46c9cf9eb7dada4af": { + "balance": "0x0", + "nonce": "0x0", + "code": "0x", + "address": "0x1c3d6d41dcb245c11a449ec46c9cf9eb7dada4af" + }, + "0xe34b1ac3074121418152c7a68b4ae6cb7803d725": { + "balance": "0x0", + "nonce": "0x0", + "code": "0x", + "address": "0xe34b1ac3074121418152c7a68b4ae6cb7803d725" + } + }, + "transactions": {}, + "currentBlockNumber": 3575443, + "currentBlockHash": "0xf03bdb8ad844336723473865a5368fa618de837d8290ad380fadbc9fa2bf87f6", + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "55e2780588aa5000f464f700d2676fd0a22ee160", + "1c3d6d41dcb245c11a449ec46c9cf9eb7dada4af", + "e34b1ac3074121418152c7a68b4ae6cb7803d725" + ] + } + ], + "selectedAddress": "0x55e2780588aa5000f464f700d2676fd0a22ee160", + "currentCurrency": "USD", + "conversionRate": 51.12009214, + "conversionDate": 1492788481, + "provider": { + "type": "mainnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [] + }, + "appState": { + "shouldClose": false, + "menuOpen": false, + "currentView": { + "name": "accountDetail", + "detailView": "tokens", + "context": "0x55e2780588aa5000f464f700d2676fd0a22ee160" + }, + "accountDetail": { + "subview": "transactions" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +} diff --git a/package.json b/package.json index 2c23d9e10..07f2c488c 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", + "eth-token-tracker": "^1.0.4", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", @@ -128,8 +129,11 @@ "xtend": "^4.0.1" }, "devDependencies": { + "babel-core": "^6.24.1", "babel-eslint": "^6.0.5", + "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "^6.23.0", "babel-preset-stage-0": "^6.24.1", "babel-register": "^6.7.2", "babelify": "^7.2.0", diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 7a78a360c..5b2588ec5 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -16,6 +16,9 @@ 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') + module.exports = connect(mapStateToProps)(AccountDetailScreen) function mapStateToProps (state) { @@ -36,6 +39,7 @@ function mapStateToProps (state) { inherits(AccountDetailScreen, Component) function AccountDetailScreen () { + this.state = {} Component.call(this) } @@ -237,11 +241,48 @@ AccountDetailScreen.prototype.subview = function () { switch (subview) { case 'transactions': - return this.transactionList() + return this.tabSections() case 'export': var state = extend({key: 'export'}, this.props) return h(ExportAccountView, state) default: + return this.tabSections() + } +} + +AccountDetailScreen.prototype.tabSections = function () { + var subview + try { + subview = this.props.accountDetail.subview + } catch (e) { + subview = null + } + + return h('section.tabSection', [ + + h(TabBar, { + tabs: [ + { content: 'History', key: 'history' }, + { content: 'Tokens', key: 'tokens' }, + ], + defaultTab: subview || 'history', + tabSelected: (key) => { + this.setState({ tabSelection: key }) + }, + }), + + this.tabSwitchView(), + ]) +} + +AccountDetailScreen.prototype.tabSwitchView = function () { + const userAddress = this.props.address + const tabSelection = this.state.tabSelection || 'history' + + switch (tabSelection) { + case 'tokens': + return h(TokenList, { userAddress }) + default: return this.transactionList() } } @@ -249,6 +290,7 @@ AccountDetailScreen.prototype.subview = function () { AccountDetailScreen.prototype.transactionList = function () { const {transactions, unapprovedMsgs, address, network, shapeShiftTxList, conversionRate } = this.props + return h(TransactionList, { transactions: transactions.sort((a, b) => b.time - a.time), network, diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 888196c5d..394d878f7 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -20,8 +20,6 @@ function mapStateToProps (state) { } ExportAccountView.prototype.render = function () { - console.log('EXPORT VIEW') - console.dir(this.props) var state = this.props var accountDetail = state.accountDetail diff --git a/ui/app/components/balance.js b/ui/app/components/balance.js new file mode 100644 index 000000000..57ca84564 --- /dev/null +++ b/ui/app/components/balance.js @@ -0,0 +1,89 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const formatBalance = require('../util').formatBalance +const generateBalanceObject = require('../util').generateBalanceObject +const Tooltip = require('./tooltip.js') +const FiatValue = require('./fiat-value.js') + +module.exports = EthBalanceComponent + +inherits(EthBalanceComponent, Component) +function EthBalanceComponent () { + Component.call(this) +} + +EthBalanceComponent.prototype.render = function () { + var props = this.props + let { value } = props + var style = props.style + var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true + value = value ? formatBalance(value, 6, needsParse) : '...' + var width = props.width + + return ( + + h('.ether-balance.ether-balance-amount', { + style: style, + }, [ + h('div', { + style: { + display: 'inline', + width: width, + }, + }, this.renderBalance(value)), + ]) + + ) +} +EthBalanceComponent.prototype.renderBalance = function (value) { + var props = this.props + if (value === 'None') return value + if (value === '...') return value + var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3) + var balance + var splitBalance = value.split(' ') + var ethNumber = splitBalance[0] + var ethSuffix = splitBalance[1] + const showFiat = 'showFiat' in props ? props.showFiat : true + + if (props.shorten) { + balance = balanceObj.shortBalance + } else { + balance = balanceObj.balance + } + + var label = balanceObj.label + + return ( + h(Tooltip, { + position: 'bottom', + title: `${ethNumber} ${ethSuffix}`, + }, h('div.flex-column', [ + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('div', { + style: { + width: '100%', + textAlign: 'right', + }, + }, this.props.incoming ? `+${balance}` : balance), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + }, + }, label), + ]), + + showFiat ? h(FiatValue, { value: props.value }) : null, + ])) + ) +} diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index 9de854b54..58bd2bdc4 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -35,21 +35,22 @@ IdenticonComponent.prototype.render = function () { IdenticonComponent.prototype.componentDidMount = function () { var props = this.props - var address = props.address + const { address } = props if (!address) return var container = findDOMNode(this) var diameter = props.diameter || this.defaultDiameter + if (!isNode) { - var img = iconFactory.iconForAddress(address, diameter, false) + var img = iconFactory.iconForAddress(address, diameter) container.appendChild(img) } } IdenticonComponent.prototype.componentDidUpdate = function () { var props = this.props - var address = props.address + const { address } = props if (!address) return @@ -62,7 +63,8 @@ IdenticonComponent.prototype.componentDidUpdate = function () { var diameter = props.diameter || this.defaultDiameter if (!isNode) { - var img = iconFactory.iconForAddress(address, diameter, false) + var img = iconFactory.iconForAddress(address, diameter) container.appendChild(img) } } + diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js new file mode 100644 index 000000000..879dc01d1 --- /dev/null +++ b/ui/app/components/token-cell.js @@ -0,0 +1,31 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const Identicon = require('./identicon') + +module.exports = TokenCell + +inherits(TokenCell, Component) +function TokenCell () { + Component.call(this) +} + +TokenCell.prototype.render = function () { + const props = this.props + const { address, symbol, string, network } = props + log.info({ address, symbol, string }) + + return ( + h('li.token-cell', [ + + h(Identicon, { + diameter: 50, + address, + network, + }), + + h('h3', `${string || 0} ${symbol}`), + ]) + ) +} + diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js new file mode 100644 index 000000000..6589dea62 --- /dev/null +++ b/ui/app/components/token-list.js @@ -0,0 +1,72 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const TokenTracker = require('eth-token-tracker') +const TokenCell = require('./token-cell.js') + +module.exports = TokenList + +inherits(TokenList, Component) +function TokenList () { + + // Hard coded for development for now: + const tokens = [ + { address: '0x48c80F1f4D53D5951e5D5438B54Cba84f29F32a5', symbol: 'REP', balance: 'aa'}, + { address: '0xc66ea802717bfb9833400264dd12c2bceaa34a6d', symbol: 'MKR', balance: '1000', decimals: 18}, + { address: '0xa74476443119A942dE498590Fe1f2454d7D4aC0d', symbol: 'GOL', balance: 'ff'}, + { address: '0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009', symbol: 'SNGLS', balance: '0' }, + ] + + this.state = { tokens } + Component.call(this) +} + +TokenList.prototype.render = function () { + const tokens = this.state.tokens + const network = this.props.network + + const tokenViews = tokens.map((tokenData) => { + tokenData.network = network + return h(TokenCell, tokenData) + }) + + return ( + h('ol', [h('style', ` + + li.token-cell { + display: flex; + flex-direction: row; + align-items: center; + padding: 10px; + } + + li.token-cell > h3 { + margin-left: 12px; + } + + li.token-cell:hover { + background: white; + cursor: pointer; + } + + `)].concat(tokenViews)) + ) +} + +TokenList.prototype.componentDidMount = function () { + const { userAddress } = this.props + + this.tracker = new TokenTracker({ + userAddress, + provider: web3.currentProvider, + tokens: this.state.tokens, + }) + + this.setState({ tokens: this.tracker.serialize() }) + this.tracker.on('update', (tokenData) => this.setState({ tokens: tokenData })) + this.tracker.updateBalances() +} + +TokenList.prototype.componentWillUnmount = function () { + this.tracker.stop() +} diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 37a757309..3b4ba741e 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -36,17 +36,6 @@ TransactionList.prototype.render = function () { } `), - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - paddingTop: '4px', - paddingBottom: '4px', - }, - }, [ - 'History', - ]), - h('.tx-list', { style: { overflowY: 'auto', diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index 45be47b7a..4ee6b600b 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -20,6 +20,7 @@ IconFactory.prototype.iconForAddress = function (address, diameter) { if (iconExistsFor(addr)) { return imageElFor(addr) } + return this.generateIdenticonSvg(address, diameter) } |