From 9bae32e78b230ede45ab159e0022da5728f0f267 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 20 Apr 2017 19:07:09 -0700 Subject: Add functional but ugly and hard-coded token list --- 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 -------- 5 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 ui/app/components/token-cell.js create mode 100644 ui/app/components/token-list.js (limited to 'ui') 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', -- cgit From 40e2450022488daa5e36d4c1b866e061bba7c5d2 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 21 Apr 2017 09:01:51 -0700 Subject: 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 (limited to 'ui') 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)) ) } -- cgit From bce4af2dcaeeab3bd931afbbcc6f17da675ce2b6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 24 Apr 2017 13:55:19 -0700 Subject: 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(-) (limited to 'ui') 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 { -- cgit From 108c4ab2c58074aa8148828fbbef8cbf3a4e23f5 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 13 Jun 2017 17:47:56 -0700 Subject: 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(-) (limited to 'ui') 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 } -- cgit From 3df2f2b2d4239a033ba23d14a75e0a10ece584aa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 13 Jun 2017 18:00:06 -0700 Subject: Rename history to sent --- ui/app/account-detail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') 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', -- cgit From b7b9e0c1ac203d39196753f39f17a1fe2f4751e5 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 14:21:50 -0700 Subject: Persist selected account tab Also improve error handling with token balances. --- ui/app/account-detail.js | 13 +++++++------ ui/app/actions.js | 25 +++++++++++++++++++++---- ui/app/components/token-list.js | 15 +++++++++++++-- 3 files changed, 41 insertions(+), 12 deletions(-) (limited to 'ui') 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 () { -- cgit From 6fda78cd2b850c7414d598227a0ef6b4235f241e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 15:17:46 -0700 Subject: Refresh token balance on network change --- ui/app/components/token-list.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'ui') 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 }) -- cgit From 96d416c486c4efd3698d41a38a02c6379fbb61b1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 15:30:03 -0700 Subject: Vertically center loading indication --- ui/app/components/token-list.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'ui') 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 -- cgit From 1814721e80c057dd5da6f89ece3f2d376ca59bc1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 18:08:03 -0700 Subject: Add no tokens message --- ui/app/components/token-list.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'ui') 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() } -- cgit From 68389d5d496dc11a25ce07b5f95b0e10954e847f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 18:12:41 -0700 Subject: Remove excessive log --- ui/app/components/token-cell.js | 1 - 1 file changed, 1 deletion(-) (limited to 'ui') 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', { -- cgit From 1ed5804e4dd2f85549abcb8dfd8981dab3f6868c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 19:15:50 -0700 Subject: 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(-) (limited to 'ui') 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, ]) -- cgit From a80945e73075b8c0dc43a68ba73da65d7074e098 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 14 Jun 2017 19:36:37 -0700 Subject: Hide message on normal loading --- ui/app/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ui') 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 -- cgit