Align contract event log l&f with transactions (#2812)

* Event into own component, align with transactions

* Pass value & type through from event log params

* Reformat display columns
This commit is contained in:
Jaco Greeff
2016-10-25 16:02:12 +02:00
committed by GitHub
parent 8d0cff3599
commit 7eacf07629
12 changed files with 272 additions and 109 deletions

View File

@@ -0,0 +1,183 @@
// 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 moment from 'moment';
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchBlock, fetchTransaction } from '../../../../redux/providers/blockchainActions';
import { IdentityIcon, IdentityName, Input, InputAddress } from '../../../../ui';
import styles from '../../contract.css';
class Event extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
event: PropTypes.object.isRequired,
blocks: PropTypes.object,
transactions: PropTypes.object,
isTest: PropTypes.bool,
fetchBlock: PropTypes.func.isRequired,
fetchTransaction: PropTypes.func.isRequired
}
componentDidMount () {
this.retrieveTransaction();
}
render () {
const { event, blocks, transactions, isTest } = this.props;
const block = blocks[event.blockNumber.toString()];
const transaction = transactions[event.transactionHash] || {};
const classes = `${styles.event} ${styles[event.state]}`;
const url = `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${event.transactionHash}`;
const keys = Object.keys(event.params).join(', ');
const values = Object.keys(event.params).map((name, index) => {
const param = event.params[name];
return (
<div className={ styles.eventValue } key={ `${event.key}_val_${index}` }>
{ this.renderParam(name, param) }
</div>
);
});
return (
<tr className={ classes }>
<td className={ styles.timestamp }>
<div>{ event.state === 'pending' ? 'pending' : this.formatBlockTimestamp(block) }</div>
<div>{ this.formatNumber(transaction.blockNumber) }</div>
</td>
<td className={ styles.txhash }>
{ this.renderAddressName(transaction.from) }
</td>
<td className={ styles.txhash }>
<div className={ styles.eventType }>
{ event.type }({ keys })
</div>
<a href={ url } target='_blank'>{ this.formatHash(event.transactionHash) }</a>
</td>
<td className={ styles.eventDetails }>
<div className={ styles.eventParams }>
{ values }
</div>
</td>
</tr>
);
}
formatHash (hash) {
if (!hash || hash.length <= 16) {
return hash;
}
return `${hash.substr(2, 6)}...${hash.slice(-6)}`;
}
renderAddressName (address, withName = true) {
return (
<span className={ styles.eventAddress }>
<IdentityIcon center inline address={ address } className={ styles.eventIdentityicon } />
{ withName ? <IdentityName address={ address } /> : address }
</span>
);
}
renderParam (name, param) {
const { api } = this.context;
switch (param.type) {
case 'address':
return (
<InputAddress
disabled
text
className={ styles.input }
value={ param.value }
label={ name } />
);
default:
let value;
if (api.util.isInstanceOf(param.value, BigNumber)) {
value = param.value.toFormat(0);
} else if (api.util.isArray(param.value)) {
value = api.util.bytesToHex(param.value);
} else {
value = param.value.toString();
}
return (
<Input
disabled
className={ styles.input }
value={ value }
label={ name } />
);
}
}
formatBlockTimestamp (block) {
if (!block) {
return null;
}
return moment(block.timestamp).fromNow();
}
formatNumber (number) {
if (!number) {
return null;
}
return new BigNumber(number).toFormat();
}
retrieveTransaction () {
const { event, fetchBlock, fetchTransaction } = this.props;
fetchBlock(event.blockNumber);
fetchTransaction(event.transactionHash);
}
}
function mapStateToProps (state) {
const { isTest } = state.nodeStatus;
const { blocks, transactions } = state.blockchain;
return {
isTest,
blocks,
transactions
};
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
fetchBlock, fetchTransaction
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Event);

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './event';

View File

@@ -14,11 +14,11 @@
// 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 { Container, ContainerTitle } from '../../../ui';
import Event from './Event';
import styles from '../contract.css';
export default class Events extends Component {
@@ -27,106 +27,23 @@ export default class Events extends Component {
}
static propTypes = {
events: PropTypes.array,
isTest: PropTypes.bool
}
state = {
transactions: {}
}
componentDidMount () {
this.componentWillReceiveProps(this.props);
}
componentWillReceiveProps (newProps) {
this.retrieveTransactions(newProps.events);
events: PropTypes.array
}
render () {
const { events, isTest } = this.props;
const { transactions } = this.state;
const { events } = this.props;
if (!events || !events.length) {
return null;
}
const rows = events.map((event) => {
const transaction = transactions[event.transactionHash] || {};
const classes = `${styles.event} ${styles[event.state]}`;
const url = `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${event.transactionHash}`;
const keys = Object.keys(event.params).map((key, index) => {
return <div className={ styles.key } key={ `${event.key}_key_${index}` }>{ key }</div>;
});
const values = Object.values(event.params).map((value, index) => {
return (
<div className={ styles.value } key={ `${event.key}_val_${index}` }>
{ this.renderValue(value) }
</div>
);
});
return (
<tr className={ classes } key={ event.key }>
<td>{ event.state === 'pending' ? 'pending' : event.blockNumber.toFormat(0) }</td>
<td className={ styles.txhash }>
<div>{ transaction.from }</div>
<a href={ url } target='_blank'>{ event.transactionHash }</a>
</td>
<td>
<div>{ event.type } =></div>
{ keys }
</td>
<td>
<div>&nbsp;</div>
{ values }
</td>
</tr>
);
});
return (
<Container>
<ContainerTitle title='events' />
<table className={ styles.events }>
<tbody>{ rows }</tbody>
<tbody>{ events.map((event) => <Event event={ event } key={ event.key } />) }</tbody>
</table>
</Container>
);
}
renderValue (value) {
const { api } = this.context;
if (api.util.isInstanceOf(value, BigNumber)) {
return value.toFormat(0);
} else if (api.util.isArray(value)) {
return api.util.bytesToHex(value);
}
return value.toString();
}
retrieveTransactions (events) {
const { api } = this.context;
const { transactions } = this.state;
const hashes = {};
events.forEach((event) => {
if (!hashes[event.transactionHash] && !transactions[event.transactionHash]) {
hashes[event.transactionHash] = true;
}
});
Promise
.all(Object.keys(hashes).map((hash) => api.eth.getTransactionByHash(hash)))
.then((newTransactions) => {
this.setState({
transactions: newTransactions.reduce((store, transaction) => {
transactions[transaction.hash] = transaction;
return transactions;
}, transactions)
});
});
}
}

View File

@@ -24,9 +24,12 @@
border-spacing: 0;
}
.event {
.events tr {
line-height: 32px;
vertical-align: top;
line-height: 26px;
}
.event {
}
.event td {
@@ -43,9 +46,6 @@
color: #aaa;
}
.value {
}
.event td div {
white-space: nowrap;
}
@@ -56,3 +56,35 @@
.pending {
opacity: 0.5;
}
.timestamp {
padding-top: 1.5em;
text-align: right;
line-height: 1.5em;
opacity: 0.5;
white-space: nowrap;
}
.eventDetails {
}
.eventType {
}
.eventParams {
padding-left: 2em;
}
.eventValue {
margin-top: -16px;
}
.eventAddress {
display: inline-block;
position: relative;
}
.eventIdentityicon {
margin-bottom: -10px;
margin-right: 0.5em;
}

View File

@@ -116,7 +116,6 @@ class Contract extends Component {
contract={ contract }
values={ queryValues } />
<Events
isTest={ isTest }
events={ allEvents } />
</Page>
</div>