From 9bae32e78b230ede45ab159e0022da5728f0f267 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 20 Apr 2017 19:07:09 -0700 Subject: [PATCH 01/23] Add functional but ugly and hard-coded token list --- .babelrc | 5 ++- package.json | 6 ++++ ui/app/account-detail.js | 37 ++++++++++++++++++- ui/app/components/account-export.js | 2 -- ui/app/components/token-cell.js | 22 ++++++++++++ ui/app/components/token-list.js | 51 +++++++++++++++++++++++++++ ui/app/components/transaction-list.js | 11 ------ 7 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 ui/app/components/token-cell.js create mode 100644 ui/app/components/token-list.js diff --git a/.babelrc b/.babelrc index 9d8d51656..bca3364fc 100644 --- a/.babelrc +++ b/.babelrc @@ -1 +1,4 @@ -{ "presets": ["es2015"] } +{ + "presets": ["es2015", "stage-0"], + "plugins": ["transform-runtime", "transform-async-to-generator"] +} diff --git a/package.json b/package.json index b892653fa..2eaaf7154 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "eth-query": "^1.0.3", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", + "eth-token-tracker": "^1.0.4", "ethereumjs-tx": "^1.2.5", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", @@ -118,7 +119,12 @@ "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", "beefy": "^2.1.5", diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 018e74893..411f38e5e 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -17,6 +17,8 @@ const ethUtil = require('ethereumjs-util') const EditableLabel = require('./components/editable-label') const Tooltip = require('./components/tooltip') const BuyButtonSubview = require('./components/buy-button-subview') +const TabBar = require('./components/tab-bar') +const TokenList = require('./components/token-list') module.exports = connect(mapStateToProps)(AccountDetailScreen) function mapStateToProps (state) { @@ -35,6 +37,7 @@ function mapStateToProps (state) { inherits(AccountDetailScreen, Component) function AccountDetailScreen () { + this.state = {} Component.call(this) } @@ -234,18 +237,50 @@ 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) case 'buyForm': return h(BuyButtonSubview, extend({key: 'buyForm'}, this.props)) + default: + return this.tabSections() + } +} + +AccountDetailScreen.prototype.tabSections = function () { + + return h('section.tabSection', [ + + h(TabBar, { + tabs: [ + { content: 'History', key: 'history' }, + { content: 'Tokens', key: 'tokens' }, + ], + defaultTab: 'history', + tabSelected: (key) => { + this.setState({ tabSelection: key }) + }, + }), + + this.tabSwitchView(), + ]) +} + +AccountDetailScreen.prototype.tabSwitchView = function () { + const tabSelection = this.state.tabSelection || 'history' + const userAddress = this.props.address + + switch (tabSelection) { + case 'tokens': + return h(TokenList, { userAddress }) default: return this.transactionList() } } AccountDetailScreen.prototype.transactionList = function () { + const {transactions, unapprovedMsgs, address, network, shapeShiftTxList } = this.props return h(TransactionList, { transactions: transactions.sort((a, b) => b.time - a.time), 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/token-cell.js b/ui/app/components/token-cell.js new file mode 100644 index 000000000..34a12733f --- /dev/null +++ b/ui/app/components/token-cell.js @@ -0,0 +1,22 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = TokenCell + +inherits(TokenCell, Component) +function TokenCell () { + Component.call(this) +} + +TokenCell.prototype.render = function () { + const props = this.props + const { address, symbol, string } = props + log.info({ address, symbol, string }) + + return ( + h('li', [ + h('span', `${symbol}: ${string}`), + ]) + ) +} diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js new file mode 100644 index 000000000..35e79401b --- /dev/null +++ b/ui/app/components/token-list.js @@ -0,0 +1,51 @@ +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 + + return ( + h('ol', tokens.map((tokenData) => { + console.log('rendering token with', tokenData) + return h(TokenCell, tokenData) + })) + ) +} + +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 3ae953637..4c25f3dd9 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', From 7202639b370279f90886f2e94ab77aba5b205bc3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 21 Apr 2017 09:01:38 -0700 Subject: [PATCH 02/23] Add token-list dev state --- development/states/token-list.json | 93 ++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 development/states/token-list.json 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": {} +} From 40e2450022488daa5e36d4c1b866e061bba7c5d2 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 21 Apr 2017 09:01:51 -0700 Subject: [PATCH 03/23] Get token list looking ok --- ui/app/account-detail.js | 18 ++++++- ui/app/components/balance.js | 88 +++++++++++++++++++++++++++++++++ ui/app/components/identicon.js | 8 +-- ui/app/components/token-cell.js | 11 ++++- ui/app/components/token-list.js | 23 +++++++-- 5 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 ui/app/components/balance.js diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 411f38e5e..5ceee4fe0 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -249,6 +249,12 @@ AccountDetailScreen.prototype.subview = function () { } AccountDetailScreen.prototype.tabSections = function () { + var subview + try { + subview = this.props.accountDetail.subview + } catch (e) { + subview = null + } return h('section.tabSection', [ @@ -257,7 +263,7 @@ AccountDetailScreen.prototype.tabSections = function () { { content: 'History', key: 'history' }, { content: 'Tokens', key: 'tokens' }, ], - defaultTab: 'history', + defaultTab: subview || 'history', tabSelected: (key) => { this.setState({ tabSelection: key }) }, @@ -268,8 +274,16 @@ AccountDetailScreen.prototype.tabSections = function () { } AccountDetailScreen.prototype.tabSwitchView = function () { - const tabSelection = this.state.tabSelection || 'history' const userAddress = this.props.address + var subview + try { + subview = this.props.accountDetail.subview + return h(TokenList, { userAddress }) + } catch (e) { + subview = null + } + + const tabSelection = this.state.tabSelection || 'history' switch (tabSelection) { case 'tokens': diff --git a/ui/app/components/balance.js b/ui/app/components/balance.js new file mode 100644 index 000000000..3c5e24b65 --- /dev/null +++ b/ui/app/components/balance.js @@ -0,0 +1,88 @@ +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 6d4871d02..1bb92301e 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -34,19 +34,19 @@ IdenticonComponent.prototype.render = function () { IdenticonComponent.prototype.componentDidMount = function () { var props = this.props - var address = props.address + const { address, network } = props if (!address) return var container = findDOMNode(this) var diameter = props.diameter || this.defaultDiameter - var img = iconFactory.iconForAddress(address, diameter, false) + var img = iconFactory.iconForAddress(address, diameter, false, network) container.appendChild(img) } IdenticonComponent.prototype.componentDidUpdate = function () { var props = this.props - var address = props.address + const { address, network } = props if (!address) return @@ -58,6 +58,6 @@ IdenticonComponent.prototype.componentDidUpdate = function () { } var diameter = props.diameter || this.defaultDiameter - var img = iconFactory.iconForAddress(address, diameter, false) + var img = iconFactory.iconForAddress(address, diameter, false, network) container.appendChild(img) } diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index 34a12733f..81e92b301 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const Identicon = require('./identicon') module.exports = TokenCell @@ -15,8 +16,14 @@ TokenCell.prototype.render = function () { log.info({ address, symbol, string }) return ( - h('li', [ - h('span', `${symbol}: ${string}`), + h('li.token-cell', [ + + h(Identicon, { + diameter: 50, + address, + }), + + h('h3', `${string || 0} ${symbol}`), ]) ) } diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 35e79401b..c6a7d3552 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -24,11 +24,26 @@ function TokenList () { TokenList.prototype.render = function () { const tokens = this.state.tokens + const tokenViews = tokens.map((tokenData) => { + console.log('rendering token with', tokenData) + return h(TokenCell, tokenData) + }) + return ( - h('ol', tokens.map((tokenData) => { - console.log('rendering token with', tokenData) - return h(TokenCell, tokenData) - })) + h('ol', [h('style', ` + + li.token-cell { + display: flex; + flex-direction: row; + align-items: center; + padding: 10px; + } + + li.token-cell > h3 { + margin-left: 12px; + } + + `)].concat(tokenViews)) ) } From bce4af2dcaeeab3bd931afbbcc6f17da675ce2b6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 24 Apr 2017 13:55:19 -0700 Subject: [PATCH 04/23] Add placeholder etherscan token icons --- ui/app/components/token-cell.js | 4 +++- ui/app/components/token-list.js | 8 +++++++- ui/lib/icon-factory.js | 26 +++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index 81e92b301..879dc01d1 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -12,7 +12,7 @@ function TokenCell () { TokenCell.prototype.render = function () { const props = this.props - const { address, symbol, string } = props + const { address, symbol, string, network } = props log.info({ address, symbol, string }) return ( @@ -21,9 +21,11 @@ TokenCell.prototype.render = function () { 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 index c6a7d3552..6589dea62 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -23,9 +23,10 @@ function TokenList () { TokenList.prototype.render = function () { const tokens = this.state.tokens + const network = this.props.network const tokenViews = tokens.map((tokenData) => { - console.log('rendering token with', tokenData) + tokenData.network = network return h(TokenCell, tokenData) }) @@ -43,6 +44,11 @@ TokenList.prototype.render = function () { margin-left: 12px; } + li.token-cell:hover { + background: white; + cursor: pointer; + } + `)].concat(tokenViews)) ) } diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index 82cc839d6..ac703ae40 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -10,9 +10,33 @@ module.exports = function (jazzicon) { function IconFactory (jazzicon) { this.jazzicon = jazzicon this.cache = {} + + this.presets = { + '1':{ // Main network: + '0x48c80f1f4d53d5951e5d5438b54cba84f29f32a5': 'https://etherscan.io/token/images/augur.png', + '0xc66ea802717bfb9833400264dd12c2bceaa34a6d': 'https://etherscan.io/token/images/mkr-etherscan-35.png', + '0xa74476443119a942de498590fe1f2454d7d4ac0d': 'https://etherscan.io/token/images/golem.png', + '0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009': 'https://etherscan.io/token/images/sngls.png', + + } + } } -IconFactory.prototype.iconForAddress = function (address, diameter, imageify) { +IconFactory.prototype.iconForAddress = function (address, diameter, imageify, network) { + + try { + const presetUri = this.presets[network][address.toLowerCase()] + if (presetUri) { + var img = document.createElement('img') + img.src = presetUri + img.style.width = `${diameter}px` + img.style.height = `${diameter}px` + img.style.borderRadius = `${diameter/2}px` + return img + } + } catch (e) {} + + if (imageify) { return this.generateIdenticonImg(address, diameter) } else { From d05d9a5f57b9311d6f29539233f9065330e8bda4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 24 Apr 2017 13:55:33 -0700 Subject: [PATCH 05/23] Add missing tx manager state --- app/scripts/transaction-manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index d7051b2cb..22d807748 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -373,6 +373,7 @@ module.exports = class TransactionManager 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 From de500250c463f51f68abff44c8ed6c20912b48c0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 13 Jun 2017 17:46:47 -0700 Subject: [PATCH 06/23] Fix build for eth-contract-metadata --- gulpfile.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 5bba1b9b3..3f235396c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -53,7 +53,7 @@ gulp.task('copy:images', copyTask({ ], })) gulp.task('copy:contractImages', copyTask({ - source: './node_modules/ethereum-contract-icons/images/', + source: './node_modules/eth-contract-metadata/images/', destinations: [ './dist/firefox/images/contract', './dist/chrome/images/contract', diff --git a/package.json b/package.json index 07f2c488c..cb4c05aaf 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "end-of-stream": "^1.1.0", "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", - "eth-contract-metadata": "^1.0.0", + "eth-contract-metadata": "^1.1.0", "eth-hd-keyring": "^1.1.1", "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", From 108c4ab2c58074aa8148828fbbef8cbf3a4e23f5 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 13 Jun 2017 17:47:56 -0700 Subject: [PATCH 07/23] Auto populate token list with popular token balances Half implements #175 Things to do: - Add ability to add tokens to the list. - Persist the token tab selection (so it is an implicit preference) - Check what's up with the token-tracker polling, it seems like it is not waiting the interval. --- ui/app/account-detail.js | 17 ++++-------- ui/app/components/identicon.js | 6 ++-- ui/app/components/tab-bar.js | 1 + ui/app/components/token-cell.js | 22 +++++++++++++-- ui/app/components/token-list.js | 49 +++++++++++++++++++++++---------- ui/app/info.js | 1 + ui/lib/icon-factory.js | 4 +-- 7 files changed, 68 insertions(+), 32 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 5b2588ec5..8234a8438 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -39,7 +39,7 @@ function mapStateToProps (state) { inherits(AccountDetailScreen, Component) function AccountDetailScreen () { - this.state = {} + this.state = { tabSelection: 'history' } Component.call(this) } @@ -251,13 +251,7 @@ AccountDetailScreen.prototype.subview = function () { } AccountDetailScreen.prototype.tabSections = function () { - var subview - try { - subview = this.props.accountDetail.subview - } catch (e) { - subview = null - } - + const tabSelection = this.state.tabSelection return h('section.tabSection', [ h(TabBar, { @@ -265,7 +259,7 @@ AccountDetailScreen.prototype.tabSections = function () { { content: 'History', key: 'history' }, { content: 'Tokens', key: 'tokens' }, ], - defaultTab: subview || 'history', + defaultTab: tabSelection || 'history', tabSelected: (key) => { this.setState({ tabSelection: key }) }, @@ -276,12 +270,13 @@ AccountDetailScreen.prototype.tabSections = function () { } AccountDetailScreen.prototype.tabSwitchView = function () { - const userAddress = this.props.address + const props = this.props + const { address, network } = props const tabSelection = this.state.tabSelection || 'history' switch (tabSelection) { case 'tokens': - return h(TokenList, { userAddress }) + return h(TokenList, { userAddress: address, network }) default: return this.transactionList() } diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index 58bd2bdc4..c754bc6ba 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -23,7 +23,9 @@ IdenticonComponent.prototype.render = function () { h('div', { key: 'identicon-' + this.props.address, style: { - display: 'inline-block', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', height: diameter, width: diameter, borderRadius: diameter / 2, @@ -40,8 +42,8 @@ IdenticonComponent.prototype.componentDidMount = function () { if (!address) return var container = findDOMNode(this) - var diameter = props.diameter || this.defaultDiameter + var diameter = props.diameter || this.defaultDiameter if (!isNode) { var img = iconFactory.iconForAddress(address, diameter) container.appendChild(img) diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js index 65078e0a4..6295e7dd9 100644 --- a/ui/app/components/tab-bar.js +++ b/ui/app/components/tab-bar.js @@ -33,3 +33,4 @@ TabBar.prototype.render = function () { })) ) } + diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index 879dc01d1..ad7f55345 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -12,11 +12,19 @@ function TokenCell () { TokenCell.prototype.render = function () { const props = this.props - const { address, symbol, string, network } = props - log.info({ address, symbol, string }) + const { address, symbol, string, network, userAddress } = props + log.info({ address, symbol, string, network }) return ( - h('li.token-cell', [ + h('li.token-cell', { + style: { cursor: network === '1' ? 'pointer' : 'default' }, + onClick: (event) => { + const url = urlFor(address, userAddress, network) + if (url) { + navigateTo(url) + } + }, + }, [ h(Identicon, { diameter: 50, @@ -29,3 +37,11 @@ TokenCell.prototype.render = function () { ) } +function navigateTo (url) { + global.platform.openWindow({ url }) +} + +function urlFor (tokenAddress, address, network) { + return `https://etherscan.io/token/${tokenAddress}?a=${address}` +} + diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 6589dea62..b79fbccf3 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -3,35 +3,49 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const TokenTracker = require('eth-token-tracker') const TokenCell = require('./token-cell.js') +const contracts = require('eth-contract-metadata') +const Loading = require('./loading') + +const tokens = [] +for (let address in contracts) { + const contract = contracts[address] + if (contract.erc20) { + contract.address = address + tokens.push(contract) + } +} 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 } + this.state = { tokens, isLoading: true } Component.call(this) } TokenList.prototype.render = function () { - const tokens = this.state.tokens + const state = this.state + const { tokens, isLoading } = state + + const { userAddress } = this.props + + if (isLoading) return h(Loading, { isLoading }) + const network = this.props.network const tokenViews = tokens.map((tokenData) => { tokenData.network = network + tokenData.userAddress = userAddress return h(TokenCell, tokenData) }) return ( - h('ol', [h('style', ` + h('ol', { + style: { + height: '302px', + overflowY: 'auto', + }, + }, [h('style', ` li.token-cell { display: flex; @@ -54,19 +68,26 @@ TokenList.prototype.render = function () { } TokenList.prototype.componentDidMount = function () { + if (!global.ethereumProvider) return const { userAddress } = this.props this.tracker = new TokenTracker({ userAddress, - provider: web3.currentProvider, + provider: global.ethereumProvider, tokens: this.state.tokens, + pollingInterval: 8000, }) this.setState({ tokens: this.tracker.serialize() }) - this.tracker.on('update', (tokenData) => this.setState({ tokens: tokenData })) + this.tracker.on('update', (tokenData) => { + const heldTokens = tokenData.filter(token => token.balance !== '0' && token.string !== '0.000') + this.setState({ tokens: heldTokens, isLoading: false }) + }) this.tracker.updateBalances() } TokenList.prototype.componentWillUnmount = function () { + if (!this.tracker) return this.tracker.stop() } + diff --git a/ui/app/info.js b/ui/app/info.js index aa4503b62..825796ed6 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -155,3 +155,4 @@ InfoScreen.prototype.render = function () { InfoScreen.prototype.navigateTo = function (url) { global.platform.openWindow({ url }) } + diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index 4ee6b600b..27a74de66 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -44,7 +44,7 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) { // util function iconExistsFor (address) { - return (contractMap.address) && isValidAddress(address) && (contractMap[address].logo) + return contractMap[address] && isValidAddress(address) && contractMap[address].logo } function imageElFor (address) { @@ -53,7 +53,7 @@ function imageElFor (address) { const path = `images/contract/${fileName}` const img = document.createElement('img') img.src = path - img.style.width = '100%' + img.style.width = '75%' return img } From 5c9a228848144288488e218df4708b940c6e0487 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 13 Jun 2017 17:57:18 -0700 Subject: [PATCH 08/23] Add token list note to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 167a34828..f30f551ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Add list of popular tokens held to the account detail view. + ## 3.7.8 2017-6-12 - Add a `ethereum:` prefix to the QR code address From 42f8f32a52b1ed621179a253a7a3da7641b55e04 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 13 Jun 2017 17:58:51 -0700 Subject: [PATCH 09/23] Remove bad state file --- development/states/token-list.json | 93 ------------------------------ 1 file changed, 93 deletions(-) delete mode 100644 development/states/token-list.json diff --git a/development/states/token-list.json b/development/states/token-list.json deleted file mode 100644 index 404f1aedd..000000000 --- a/development/states/token-list.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "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": {} -} From 3df2f2b2d4239a033ba23d14a75e0a10ece584aa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 13 Jun 2017 18:00:06 -0700 Subject: [PATCH 10/23] Rename history to sent --- ui/app/account-detail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 8234a8438..2e7f3b1be 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -256,7 +256,7 @@ AccountDetailScreen.prototype.tabSections = function () { h(TabBar, { tabs: [ - { content: 'History', key: 'history' }, + { content: 'Sent', key: 'history' }, { content: 'Tokens', key: 'tokens' }, ], defaultTab: tabSelection || 'history', From b7b9e0c1ac203d39196753f39f17a1fe2f4751e5 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 14:21:50 -0700 Subject: [PATCH 11/23] Persist selected account tab Also improve error handling with token balances. --- app/scripts/controllers/preferences.js | 8 ++++++++ app/scripts/metamask-controller.js | 1 + package.json | 4 ++-- ui/app/account-detail.js | 13 +++++++------ ui/app/actions.js | 25 +++++++++++++++++++++---- ui/app/components/token-list.js | 15 +++++++++++++-- 6 files changed, 52 insertions(+), 14 deletions(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 7212c7c43..aa8e05fcc 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -7,6 +7,7 @@ class PreferencesController { constructor (opts = {}) { const initState = extend({ frequentRpcList: [], + currentAccountTab: 'history', }, opts.initState) this.store = new ObservableStore(initState) } @@ -35,6 +36,13 @@ class PreferencesController { }) } + setCurrentAccountTab (currentAccountTab) { + return new Promise((resolve, reject) => { + this.store.updateState({ currentAccountTab }) + resolve() + }) + } + addToFrequentRpcList (_url) { const rpcList = this.getFrequentRpcList() const index = rpcList.findIndex((element) => { return element === _url }) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a7eb3d056..410693df4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -275,6 +275,7 @@ module.exports = class MetamaskController extends EventEmitter { // PreferencesController setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController), + setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab).bind(preferencesController), setDefaultRpc: nodeify(this.setDefaultRpc).bind(this), setCustomRpc: nodeify(this.setCustomRpc).bind(this), diff --git a/package.json b/package.json index cb4c05aaf..edef4753f 100644 --- a/package.json +++ b/package.json @@ -62,12 +62,12 @@ "end-of-stream": "^1.1.0", "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", - "eth-contract-metadata": "^1.1.0", + "eth-contract-metadata": "^1.1.1", "eth-hd-keyring": "^1.1.1", "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.0.4", + "eth-token-tracker": "^1.0.6", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 2e7f3b1be..836032b3c 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -34,12 +34,12 @@ function mapStateToProps (state) { transactions: state.metamask.selectedAddressTxList || [], conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, + currentAccountTab: state.metamask.currentAccountTab, } } inherits(AccountDetailScreen, Component) function AccountDetailScreen () { - this.state = { tabSelection: 'history' } Component.call(this) } @@ -251,7 +251,8 @@ AccountDetailScreen.prototype.subview = function () { } AccountDetailScreen.prototype.tabSections = function () { - const tabSelection = this.state.tabSelection + const { currentAccountTab } = this.props + return h('section.tabSection', [ h(TabBar, { @@ -259,9 +260,9 @@ AccountDetailScreen.prototype.tabSections = function () { { content: 'Sent', key: 'history' }, { content: 'Tokens', key: 'tokens' }, ], - defaultTab: tabSelection || 'history', + defaultTab: currentAccountTab || 'history', tabSelected: (key) => { - this.setState({ tabSelection: key }) + this.props.dispatch(actions.setCurrentAccountTab(key)) }, }), @@ -272,9 +273,9 @@ AccountDetailScreen.prototype.tabSections = function () { AccountDetailScreen.prototype.tabSwitchView = function () { const props = this.props const { address, network } = props - const tabSelection = this.state.tabSelection || 'history' + const { currentAccountTab } = this.props - switch (tabSelection) { + switch (currentAccountTab) { case 'tokens': return h(TokenList, { userAddress: address, network }) default: diff --git a/ui/app/actions.js b/ui/app/actions.js index 1a3557cb4..b6b5d6eb1 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -74,6 +74,7 @@ var actions = { SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE', SET_CURRENT_FIAT: 'SET_CURRENT_FIAT', setCurrentCurrency: setCurrentCurrency, + setCurrentAccountTab, // account detail screen SHOW_SEND_PAGE: 'SHOW_SEND_PAGE', showSendPage: showSendPage, @@ -218,7 +219,7 @@ function confirmSeedWords () { return dispatch(actions.displayWarning(err.message)) } - console.log('Seed word cache cleared. ' + account) + log.info('Seed word cache cleared. ' + account) dispatch(actions.showAccountDetail(account)) }) } @@ -338,7 +339,7 @@ function setCurrentCurrency (currencyCode) { background.setCurrentCurrency(currencyCode, (err, data) => { dispatch(this.hideLoadingIndication()) if (err) { - console.error(err.stack) + log.error(err.stack) return dispatch(actions.displayWarning(err.message)) } dispatch({ @@ -409,7 +410,7 @@ function sendTx (txData) { background.approveTransaction(txData.id, (err) => { if (err) { dispatch(actions.txError(err)) - return console.error(err.message) + return log.error(err.message) } dispatch(actions.completedTx(txData.id)) }) @@ -424,7 +425,7 @@ function updateAndApproveTx (txData) { dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.txError(err)) - return console.error(err.message) + return log.error(err.message) } dispatch(actions.completedTx(txData.id)) }) @@ -558,6 +559,11 @@ function lockMetamask () { return callBackgroundThenUpdate(background.setLocked) } +function setCurrentAccountTab (newTabName) { + log.debug(`background.setCurrentAccountTab: ${newTabName}`) + return callBackgroundThenUpdateNoSpinner(background.setCurrentAccountTab, newTabName) +} + function showAccountDetail (address) { return (dispatch) => { dispatch(actions.showLoadingIndication()) @@ -965,6 +971,17 @@ function shapeShiftRequest (query, options, cb) { // We hide loading indication. // If it errored, we show a warning. // If it didn't, we update the state. +function callBackgroundThenUpdateNoSpinner (method, ...args) { + return (dispatch) => { + method.call(background, ...args, (err) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + forceUpdateMetamaskState(dispatch) + }) + } +} + function callBackgroundThenUpdate (method, ...args) { return (dispatch) => { dispatch(actions.showLoadingIndication()) diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index b79fbccf3..66cbddeda 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -80,10 +80,21 @@ TokenList.prototype.componentDidMount = function () { this.setState({ tokens: this.tracker.serialize() }) this.tracker.on('update', (tokenData) => { - const heldTokens = tokenData.filter(token => token.balance !== '0' && token.string !== '0.000') - this.setState({ tokens: heldTokens, isLoading: false }) + this.updateBalances(tokenData) }) this.tracker.updateBalances() + .then(() => { + this.updateBalances(this.tracker.serialize()) + }) + .catch((reason) => { + log.error(`Problem updating balances`, reason) + this.setState({ isLoading: false }) + }) +} + +TokenList.prototype.updateBalances = function (tokenData) { + const heldTokens = tokenData.filter(token => token.balance !== '0' && token.string !== '0.000') + this.setState({ tokens: heldTokens, isLoading: false }) } TokenList.prototype.componentWillUnmount = function () { From 6fda78cd2b850c7414d598227a0ef6b4235f241e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 15:17:46 -0700 Subject: [PATCH 12/23] Refresh token balance on network change --- app/scripts/controllers/transactions.js | 1 - package.json | 2 +- ui/app/components/token-list.js | 27 ++++++++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 8be73fad8..d546615ed 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -389,7 +389,6 @@ module.exports = class TransactionController extends EventEmitter { this.emit(`${txMeta.id}:${status}`, txId) if (status === 'submitted' || status === 'rejected') { this.emit(`${txMeta.id}:finished`, txMeta) - } this.updateTx(txMeta) this.emit('updateBadge') diff --git a/package.json b/package.json index edef4753f..8c4ef3dc4 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.0.6", + "eth-token-tracker": "^1.0.7", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 66cbddeda..90e7e876e 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -7,7 +7,7 @@ const contracts = require('eth-contract-metadata') const Loading = require('./loading') const tokens = [] -for (let address in contracts) { +for (const address in contracts) { const contract = contracts[address] if (contract.erc20) { contract.address = address @@ -19,7 +19,7 @@ module.exports = TokenList inherits(TokenList, Component) function TokenList () { - this.state = { tokens, isLoading: true } + this.state = { tokens, isLoading: true, network: null } Component.call(this) } @@ -68,17 +68,23 @@ TokenList.prototype.render = function () { } TokenList.prototype.componentDidMount = function () { + this.createFreshTokenTracker() +} + +TokenList.prototype.createFreshTokenTracker = function () { + if (this.tracker) { + this.tracker.stop() + } + if (!global.ethereumProvider) return const { userAddress } = this.props - this.tracker = new TokenTracker({ userAddress, provider: global.ethereumProvider, - tokens: this.state.tokens, + tokens: tokens, pollingInterval: 8000, }) - this.setState({ tokens: this.tracker.serialize() }) this.tracker.on('update', (tokenData) => { this.updateBalances(tokenData) }) @@ -92,6 +98,17 @@ TokenList.prototype.componentDidMount = function () { }) } +TokenList.prototype.componentWillUpdate = function (nextProps) { + if (nextProps.network === 'loading') return + const oldNet = this.props.network + const newNet = nextProps.network + + if (oldNet && newNet && newNet !== oldNet) { + this.setState({ isLoading: true }) + this.createFreshTokenTracker() + } +} + TokenList.prototype.updateBalances = function (tokenData) { const heldTokens = tokenData.filter(token => token.balance !== '0' && token.string !== '0.000') this.setState({ tokens: heldTokens, isLoading: false }) From 96d416c486c4efd3698d41a38a02c6379fbb61b1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 15:30:03 -0700 Subject: [PATCH 13/23] Vertically center loading indication --- ui/app/components/token-list.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 90e7e876e..c9e86dd22 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -4,7 +4,6 @@ const inherits = require('util').inherits const TokenTracker = require('eth-token-tracker') const TokenCell = require('./token-cell.js') const contracts = require('eth-contract-metadata') -const Loading = require('./loading') const tokens = [] for (const address in contracts) { @@ -29,7 +28,16 @@ TokenList.prototype.render = function () { const { userAddress } = this.props - if (isLoading) return h(Loading, { isLoading }) + if (isLoading) { + return h('div', { + style: { + display: 'flex', + height: '250px', + alignItems: 'center', + justifyContent: 'center', + }, + }, 'Loading') + } const network = this.props.network From 0b18a69679e2119fb2bc7ebe027312793504bd5f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 15:52:46 -0700 Subject: [PATCH 14/23] Bump token-tracker version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c4ef3dc4..7f0089d8a 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.0.7", + "eth-token-tracker": "^1.0.8", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", From 1814721e80c057dd5da6f89ece3f2d376ca59bc1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 18:08:03 -0700 Subject: [PATCH 15/23] Add no tokens message --- ui/app/components/token-list.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index c9e86dd22..c560a6072 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -29,14 +29,7 @@ TokenList.prototype.render = function () { const { userAddress } = this.props if (isLoading) { - return h('div', { - style: { - display: 'flex', - height: '250px', - alignItems: 'center', - justifyContent: 'center', - }, - }, 'Loading') + return this.message('Loading') } const network = this.props.network @@ -71,10 +64,21 @@ TokenList.prototype.render = function () { cursor: pointer; } - `)].concat(tokenViews)) + `)].concat(tokenViews.length ? tokenViews : this.message('No Tokens Found.'))) ) } +TokenList.prototype.message = function (body) { + return h('div', { + style: { + display: 'flex', + height: '250px', + alignItems: 'center', + justifyContent: 'center', + }, + }, body) +} + TokenList.prototype.componentDidMount = function () { this.createFreshTokenTracker() } From 68389d5d496dc11a25ce07b5f95b0e10954e847f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 18:12:41 -0700 Subject: [PATCH 16/23] Remove excessive log --- ui/app/components/token-cell.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index ad7f55345..d3a895d36 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -13,7 +13,6 @@ function TokenCell () { TokenCell.prototype.render = function () { const props = this.props const { address, symbol, string, network, userAddress } = props - log.info({ address, symbol, string, network }) return ( h('li.token-cell', { From 1ed5804e4dd2f85549abcb8dfd8981dab3f6868c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 19:15:50 -0700 Subject: [PATCH 17/23] Multiple loading style improvements - When seeking network, show a full screen loading indication + message. - Network menu is still accessible "over" this indication. - Top Menu-Droppo components now slide *under* the menu bar like they should. - Loading indication opacity increased to increase message legibility. --- ui/app/app.js | 19 ++++++++++++------- ui/app/components/loading.js | 5 ++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index 53dbc3354..17a0f8ef3 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -21,7 +21,7 @@ const generateLostAccountsNotice = require('../lib/lost-accounts-notice') const ConfigScreen = require('./config') const Import = require('./accounts/import') const InfoScreen = require('./info') -const LoadingIndicator = require('./components/loading') +const Loading = require('./components/loading') const SandwichExpando = require('sandwich-expando') const MenuDroppo = require('menu-droppo') const DropMenuItem = require('./components/drop-menu-item') @@ -64,7 +64,9 @@ function mapStateToProps (state) { App.prototype.render = function () { var props = this.props - const { isLoading, loadingMessage, transForward } = props + const { isLoading, loadingMessage, transForward, network } = props + const isLoadingNetwork = network === 'loading' + log.debug('Main ui render function') return ( @@ -77,13 +79,16 @@ App.prototype.render = function () { }, }, [ - h(LoadingIndicator, { isLoading, loadingMessage }), - // app bar this.renderAppBar(), this.renderNetworkDropdown(), this.renderDropdown(), + h(Loading, { + isLoading: isLoading || isLoadingNetwork, + loadingMessage: loadingMessage || 'Searching for Network', + }), + // panel content h('.app-primary.flex-grow' + (transForward ? '.from-right' : '.from-left'), { style: { @@ -124,7 +129,7 @@ App.prototype.renderAppBar = function () { background: props.isUnlocked ? 'white' : 'none', height: '36px', position: 'relative', - zIndex: 10, + zIndex: 12, }, }, [ @@ -221,7 +226,7 @@ App.prototype.renderNetworkDropdown = function () { onClickOutside: (event) => { this.setState({ isNetworkMenuOpen: !isOpen }) }, - zIndex: 1, + zIndex: 11, style: { position: 'absolute', left: 0, @@ -300,7 +305,7 @@ App.prototype.renderDropdown = function () { return h(MenuDroppo, { isOpen: isOpen, - zIndex: 1, + zIndex: 11, onClickOutside: (event) => { this.setState({ isMainMenuOpen: !isOpen }) }, diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js index 88dc535df..87d6f5d20 100644 --- a/ui/app/components/loading.js +++ b/ui/app/components/loading.js @@ -26,18 +26,21 @@ LoadingIndicator.prototype.render = function () { style: { zIndex: 10, position: 'absolute', + flexDirection: 'column', display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%', width: '100%', - background: 'rgba(255, 255, 255, 0.5)', + background: 'rgba(255, 255, 255, 0.8)', }, }, [ h('img', { src: 'images/loading.svg', }), + h('br'), + showMessageIfAny(loadingMessage), ]) : null, ]) From a80945e73075b8c0dc43a68ba73da65d7074e098 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 19:36:37 -0700 Subject: [PATCH 18/23] Hide message on normal loading --- ui/app/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/app.js b/ui/app/app.js index 17a0f8ef3..d444a8349 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -66,6 +66,8 @@ App.prototype.render = function () { var props = this.props const { isLoading, loadingMessage, transForward, network } = props const isLoadingNetwork = network === 'loading' + const loadMessage = loadingMessage || isLoadingNetwork ? + 'Searching for Network' : null log.debug('Main ui render function') @@ -86,7 +88,7 @@ App.prototype.render = function () { h(Loading, { isLoading: isLoading || isLoadingNetwork, - loadingMessage: loadingMessage || 'Searching for Network', + loadingMessage: loadMessage, }), // panel content From 1c05c82867f2162db1b1d28be24a051161661517 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 19 Jun 2017 15:22:58 -0700 Subject: [PATCH 19/23] Add MetaMark support --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f0089d8a..f8f77aaa3 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "end-of-stream": "^1.1.0", "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", - "eth-contract-metadata": "^1.1.1", + "eth-contract-metadata": "^1.1.3", "eth-hd-keyring": "^1.1.1", "eth-query": "^2.1.1", "eth-sig-util": "^1.1.1", From 0799e5edf5b508f588af93431db0df3bd7e6c27d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 19 Jun 2017 19:02:38 -0700 Subject: [PATCH 20/23] Fix token balance rendering --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b0024e901..01c256ef3 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eth-query": "^2.1.2", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.0.8", + "eth-token-tracker": "^1.0.9", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", From a2781df8b4ce8d1fc03080eb3361217a236ec82d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 19 Jun 2017 19:11:55 -0700 Subject: [PATCH 21/23] Add better event lifecycle management to token list. Token list now renders errors when a token lookup fails. Also now cleans up event listeners when re-initializing the token list. --- ui/app/components/token-list.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index c560a6072..633d3ccfe 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -24,7 +24,7 @@ function TokenList () { TokenList.prototype.render = function () { const state = this.state - const { tokens, isLoading } = state + const { tokens, isLoading, error } = state const { userAddress } = this.props @@ -32,6 +32,11 @@ TokenList.prototype.render = function () { return this.message('Loading') } + if (error) { + log.error(error) + return this.message('There was a problem loading your token balances.') + } + const network = this.props.network const tokenViews = tokens.map((tokenData) => { @@ -85,7 +90,10 @@ TokenList.prototype.componentDidMount = function () { TokenList.prototype.createFreshTokenTracker = function () { if (this.tracker) { + // Clean up old trackers when refreshing: this.tracker.stop() + this.tracker.removeListener('update', this.balanceUpdater) + this.tracker.removeListener('error', this.showError) } if (!global.ethereumProvider) return @@ -97,9 +105,15 @@ TokenList.prototype.createFreshTokenTracker = function () { pollingInterval: 8000, }) - this.tracker.on('update', (tokenData) => { - this.updateBalances(tokenData) - }) + + // Set up listener instances for cleaning up + this.balanceUpdater = this.updateBalances.bind(this) + this.showError = (error) => { + this.setState({ error, isLoading: false }) + } + this.tracker.on('update', this.balanceUpdater) + this.tracker.on('error', this.showError) + this.tracker.updateBalances() .then(() => { this.updateBalances(this.tracker.serialize()) From c7780727eb26cd35806f36aaf91cfb6865dd9693 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 20 Jun 2017 09:01:55 -0700 Subject: [PATCH 22/23] Bump circleCI node version to 8.0.0 --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 4305ca3b4..1f018ac24 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: node: - version: 7.6.0 + version: 8.0.0 dependencies: pre: - "npm i -g testem" From c0c588053a29a4406ef30de8628065429ff99595 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 22 Jun 2017 09:46:03 -0400 Subject: [PATCH 23/23] Print integration build errors --- test/integration/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/integration/index.js b/test/integration/index.js index f2d656b0b..85f91d92b 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -9,13 +9,15 @@ var b = browserify() // Remove old bundle try { fs.unlinkSync(bundlePath) -} catch (e) {} -var writeStream = fs.createWriteStream(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) + b.bundle().pipe(writeStream) +} catch (e) { + console.error('Integration build failure', e) +}