From 0ea3b8cc34ecc4fd99f1e8363df27af31b75025e Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 25 Nov 2016 16:05:11 +0100 Subject: [PATCH 01/12] Initial fetch of local transactions --- .../containers/RequestsPage/RequestsPage.js | 2 +- js/src/views/Signer/store.js | 64 ++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js index c53d392646f..abc394a861b 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js @@ -44,7 +44,7 @@ class RequestsPage extends Component { isTest: PropTypes.bool.isRequired }; - store = new Store(this.context.api); + store = new Store(this.context.api, true); render () { const { pending, finished } = this.props.signer; diff --git a/js/src/views/Signer/store.js b/js/src/views/Signer/store.js index 1bb63bbe260..1e79ce7c6d1 100644 --- a/js/src/views/Signer/store.js +++ b/js/src/views/Signer/store.js @@ -14,13 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import BigNumber from 'bignumber.js'; +import { isEqual } from 'lodash'; import { action, observable } from 'mobx'; export default class Store { @observable balances = {}; + @observable localTransactions = []; - constructor (api) { + constructor (api, withLocalTransactions = false) { this._api = api; + + if (withLocalTransactions) { + this.fetchLocalTransactions(); + } } @action setBalance = (address, balance) => { @@ -31,6 +38,12 @@ export default class Store { this.balances = Object.assign({}, this.balances, balances); } + @action setLocalTransactions = (localTransactions) => { + if (!isEqual(localTransactions, this.localTransactions)) { + this.localTransactions = localTransactions; + } + } + fetchBalance (address) { this._api.eth .getBalance(address) @@ -63,4 +76,53 @@ export default class Store { console.warn('Store:fetchBalances', error); }); } + + fetchLocalTransactions = () => { + const nextTimeout = () => { + setTimeout(this.fetchLocalTransactions, 1500); + }; + + Promise + .all([ + this._api.parity.pendingTransactions(), + this._api.parity.pendingTransactionsStats(), + this._api.parity.localTransactions() + ]) + .then(([pending, stats, local]) => { + pending + .filter((transaction) => local[transaction.hash]) + .forEach((transaction) => { + local[transaction.hash].transaction = transaction; + local[transaction.hash].stats = stats[transaction.hash].stats; + }); + + const localTransactions = Object + .keys(local) + .map((hash) => { + const data = local[hash]; + + data.txHash = hash; + return data; + }); + + localTransactions.sort((a, b) => { + a = a.transaction || {}; + b = b.transaction || {}; + + if (a.from && b.from && a.from !== b.from) { + return a.from < b.from; + } + + if (!a.nonce || !b.nonce) { + return !a.nonce ? 1 : -1; + } + + return new BigNumber(a.nonce || 0).cmp(new BigNumber(b.nonce || 0)); + }); + + this.setLocalTransactions(localTransactions); + }) + .then(nextTimeout) + .catch(nextTimeout); + } } From e4d432ba0e816f6c057b8c4827b58272c450f4f1 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 27 Nov 2016 11:56:43 +0100 Subject: [PATCH 02/12] Container allows for title specification --- js/src/ui/Container/container.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/js/src/ui/Container/container.js b/js/src/ui/Container/container.js index 143115f45e0..55b0b838e3a 100644 --- a/js/src/ui/Container/container.js +++ b/js/src/ui/Container/container.js @@ -17,6 +17,8 @@ import React, { Component, PropTypes } from 'react'; import { Card } from 'material-ui/Card'; +import Title from './Title'; + import styles from './container.css'; export default class Container extends Component { @@ -25,7 +27,10 @@ export default class Container extends Component { className: PropTypes.string, compact: PropTypes.bool, light: PropTypes.bool, - style: PropTypes.object + style: PropTypes.object, + title: PropTypes.oneOfType([ + PropTypes.string, PropTypes.node + ]) } render () { @@ -35,9 +40,22 @@ export default class Container extends Component { return (
+ { this.renderTitle() } { children }
); } + + renderTitle () { + const { title } = this.props; + + if (!title) { + return null; + } + + return ( + + ); + } } From 59aa24ae1dac6d7452f7470f43cb3f715eefc197 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Sun, 27 Nov 2016 11:57:18 +0100 Subject: [PATCH 03/12] Introduce TxList component --- .../Transaction => ui/TxList}/index.js | 2 +- js/src/ui/TxList/store.js | 100 +++++++++ js/src/ui/TxList/txList.css | 81 ++++++++ js/src/ui/TxList/txList.js | 173 ++++++++++++++++ js/src/ui/index.js | 4 +- .../Transactions/Transaction/transaction.js | 191 ------------------ .../Account/Transactions/transactions.css | 71 ------- .../Account/Transactions/transactions.js | 83 ++------ 8 files changed, 370 insertions(+), 335 deletions(-) rename js/src/{views/Account/Transactions/Transaction => ui/TxList}/index.js (94%) create mode 100644 js/src/ui/TxList/store.js create mode 100644 js/src/ui/TxList/txList.css create mode 100644 js/src/ui/TxList/txList.js delete mode 100644 js/src/views/Account/Transactions/Transaction/transaction.js diff --git a/js/src/views/Account/Transactions/Transaction/index.js b/js/src/ui/TxList/index.js similarity index 94% rename from js/src/views/Account/Transactions/Transaction/index.js rename to js/src/ui/TxList/index.js index 28a39ad4680..820d4b3aae4 100644 --- a/js/src/views/Account/Transactions/Transaction/index.js +++ b/js/src/ui/TxList/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -export default from './transaction'; +export default from './txList'; diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js new file mode 100644 index 00000000000..9643c5f7950 --- /dev/null +++ b/js/src/ui/TxList/store.js @@ -0,0 +1,100 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see <http://www.gnu.org/licenses/>. + +import BigNumber from 'bignumber.js'; +import { action, observable, transaction } from 'mobx'; + +export default class Store { + @observable blocks = {}; + @observable transactions = []; + + constructor (api) { + this._api = api; + this._transactions = {}; + } + + @action addBlocks = (blocks) => { + this.blocks = Object.assign({}, this.blocks, blocks); + } + + @action addTransactions = (transactionsArray) => { + transaction(() => { + this._transactions = Object.assign(this._transactions, transactionsArray.reduce((txs, tx) => { + txs[tx.hash] = tx; + return txs; + }, {})); + + this.transactions = Object + .keys(this._transactions) + .sort((ahash, bhash) => { + const bnA = this._transactions[ahash].blockNumber || new BigNumber(0); + const bnB = this._transactions[bhash].blockNumber || new BigNumber(0); + + return bnB.comparedTo(bnA); + }) + .map((txhash) => this._transactions[txhash]); + }); + } + + loadTransactions (_hashes) { + const hashes = _hashes.filter((txhash) => !this._transactions[txhash]); + + if (!hashes || !hashes.length) { + return; + } + + Promise + .all(hashes.map((txhash) => this._api.eth.getTransactionByHash(txhash))) + .then((transactions) => { + this.loadBlocks(transactions.map((transaction) => transaction.blockNumber)); + this.addTransactions(transactions); + }) + .catch((error) => { + console.warn('loadTransactions', error); + }); + } + + loadBlocks (_blockNumbers) { + const blockNumbers = Object + .keys( + _blockNumbers + .filter((blockNumber) => blockNumber) + .reduce((blockNumbers, blockNumber) => { + blockNumbers[blockNumber.toNumber()] = true; + return blockNumbers; + }, {}) + ) + .filter((bn) => !this.blocks[bn]); + + if (!blockNumbers || !blockNumbers.length) { + return; + } + + Promise + .all(blockNumbers.map((blockNumber) => this._api.eth.getBlockByNumber(blockNumber))) + .then((_blocks) => { + this.addBlocks( + blockNumbers.reduce((blocks, blockNumber, index) => { + blocks[blockNumber] = _blocks[index]; + return blocks; + }, {}) + ); + }) + .catch((error) => { + console.warn('loadBlocks', error); + }); + } +} diff --git a/js/src/ui/TxList/txList.css b/js/src/ui/TxList/txList.css new file mode 100644 index 00000000000..ebe672bf87f --- /dev/null +++ b/js/src/ui/TxList/txList.css @@ -0,0 +1,81 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see <http://www.gnu.org/licenses/>. +*/ + +.transactions { + width: 100%; + border-collapse: collapse; + + tr { + line-height: 32px; + vertical-align: top; + + &:nth-child(even) { + background: rgba(255, 255, 255, 0.04); + } + } + + th { + color: #aaa; + text-align: center; + } + + td { + vertical-align: top; + padding: 0.75em 0.75em; + + &.method { + width: 40%; + } + + &.timestamp { + padding-top: 1.5em; + text-align: right; + line-height: 1.5em; + opacity: 0.5; + } + + &.transaction { + padding-top: 1.5em; + text-align: center; + + & div { + line-height: 1.25em; + min-height: 1.25em; + } + } + } + + .icon { + margin: 0; + } + + .link { + vertical-align: top; + } + + .right { + text-align: right; + } + + .center { + text-align: center; + } + + .left { + text-align: left; + } +} diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js new file mode 100644 index 00000000000..28fc5058863 --- /dev/null +++ b/js/src/ui/TxList/txList.js @@ -0,0 +1,173 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see <http://www.gnu.org/licenses/>. + +import moment from 'moment'; +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { observer } from 'mobx-react'; + +import { txLink, addressLink } from '../../3rdparty/etherscan/links'; + +import IdentityIcon from '../IdentityIcon'; +import IdentityName from '../IdentityName'; +import MethodDecoding from '../MethodDecoding'; +import Store from './store'; + +import styles from './txList.css'; + +@observer +class TxList extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + } + + static propTypes = { + address: PropTypes.string.isRequired, + txhashes: PropTypes.array.isRequired, + isTest: PropTypes.bool.isRequired + } + + store = new Store(this.context.api); + + componentWillMount () { + this.store.loadTransactions(this.props.txhashes); + } + + componentWillReceiveProps (newProps) { + this.store.loadTransactions(newProps.txhashes); + } + + render () { + return ( + <table className={ styles.transactions }> + <tbody> + { this.renderRows() } + </tbody> + </table> + ); + } + + renderRows () { + const { address, isTest } = this.props; + + const rows = this.store.transactions.map((transaction) => { + return ( + <tr key={ transaction.hash }> + <td className={ styles.timestamp }> + <div>{ this.formatBlockTimestamp(transaction.blockNumber) }</div> + <div>{ transaction.blockNumber.toFormat() }</div> + </td> + { this.renderAddress(transaction.from) } + <td className={ styles.transaction }> + { this.renderEtherValue(transaction.value) } + <div>⇒</div> + <div> + <a + className={ styles.link } + href={ txLink(transaction.hash, isTest) } + target='_blank'> + { `${transaction.hash.substr(2, 6)}...${transaction.hash.slice(-6)}` } + </a> + </div> + </td> + { this.renderAddress(transaction.to) } + <td className={ styles.method }> + <MethodDecoding + historic + address={ address } + transaction={ transaction } /> + </td> + </tr> + ); + }); + + console.log(rows); + return rows; + } + + renderAddress (address) { + const { isTest } = this.props; + + let esLink = null; + if (address) { + esLink = ( + <a + href={ addressLink(address, isTest) } + target='_blank' + className={ styles.link }> + <IdentityName address={ address } shorten /> + </a> + ); + } + + return ( + <td className={ styles.address }> + <div className={ styles.center }> + <IdentityIcon + center + className={ styles.icon } + address={ address } /> + </div> + <div className={ styles.center }> + { esLink || 'DEPLOY' } + </div> + </td> + ); + } + + renderEtherValue (_value) { + const { api } = this.context; + const value = api.util.fromWei(_value); + + if (value.eq(0)) { + return <div className={ styles.value }>{ ' ' }</div>; + } + + return ( + <div className={ styles.value }> + { value.toFormat(5) }<small>ETH</small> + </div> + ); + } + + formatBlockTimestamp (blockNumber) { + const block = this.store.blocks[blockNumber.toNumber()]; + + if (!block) { + return null; + } + + return moment(block.timestamp).fromNow(); + } +} + +function mapStateToProps (state) { + const { isTest } = state.nodeStatus; + + return { + isTest + }; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({}, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TxList); diff --git a/js/src/ui/index.js b/js/src/ui/index.js index d443d0dbc75..94a83db9136 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -41,6 +41,7 @@ import SignerIcon from './SignerIcon'; import Tags from './Tags'; import Tooltips, { Tooltip } from './Tooltips'; import TxHash from './TxHash'; +import TxList from './TxList'; export { Actionbar, @@ -83,5 +84,6 @@ export { Tags, Tooltip, Tooltips, - TxHash + TxHash, + TxList }; diff --git a/js/src/views/Account/Transactions/Transaction/transaction.js b/js/src/views/Account/Transactions/Transaction/transaction.js deleted file mode 100644 index d84917d6b57..00000000000 --- a/js/src/views/Account/Transactions/Transaction/transaction.js +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see <http://www.gnu.org/licenses/>. - -import BigNumber from 'bignumber.js'; -import React, { Component, PropTypes } from 'react'; -import moment from 'moment'; - -import { IdentityIcon, IdentityName, MethodDecoding } from '../../../../ui'; -import { txLink, addressLink } from '../../../../3rdparty/etherscan/links'; - -import styles from '../transactions.css'; - -export default class Transaction extends Component { - static contextTypes = { - api: PropTypes.object.isRequired - } - - static propTypes = { - address: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, - transaction: PropTypes.object.isRequired - } - - state = { - isContract: false, - isReceived: false, - transaction: null, - block: null - } - - componentDidMount () { - this.lookup(); - } - - render () { - const { block } = this.state; - const { transaction } = this.props; - - return ( - <tr> - <td className={ styles.timestamp }> - <div>{ this.formatBlockTimestamp(block) }</div> - <div>{ this.formatNumber(transaction.blockNumber) }</div> - </td> - { this.renderAddress(transaction.from) } - { this.renderTransaction() } - { this.renderAddress(transaction.to) } - <td className={ styles.method }> - { this.renderMethod() } - </td> - </tr> - ); - } - - renderMethod () { - const { address } = this.props; - const { transaction } = this.state; - - if (!transaction) { - return null; - } - - return ( - <MethodDecoding - historic - address={ address } - transaction={ transaction } /> - ); - } - - renderTransaction () { - const { isTest } = this.props; - const { transaction } = this.props; - - return ( - <td className={ styles.transaction }> - { this.renderEtherValue() } - <div>⇒</div> - <div> - <a - className={ styles.link } - href={ txLink(transaction.hash, isTest) } - target='_blank' - > - { this.formatHash(transaction.hash) } - </a> - </div> - </td> - ); - } - - renderAddress (address) { - const { isTest } = this.props; - - const eslink = address ? ( - <a - href={ addressLink(address, isTest) } - target='_blank' - className={ styles.link }> - <IdentityName address={ address } shorten /> - </a> - ) : 'DEPLOY'; - - return ( - <td className={ styles.address }> - <div className={ styles.center }> - <IdentityIcon - center - className={ styles.icon } - address={ address } /> - </div> - <div className={ styles.center }> - { eslink } - </div> - </td> - ); - } - - renderEtherValue () { - const { api } = this.context; - const { transaction } = this.state; - - if (!transaction) { - return null; - } - - const value = api.util.fromWei(transaction.value); - - if (value.eq(0)) { - return <div className={ styles.value }>{ ' ' }</div>; - } - - return ( - <div className={ styles.value }> - { value.toFormat(5) }<small>ETH</small> - </div> - ); - } - - formatHash (hash) { - if (!hash || hash.length <= 16) { - return hash; - } - - return `${hash.substr(2, 6)}...${hash.slice(-6)}`; - } - - formatNumber (number) { - return new BigNumber(number).toFormat(); - } - - formatBlockTimestamp (block) { - if (!block) { - return null; - } - - return moment(block.timestamp).fromNow(); - } - - lookup () { - const { api } = this.context; - const { transaction, address } = this.props; - - this.setState({ isReceived: address === transaction.to }); - - Promise - .all([ - api.eth.getBlockByNumber(transaction.blockNumber), - api.eth.getTransactionByHash(transaction.hash) - ]) - .then(([block, transaction]) => { - this.setState({ block, transaction }); - }) - .catch((error) => { - console.warn('lookup', error); - }); - } -} diff --git a/js/src/views/Account/Transactions/transactions.css b/js/src/views/Account/Transactions/transactions.css index 62411d8f2cf..13d727deba1 100644 --- a/js/src/views/Account/Transactions/transactions.css +++ b/js/src/views/Account/Transactions/transactions.css @@ -14,49 +14,10 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see <http://www.gnu.org/licenses/>. */ -.right { - text-align: right; -} - -.center { - text-align: center; -} - -.left { - text-align: left; -} .transactions { } -.transactions table { - width: 100%; - border-collapse: collapse; -} - -.transactions tr { - line-height: 32px; - vertical-align: top; -} - -.transactions tr:nth-child(even) { - background: rgba(255, 255, 255, 0.04); -} - -.transactions th { - color: #aaa; - text-align: center; -} - -.transactions td { - vertical-align: top; - padding: 0.75em 0.75em; -} - -.transactions .link { - vertical-align: top; -} - .infonone { opacity: 0.25; } @@ -67,35 +28,3 @@ font-size: 0.75em; color: #aaa; } - -.address { - text-align: center; -} - -.transaction { - text-align: center; -} - -.transactions td.transaction { - padding-top: 1.5em; -} - -.transaction div { - line-height: 1.25em; - min-height: 1.25em; -} - -.icon { - margin: 0; -} - -.method { - width: 40%; -} - -.transactions td.timestamp { - padding-top: 1.5em; - text-align: right; - line-height: 1.5em; - opacity: 0.5; -} diff --git a/js/src/views/Account/Transactions/transactions.js b/js/src/views/Account/Transactions/transactions.js index 2261284a1c8..b6e75d4519a 100644 --- a/js/src/views/Account/Transactions/transactions.js +++ b/js/src/views/Account/Transactions/transactions.js @@ -17,12 +17,9 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import LinearProgress from 'material-ui/LinearProgress'; import etherscan from '../../../3rdparty/etherscan'; -import { Container, ContainerTitle } from '../../../ui'; - -import Transaction from './Transaction'; +import { Container, TxList } from '../../../ui'; import styles from './transactions.css'; @@ -33,16 +30,12 @@ class Transactions extends Component { static propTypes = { address: PropTypes.string.isRequired, - accounts: PropTypes.object, - contacts: PropTypes.object, - contracts: PropTypes.object, - tokens: PropTypes.object, isTest: PropTypes.bool, traceMode: PropTypes.bool } state = { - transactions: [], + txhashes: [], loading: true, callInfo: {} } @@ -67,38 +60,16 @@ class Transactions extends Component { } render () { - return ( - <Container> - <ContainerTitle title='transactions' /> - { this.renderTransactions() } - </Container> - ); - } - - renderTransactions () { - const { loading, transactions } = this.state; - - if (loading) { - return ( - <LinearProgress mode='indeterminate' /> - ); - } else if (!transactions.length) { - return ( - <div className={ styles.infonone }> - No transactions were found for this account - </div> - ); - } + const { address } = this.props; + const { txhashes } = this.state; return ( - <div className={ styles.transactions }> - <table> - <tbody> - { this.renderRows() } - </tbody> - </table> + <Container title='transactions'> + <TxList + address={ address } + txhashes={ txhashes } /> { this.renderEtherscanFooter() } - </div> + </Container> ); } @@ -116,30 +87,6 @@ class Transactions extends Component { ); } - renderRows () { - const { address, accounts, contacts, contracts, tokens, isTest } = this.props; - const { transactions } = this.state; - - return (transactions || []) - .sort((tA, tB) => { - return tB.blockNumber.comparedTo(tA.blockNumber); - }) - .slice(0, 25) - .map((transaction, index) => { - return ( - <Transaction - key={ index } - transaction={ transaction } - address={ address } - accounts={ accounts } - contacts={ contacts } - contracts={ contracts } - tokens={ tokens } - isTest={ isTest } /> - ); - }); - } - getTransactions = (props) => { const { isTest, address, traceMode } = props; @@ -151,9 +98,9 @@ class Transactions extends Component { return this .fetchTransactions(isTest, address, traceMode) - .then(transactions => { + .then((transactions) => { this.setState({ - transactions, + txhashes: transactions.map((transaction) => transaction.hash), loading: false }); }); @@ -204,16 +151,10 @@ class Transactions extends Component { function mapStateToProps (state) { const { isTest, traceMode } = state.nodeStatus; - const { accounts, contacts, contracts } = state.personal; - const { tokens } = state.balances; return { isTest, - traceMode, - accounts, - contacts, - contracts, - tokens + traceMode }; } From 4d1a14fb1b888cc8b41814b880393e1aae21b161 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Sun, 27 Nov 2016 12:14:47 +0100 Subject: [PATCH 04/12] Display local transactions in signer list --- js/src/ui/TxList/txList.js | 11 ++--- .../Account/Transactions/transactions.js | 8 ++-- .../containers/RequestsPage/RequestsPage.js | 42 ++++++++++++++----- js/src/views/Signer/signer.js | 6 +-- js/src/views/Signer/store.js | 9 +++- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index 28fc5058863..089c5aa6f5b 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -37,18 +37,18 @@ class TxList extends Component { static propTypes = { address: PropTypes.string.isRequired, - txhashes: PropTypes.array.isRequired, + hashes: PropTypes.array.isRequired, isTest: PropTypes.bool.isRequired } store = new Store(this.context.api); componentWillMount () { - this.store.loadTransactions(this.props.txhashes); + this.store.loadTransactions(this.props.hashes); } componentWillReceiveProps (newProps) { - this.store.loadTransactions(newProps.txhashes); + this.store.loadTransactions(newProps.hashes); } render () { @@ -64,7 +64,7 @@ class TxList extends Component { renderRows () { const { address, isTest } = this.props; - const rows = this.store.transactions.map((transaction) => { + return this.store.transactions.map((transaction) => { return ( <tr key={ transaction.hash }> <td className={ styles.timestamp }> @@ -94,9 +94,6 @@ class TxList extends Component { </tr> ); }); - - console.log(rows); - return rows; } renderAddress (address) { diff --git a/js/src/views/Account/Transactions/transactions.js b/js/src/views/Account/Transactions/transactions.js index b6e75d4519a..8a205b34355 100644 --- a/js/src/views/Account/Transactions/transactions.js +++ b/js/src/views/Account/Transactions/transactions.js @@ -35,7 +35,7 @@ class Transactions extends Component { } state = { - txhashes: [], + hashes: [], loading: true, callInfo: {} } @@ -61,13 +61,13 @@ class Transactions extends Component { render () { const { address } = this.props; - const { txhashes } = this.state; + const { hashes } = this.state; return ( <Container title='transactions'> <TxList address={ address } - txhashes={ txhashes } /> + hashes={ hashes } /> { this.renderEtherscanFooter() } </Container> ); @@ -100,7 +100,7 @@ class Transactions extends Component { .fetchTransactions(isTest, address, traceMode) .then((transactions) => { this.setState({ - txhashes: transactions.map((transaction) => transaction.hash), + hashes: transactions.map((transaction) => transaction.hash), loading: false }); }); diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js index abc394a861b..174e701a273 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js @@ -18,15 +18,17 @@ import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; +import { observer } from 'mobx-react'; import Store from '../../store'; import * as RequestsActions from '../../../../redux/providers/signerActions'; -import { Container, ContainerTitle } from '../../../../ui'; +import { Container, Page, TxList } from '../../../../ui'; import { RequestPending, RequestFinished } from '../../components'; import styles from './RequestsPage.css'; +@observer class RequestsPage extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -46,18 +48,24 @@ class RequestsPage extends Component { store = new Store(this.context.api, true); + componentWillUnmount () { + this.store.unsubscribe(); + } + render () { const { pending, finished } = this.props.signer; + const { localTransactions } = this.store; - if (!pending.length && !finished.length) { + if (!pending.length && !finished.length && !localTransactions.length) { return this.renderNoRequestsMsg(); } return ( - <div> - { this.renderPendingRequests() } - { this.renderFinishedRequests() } - </div> + <Page> + <div>{ this.renderPendingRequests() }</div> + <div>{ this.renderLocalQueue() }</div> + <div>{ this.renderFinishedRequests() }</div> + </Page> ); } @@ -65,6 +73,22 @@ class RequestsPage extends Component { return new BigNumber(b.id).cmp(a.id); } + renderLocalQueue () { + const { localTransactions } = this.store; + + if (!localTransactions.length) { + return null; + } + + return ( + <Container title='Local Transactions'> + <TxList + address='' + hashes={ localTransactions.map((tx) => tx.transaction.hash) } /> + </Container> + ); + } + renderPendingRequests () { const { pending } = this.props.signer; @@ -75,8 +99,7 @@ class RequestsPage extends Component { const items = pending.sort(this._sortRequests).map(this.renderPending); return ( - <Container> - <ContainerTitle title='Pending Requests' /> + <Container title='Pending Requests'> <div className={ styles.items }> { items } </div> @@ -94,8 +117,7 @@ class RequestsPage extends Component { const items = finished.sort(this._sortRequests).map(this.renderFinished); return ( - <Container> - <ContainerTitle title='Finished Requests' /> + <Container title='Finished Requests'> <div className={ styles.items }> { items } </div> diff --git a/js/src/views/Signer/signer.js b/js/src/views/Signer/signer.js index b9aace59530..32aca9c82f9 100644 --- a/js/src/views/Signer/signer.js +++ b/js/src/views/Signer/signer.js @@ -16,7 +16,7 @@ import React, { Component } from 'react'; -import { Actionbar, Page } from '../../ui'; +import { Actionbar } from '../../ui'; import RequestsPage from './containers/RequestsPage'; import styles from './signer.css'; @@ -27,9 +27,7 @@ export default class Signer extends Component { <div className={ styles.signer }> <Actionbar title='Trusted Signer' /> - <Page> - <RequestsPage /> - </Page> + <RequestsPage /> </div> ); } diff --git a/js/src/views/Signer/store.js b/js/src/views/Signer/store.js index 1e79ce7c6d1..15119b659d3 100644 --- a/js/src/views/Signer/store.js +++ b/js/src/views/Signer/store.js @@ -21,6 +21,7 @@ import { action, observable } from 'mobx'; export default class Store { @observable balances = {}; @observable localTransactions = []; + @observable doPolling = true; constructor (api, withLocalTransactions = false) { this._api = api; @@ -30,6 +31,10 @@ export default class Store { } } + @action unsubscribe () { + this.doPolling = false; + } + @action setBalance = (address, balance) => { this.setBalances({ [address]: balance }); } @@ -79,7 +84,9 @@ export default class Store { fetchLocalTransactions = () => { const nextTimeout = () => { - setTimeout(this.fetchLocalTransactions, 1500); + if (this.doPolling) { + setTimeout(this.fetchLocalTransactions, 1500); + } }; Promise From 292ec270236fce5f0d435ae610551001fdfc3875 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 09:30:01 +0100 Subject: [PATCH 05/12] Simplify --- js/src/ui/TxList/store.js | 94 ++++++++++++++------------------------ js/src/ui/TxList/txList.js | 22 +++++---- 2 files changed, 46 insertions(+), 70 deletions(-) diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 9643c5f7950..80d9812a069 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import BigNumber from 'bignumber.js'; import { action, observable, transaction } from 'mobx'; export default class Store { @observable blocks = {}; - @observable transactions = []; + @observable sortedHashes = []; + @observable transactions = {}; constructor (api) { this._api = api; @@ -30,71 +30,45 @@ export default class Store { this.blocks = Object.assign({}, this.blocks, blocks); } - @action addTransactions = (transactionsArray) => { + @action addTransaction = (tx) => { transaction(() => { - this._transactions = Object.assign(this._transactions, transactionsArray.reduce((txs, tx) => { - txs[tx.hash] = tx; - return txs; - }, {})); - - this.transactions = Object - .keys(this._transactions) + this.transactions[tx.hash] = tx; + this.sortedHashes = Object + .keys(this.transactions) .sort((ahash, bhash) => { - const bnA = this._transactions[ahash].blockNumber || new BigNumber(0); - const bnB = this._transactions[bhash].blockNumber || new BigNumber(0); + const bnA = this.transactions[ahash].blockNumber; + const bnB = this.transactions[bhash].blockNumber; return bnB.comparedTo(bnA); - }) - .map((txhash) => this._transactions[txhash]); + }); }); } loadTransactions (_hashes) { - const hashes = _hashes.filter((txhash) => !this._transactions[txhash]); - - if (!hashes || !hashes.length) { - return; - } - - Promise - .all(hashes.map((txhash) => this._api.eth.getTransactionByHash(txhash))) - .then((transactions) => { - this.loadBlocks(transactions.map((transaction) => transaction.blockNumber)); - this.addTransactions(transactions); - }) - .catch((error) => { - console.warn('loadTransactions', error); - }); - } - - loadBlocks (_blockNumbers) { - const blockNumbers = Object - .keys( - _blockNumbers - .filter((blockNumber) => blockNumber) - .reduce((blockNumbers, blockNumber) => { - blockNumbers[blockNumber.toNumber()] = true; - return blockNumbers; - }, {}) - ) - .filter((bn) => !this.blocks[bn]); - - if (!blockNumbers || !blockNumbers.length) { - return; - } - - Promise - .all(blockNumbers.map((blockNumber) => this._api.eth.getBlockByNumber(blockNumber))) - .then((_blocks) => { - this.addBlocks( - blockNumbers.reduce((blocks, blockNumber, index) => { - blocks[blockNumber] = _blocks[index]; - return blocks; - }, {}) - ); - }) - .catch((error) => { - console.warn('loadBlocks', error); - }); + _hashes.forEach((txhash) => { + if (this._transactions[txhash]) { + return; + } + + this._api.eth + .getTransactionByHash(txhash) + .then((transaction) => { + const blockNumber = transaction.blockNumber.toNumber(); + this.addTransaction(transaction); + + if (this.blocks[blockNumber]) { + return; + } + + return this._api.eth + .getBlockByNumber(blockNumber) + .then((block) => { + this.addBlocks({ [blockNumber]: block }); + }); + }) + .catch((error) => { + console.warn('loadTransaction', txhash, error); + }); + }); } } diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index 089c5aa6f5b..d24f75ed606 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -64,32 +64,34 @@ class TxList extends Component { renderRows () { const { address, isTest } = this.props; - return this.store.transactions.map((transaction) => { + return this.store.sortedHashes.map((txhash) => { + const tx = this.store.transactions[txhash]; + return ( - <tr key={ transaction.hash }> + <tr key={ tx.hash }> <td className={ styles.timestamp }> - <div>{ this.formatBlockTimestamp(transaction.blockNumber) }</div> - <div>{ transaction.blockNumber.toFormat() }</div> + <div>{ this.formatBlockTimestamp(tx.blockNumber) }</div> + <div>{ tx.blockNumber.toFormat() }</div> </td> - { this.renderAddress(transaction.from) } + { this.renderAddress(tx.from) } <td className={ styles.transaction }> - { this.renderEtherValue(transaction.value) } + { this.renderEtherValue(tx.value) } <div>⇒</div> <div> <a className={ styles.link } - href={ txLink(transaction.hash, isTest) } + href={ txLink(tx.hash, isTest) } target='_blank'> - { `${transaction.hash.substr(2, 6)}...${transaction.hash.slice(-6)}` } + { `${tx.hash.substr(2, 6)}...${tx.hash.slice(-6)}` } </a> </div> </td> - { this.renderAddress(transaction.to) } + { this.renderAddress(tx.to) } <td className={ styles.method }> <MethodDecoding historic address={ address } - transaction={ transaction } /> + transaction={ tx } /> </td> </tr> ); From 0e265bd1f218108b110f03221c84be0d0acf7f59 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 10:26:29 +0100 Subject: [PATCH 06/12] Pass only hashes from calling components --- js/src/ui/TxList/store.js | 83 ++++++++++++------- js/src/ui/TxList/txList.js | 5 +- .../containers/RequestsPage/RequestsPage.js | 18 ++-- js/src/views/Signer/store.js | 52 ++---------- 4 files changed, 77 insertions(+), 81 deletions(-) diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 80d9812a069..8ce86c47913 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -23,16 +23,15 @@ export default class Store { constructor (api) { this._api = api; - this._transactions = {}; } @action addBlocks = (blocks) => { this.blocks = Object.assign({}, this.blocks, blocks); } - @action addTransaction = (tx) => { + @action addTransactions = (transactions) => { transaction(() => { - this.transactions[tx.hash] = tx; + this.transactions = Object.assign({}, this.transactions, transactions); this.sortedHashes = Object .keys(this.transactions) .sort((ahash, bhash) => { @@ -44,31 +43,57 @@ export default class Store { }); } - loadTransactions (_hashes) { - _hashes.forEach((txhash) => { - if (this._transactions[txhash]) { - return; - } - - this._api.eth - .getTransactionByHash(txhash) - .then((transaction) => { - const blockNumber = transaction.blockNumber.toNumber(); - this.addTransaction(transaction); - - if (this.blocks[blockNumber]) { - return; - } - - return this._api.eth - .getBlockByNumber(blockNumber) - .then((block) => { - this.addBlocks({ [blockNumber]: block }); - }); - }) - .catch((error) => { - console.warn('loadTransaction', txhash, error); - }); - }); + loadTransactions (_txhashes) { + const txhashes = _txhashes.filter((txhash) => !this.transactions[txhash]); + + if (!txhashes || !txhashes.length) { + return; + } + + Promise + .all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash))) + .then((transactions) => { + this.addTransactions( + transactions.reduce((transactions, tx, index) => { + transactions[txhashes[index]] = tx; + return transactions; + }, {}) + ); + + this.loadBlocks(transactions.map((tx) => tx.blockNumber.toNumber())); + }) + .catch((error) => { + console.warn('loadTransactions', error); + }); + } + + loadBlocks (_blockNumbers) { + const blockNumbers = Object.keys( + _blockNumbers.reduce((blockNumbers, bn) => { + if (!this.blocks[bn]) { + blockNumbers[bn] = true; + } + + return blockNumbers; + }, {}) + ); + + if (!blockNumbers.length) { + return; + } + + Promise + .all(blockNumbers.map((blockNumber) => this._api.eth.getBlockByNumber(blockNumber))) + .then((blocks) => { + this.addBlocks( + blocks.reduce((blocks, block, index) => { + blocks[blockNumbers[index]] = block; + return blocks; + }, {}) + ); + }) + .catch((error) => { + console.warn('loadBlocks', error); + }); } } diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index d24f75ed606..cd55da1f113 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -37,7 +37,10 @@ class TxList extends Component { static propTypes = { address: PropTypes.string.isRequired, - hashes: PropTypes.array.isRequired, + hashes: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]).isRequired, isTest: PropTypes.bool.isRequired } diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js index 174e701a273..5a9f569404f 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js @@ -54,10 +54,14 @@ class RequestsPage extends Component { render () { const { pending, finished } = this.props.signer; - const { localTransactions } = this.store; - - if (!pending.length && !finished.length && !localTransactions.length) { - return this.renderNoRequestsMsg(); + const { localHashes } = this.store; + + if (!pending.length && !finished.length && !localHashes.length) { + return ( + <Page> + <div>{ this.renderNoRequestsMsg() }</div> + </Page> + ); } return ( @@ -74,9 +78,9 @@ class RequestsPage extends Component { } renderLocalQueue () { - const { localTransactions } = this.store; + const { localHashes } = this.store; - if (!localTransactions.length) { + if (!localHashes.length) { return null; } @@ -84,7 +88,7 @@ class RequestsPage extends Component { <Container title='Local Transactions'> <TxList address='' - hashes={ localTransactions.map((tx) => tx.transaction.hash) } /> + hashes={ localHashes } /> </Container> ); } diff --git a/js/src/views/Signer/store.js b/js/src/views/Signer/store.js index 15119b659d3..5b9b04f293f 100644 --- a/js/src/views/Signer/store.js +++ b/js/src/views/Signer/store.js @@ -14,13 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. -import BigNumber from 'bignumber.js'; import { isEqual } from 'lodash'; import { action, observable } from 'mobx'; export default class Store { @observable balances = {}; - @observable localTransactions = []; + @observable localHashes = []; @observable doPolling = true; constructor (api, withLocalTransactions = false) { @@ -43,9 +42,9 @@ export default class Store { this.balances = Object.assign({}, this.balances, balances); } - @action setLocalTransactions = (localTransactions) => { - if (!isEqual(localTransactions, this.localTransactions)) { - this.localTransactions = localTransactions; + @action setLocalHashes = (localHashes) => { + if (!isEqual(localHashes, this.localHashes)) { + this.localHashes = localHashes; } } @@ -89,45 +88,10 @@ export default class Store { } }; - Promise - .all([ - this._api.parity.pendingTransactions(), - this._api.parity.pendingTransactionsStats(), - this._api.parity.localTransactions() - ]) - .then(([pending, stats, local]) => { - pending - .filter((transaction) => local[transaction.hash]) - .forEach((transaction) => { - local[transaction.hash].transaction = transaction; - local[transaction.hash].stats = stats[transaction.hash].stats; - }); - - const localTransactions = Object - .keys(local) - .map((hash) => { - const data = local[hash]; - - data.txHash = hash; - return data; - }); - - localTransactions.sort((a, b) => { - a = a.transaction || {}; - b = b.transaction || {}; - - if (a.from && b.from && a.from !== b.from) { - return a.from < b.from; - } - - if (!a.nonce || !b.nonce) { - return !a.nonce ? 1 : -1; - } - - return new BigNumber(a.nonce || 0).cmp(new BigNumber(b.nonce || 0)); - }); - - this.setLocalTransactions(localTransactions); + this._api.parity + .localTransactions() + .then((localTransactions) => { + this.setLocalHashes(Object.keys(localTransactions)); }) .then(nextTimeout) .catch(nextTimeout); From 222b40c67c6b673243cf1725cff67e9e8e5a24b7 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 10:31:18 +0100 Subject: [PATCH 07/12] Simplify no pending display --- .../containers/RequestsPage/RequestsPage.js | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js index 5a9f569404f..ae2ba05fb1d 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/RequestsPage.js @@ -53,17 +53,6 @@ class RequestsPage extends Component { } render () { - const { pending, finished } = this.props.signer; - const { localHashes } = this.store; - - if (!pending.length && !finished.length && !localHashes.length) { - return ( - <Page> - <div>{ this.renderNoRequestsMsg() }</div> - </Page> - ); - } - return ( <Page> <div>{ this.renderPendingRequests() }</div> @@ -97,7 +86,13 @@ class RequestsPage extends Component { const { pending } = this.props.signer; if (!pending.length) { - return; + return ( + <Container> + <div className={ styles.noRequestsMsg }> + There are no requests requiring your confirmation. + </div> + </Container> + ); } const items = pending.sort(this._sortRequests).map(this.renderPending); @@ -169,16 +164,6 @@ class RequestsPage extends Component { /> ); } - - renderNoRequestsMsg () { - return ( - <Container> - <div className={ styles.noRequestsMsg }> - There are no requests requiring your confirmation. - </div> - </Container> - ); - } } function mapStateToProps (state) { From 61b96f9911d3c7d2437de7755579ffdf1967db33 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 16:45:21 +0100 Subject: [PATCH 08/12] Render pending blocks at the top --- js/src/ui/TxList/store.js | 17 +++++++---------- js/src/ui/TxList/txList.js | 21 ++++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 8ce86c47913..738abe259f1 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -15,6 +15,7 @@ // along with Parity. If not, see <http://www.gnu.org/licenses/>. import { action, observable, transaction } from 'mobx'; +import { uniq } from 'lodash'; export default class Store { @observable blocks = {}; @@ -38,6 +39,10 @@ export default class Store { const bnA = this.transactions[ahash].blockNumber; const bnB = this.transactions[bhash].blockNumber; + if (bnA.eq(0)) { + return bnA.eq(bnB) ? 0 : 1; + } + return bnB.comparedTo(bnA); }); }); @@ -68,17 +73,9 @@ export default class Store { } loadBlocks (_blockNumbers) { - const blockNumbers = Object.keys( - _blockNumbers.reduce((blockNumbers, bn) => { - if (!this.blocks[bn]) { - blockNumbers[bn] = true; - } - - return blockNumbers; - }, {}) - ); + const blockNumbers = uniq(_blockNumbers).filter((bn) => !this.blocks[bn]); - if (!blockNumbers.length) { + if (!blockNumbers || !blockNumbers.length) { return; } diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index cd55da1f113..73379a7964f 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -72,10 +72,7 @@ class TxList extends Component { return ( <tr key={ tx.hash }> - <td className={ styles.timestamp }> - <div>{ this.formatBlockTimestamp(tx.blockNumber) }</div> - <div>{ tx.blockNumber.toFormat() }</div> - </td> + { this.renderBlockNumber(tx.bloxkNumber) } { this.renderAddress(tx.from) } <td className={ styles.transaction }> { this.renderEtherValue(tx.value) } @@ -146,14 +143,16 @@ class TxList extends Component { ); } - formatBlockTimestamp (blockNumber) { - const block = this.store.blocks[blockNumber.toNumber()]; - - if (!block) { - return null; - } + renderBlockNumber (_blockNumber) { + const blockNumber = _blockNumber.toNumber(); + const block = this.store.blocks[blockNumber]; - return moment(block.timestamp).fromNow(); + return ( + <td className={ styles.timestamp }> + <div>{ block ? moment(block.timestamp).fromNow() : null }</div> + <div>{ blockNumber === 0 ? 'Pending' : _blockNumber.toFormat() }</div> + </td> + ); } } From edf8139ae29c5176a74cb0805cfcd2d0dc0fc339 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 16:52:21 +0100 Subject: [PATCH 09/12] Get rid of time for 0 blocks --- js/src/ui/TxList/store.js | 2 +- js/src/ui/TxList/txList.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 738abe259f1..5fd45312428 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -65,7 +65,7 @@ export default class Store { }, {}) ); - this.loadBlocks(transactions.map((tx) => tx.blockNumber.toNumber())); + this.loadBlocks(transactions.map((tx) => tx.blockNumber ? tx.blockNumber.toNumber() : 0)); }) .catch((error) => { console.warn('loadTransactions', error); diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index 73379a7964f..596034c0d00 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -72,7 +72,7 @@ class TxList extends Component { return ( <tr key={ tx.hash }> - { this.renderBlockNumber(tx.bloxkNumber) } + { this.renderBlockNumber(tx.blockNumber) } { this.renderAddress(tx.from) } <td className={ styles.transaction }> { this.renderEtherValue(tx.value) } @@ -149,8 +149,8 @@ class TxList extends Component { return ( <td className={ styles.timestamp }> - <div>{ block ? moment(block.timestamp).fromNow() : null }</div> - <div>{ blockNumber === 0 ? 'Pending' : _blockNumber.toFormat() }</div> + <div>{ blockNumber && block ? moment(block.timestamp).fromNow() : null }</div> + <div>{ blockNumber ? _blockNumber.toFormat() : 'Pending' }</div> </td> ); } From 48f3b19193f697a88baff450f4544cc61d9b21eb Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 16:56:04 +0100 Subject: [PATCH 10/12] Indeed sort Pending to the top --- js/src/ui/TxList/store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 5fd45312428..15ef37f9b30 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -39,8 +39,8 @@ export default class Store { const bnA = this.transactions[ahash].blockNumber; const bnB = this.transactions[bhash].blockNumber; - if (bnA.eq(0)) { - return bnA.eq(bnB) ? 0 : 1; + if (bnB.eq(0)) { + return bnB.eq(bnA) ? 0 : 1; } return bnB.comparedTo(bnA); From 4aee815d582e76989c0ba1bcbbb9fa12e1f223d2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 17:29:06 +0100 Subject: [PATCH 11/12] Allow retrieval of pending transactions --- js/src/ui/TxList/store.js | 37 ++++++++++++++++++++++++++++++++++++- js/src/ui/TxList/txList.js | 4 ++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 15ef37f9b30..ab35d546844 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -24,6 +24,10 @@ export default class Store { constructor (api) { this._api = api; + this._subscriptionId = 0; + this._pendingHashes = []; + + this.subscribe(); } @action addBlocks = (blocks) => { @@ -45,11 +49,42 @@ export default class Store { return bnB.comparedTo(bnA); }); + this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0)); }); } + @action clearPending () { + this._pendingHashes = []; + } + + subscribe () { + this._api + .subscribe('eth_blockNumber', (error, blockNumber) => { + if (error) { + return; + } + + if (this._pendingHashes.length) { + this.loadTransactions(this._pendingHashes); + this.clearPending(); + } + }) + .then((subscriptionId) => { + this._subscriptionId = subscriptionId; + }); + } + + unsubscribe () { + if (!this._subscriptionId) { + return; + } + + this._api.unsubscribe(this._subscriptionId); + this._subscriptionId = 0; + } + loadTransactions (_txhashes) { - const txhashes = _txhashes.filter((txhash) => !this.transactions[txhash]); + const txhashes = _txhashes.filter((hash) => !this.transactions[hash] || this._pendingHashes.includes(hash)); if (!txhashes || !txhashes.length) { return; diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index 596034c0d00..5b47c2ca8ec 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -50,6 +50,10 @@ class TxList extends Component { this.store.loadTransactions(this.props.hashes); } + componentWillUnmount () { + this.store.unsubscribe(); + } + componentWillReceiveProps (newProps) { this.store.loadTransactions(newProps.hashes); } From 031ecfa411084afcc246f2d4d2e0cdac226a34ec Mon Sep 17 00:00:00 2001 From: Jaco Greeff <jaco@ethcore.io> Date: Mon, 28 Nov 2016 17:33:05 +0100 Subject: [PATCH 12/12] setTimeout with clearTimeout --- js/src/views/Signer/store.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/src/views/Signer/store.js b/js/src/views/Signer/store.js index 5b9b04f293f..0eeb99861cb 100644 --- a/js/src/views/Signer/store.js +++ b/js/src/views/Signer/store.js @@ -20,10 +20,10 @@ import { action, observable } from 'mobx'; export default class Store { @observable balances = {}; @observable localHashes = []; - @observable doPolling = true; constructor (api, withLocalTransactions = false) { this._api = api; + this._timeoutId = 0; if (withLocalTransactions) { this.fetchLocalTransactions(); @@ -31,7 +31,9 @@ export default class Store { } @action unsubscribe () { - this.doPolling = false; + if (this._timeoutId) { + clearTimeout(this._timeoutId); + } } @action setBalance = (address, balance) => { @@ -83,9 +85,7 @@ export default class Store { fetchLocalTransactions = () => { const nextTimeout = () => { - if (this.doPolling) { - setTimeout(this.fetchLocalTransactions, 1500); - } + this._timeoutId = setTimeout(this.fetchLocalTransactions, 1500); }; this._api.parity