Merge pull request #3498 from ethcore/jg-ghh-info-display
Better GHH event display & tracking
This commit is contained in:
commit
a97e08fbc8
@ -15,12 +15,17 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.container {
|
.body {
|
||||||
|
text-align: center;
|
||||||
background: #333;
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding: 4em 0;
|
padding: 4em 0;
|
||||||
text-align: center;
|
margin: 0 0 2em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
@ -98,7 +103,7 @@
|
|||||||
color: #333;
|
color: #333;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 0.5em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -113,20 +118,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hashError, .hashWarning, .hashOk {
|
.hashError, .hashWarning, .hashOk {
|
||||||
padding-top: 0.5em;
|
margin: 0.5em 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding: 1em 0;
|
||||||
|
border: 0.25em solid #333;
|
||||||
|
border-radius: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hashError {
|
.hashError {
|
||||||
|
border-color: #f66;
|
||||||
color: #f66;
|
color: #f66;
|
||||||
|
background: rgba(255, 102, 102, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hashWarning {
|
.hashWarning {
|
||||||
|
border-color: #f80;
|
||||||
color: #f80;
|
color: #f80;
|
||||||
|
background: rgba(255, 236, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hashOk {
|
.hashOk {
|
||||||
opacity: 0.5;
|
border-color: #6f6;
|
||||||
|
color: #6f6;
|
||||||
|
background: rgba(102, 255, 102, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typeButtons {
|
.typeButtons {
|
||||||
|
@ -19,6 +19,7 @@ import React, { Component } from 'react';
|
|||||||
import { api } from '../parity';
|
import { api } from '../parity';
|
||||||
import { attachInterface } from '../services';
|
import { attachInterface } from '../services';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
|
import Events from '../Events';
|
||||||
import IdentityIcon from '../IdentityIcon';
|
import IdentityIcon from '../IdentityIcon';
|
||||||
import Loading from '../Loading';
|
import Loading from '../Loading';
|
||||||
|
|
||||||
@ -27,6 +28,8 @@ import styles from './application.css';
|
|||||||
const INVALID_URL_HASH = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
|
const INVALID_URL_HASH = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
|
||||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
|
|
||||||
|
let nextEventId = 0;
|
||||||
|
|
||||||
export default class Application extends Component {
|
export default class Application extends Component {
|
||||||
state = {
|
state = {
|
||||||
fromAddress: null,
|
fromAddress: null,
|
||||||
@ -43,7 +46,9 @@ export default class Application extends Component {
|
|||||||
registerState: '',
|
registerState: '',
|
||||||
registerType: 'file',
|
registerType: 'file',
|
||||||
repo: '',
|
repo: '',
|
||||||
repoError: null
|
repoError: null,
|
||||||
|
events: {},
|
||||||
|
eventIds: []
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@ -75,7 +80,7 @@ export default class Application extends Component {
|
|||||||
let hashClass = null;
|
let hashClass = null;
|
||||||
if (contentHashError) {
|
if (contentHashError) {
|
||||||
hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning;
|
hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning;
|
||||||
} else {
|
} else if (contentHash) {
|
||||||
hashClass = styles.hashOk;
|
hashClass = styles.hashOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,29 +121,34 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.body }>
|
||||||
<div className={ styles.form }>
|
<div className={ styles.container }>
|
||||||
<div className={ styles.typeButtons }>
|
<div className={ styles.form }>
|
||||||
<Button
|
<div className={ styles.typeButtons }>
|
||||||
disabled={ registerBusy }
|
<Button
|
||||||
invert={ registerType !== 'file' }
|
disabled={ registerBusy }
|
||||||
onClick={ this.onClickTypeNormal }>File Link</Button>
|
invert={ registerType !== 'file' }
|
||||||
<Button
|
onClick={ this.onClickTypeNormal }>File Link</Button>
|
||||||
disabled={ registerBusy }
|
<Button
|
||||||
invert={ registerType !== 'content' }
|
disabled={ registerBusy }
|
||||||
onClick={ this.onClickTypeContent }>Content Bundle</Button>
|
invert={ registerType !== 'content' }
|
||||||
</div>
|
onClick={ this.onClickTypeContent }>Content Bundle</Button>
|
||||||
<div className={ styles.box }>
|
|
||||||
<div className={ styles.description }>
|
|
||||||
Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc.
|
|
||||||
</div>
|
</div>
|
||||||
{ valueInputs }
|
<div className={ styles.box }>
|
||||||
<div className={ hashClass }>
|
<div className={ styles.description }>
|
||||||
{ contentHashError || contentHash }
|
Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc.
|
||||||
|
</div>
|
||||||
|
{ valueInputs }
|
||||||
|
<div className={ hashClass }>
|
||||||
|
{ contentHashError || contentHash }
|
||||||
|
</div>
|
||||||
|
{ registerBusy ? this.renderProgress() : this.renderButtons() }
|
||||||
</div>
|
</div>
|
||||||
{ registerBusy ? this.renderProgress() : this.renderButtons() }
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Events
|
||||||
|
eventIds={ this.state.eventIds }
|
||||||
|
events={ this.state.events } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -285,15 +295,29 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackRequest (promise) {
|
trackRequest (eventId, promise) {
|
||||||
return promise
|
return promise
|
||||||
.then((signerRequestId) => {
|
.then((signerRequestId) => {
|
||||||
this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' });
|
this.setState({
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: Object.assign({}, this.state.events[eventId], {
|
||||||
|
signerRequestId,
|
||||||
|
registerState: 'Transaction posted, Waiting for transaction authorization'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return api.pollMethod('parity_checkRequest', signerRequestId);
|
return api.pollMethod('parity_checkRequest', signerRequestId);
|
||||||
})
|
})
|
||||||
.then((txHash) => {
|
.then((txHash) => {
|
||||||
this.setState({ txHash, registerState: 'Transaction authorized, Waiting for network confirmations' });
|
this.setState({
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: Object.assign({}, this.state.events[eventId], {
|
||||||
|
txHash,
|
||||||
|
registerState: 'Transaction authorized, Waiting for network confirmations'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return api.pollMethod('eth_getTransactionReceipt', txHash, (receipt) => {
|
return api.pollMethod('eth_getTransactionReceipt', txHash, (receipt) => {
|
||||||
if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) {
|
if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) {
|
||||||
@ -304,27 +328,72 @@ export default class Application extends Component {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((txReceipt) => {
|
.then((txReceipt) => {
|
||||||
this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', commit: '', repo: '', commitError: null, contentHash: '', contentHashOwner: null, contentHashError: null });
|
this.setState({
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: Object.assign({}, this.state.events[eventId], {
|
||||||
|
txReceipt,
|
||||||
|
registerBusy: false,
|
||||||
|
registerState: 'Network confirmed, Received transaction receipt'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('onSend', error);
|
console.error('onSend', error);
|
||||||
this.setState({ registerError: error.message });
|
|
||||||
|
this.setState({
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: Object.assign({}, this.state.events[eventId], {
|
||||||
|
registerState: error.message,
|
||||||
|
registerError: true,
|
||||||
|
registerBusy: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerContent (repo, commit) {
|
registerContent (contentRepo, contentCommit) {
|
||||||
const { contentHash, fromAddress, instance } = this.state;
|
const { contentHash, fromAddress, instance } = this.state;
|
||||||
|
contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`;
|
||||||
|
|
||||||
this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' });
|
const eventId = nextEventId++;
|
||||||
|
const values = [contentHash, contentRepo, contentCommit];
|
||||||
const values = [contentHash, repo, commit.substr(0, 2) === '0x' ? commit : `0x${commit}`];
|
|
||||||
const options = { from: fromAddress };
|
const options = { from: fromAddress };
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
eventIds: [eventId].concat(this.state.eventIds),
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: {
|
||||||
|
contentHash,
|
||||||
|
contentRepo,
|
||||||
|
contentCommit,
|
||||||
|
fromAddress,
|
||||||
|
registerBusy: true,
|
||||||
|
registerState: 'Estimating gas for the transaction',
|
||||||
|
timestamp: new Date()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
url: '',
|
||||||
|
commit: '',
|
||||||
|
repo: '',
|
||||||
|
commitError: null,
|
||||||
|
contentHash: '',
|
||||||
|
contentHashOwner: null,
|
||||||
|
contentHashError: null
|
||||||
|
});
|
||||||
|
|
||||||
this.trackRequest(
|
this.trackRequest(
|
||||||
instance
|
eventId, instance
|
||||||
.hint.estimateGas(options, values)
|
.hint.estimateGas(options, values)
|
||||||
.then((gas) => {
|
.then((gas) => {
|
||||||
this.setState({ registerState: 'Gas estimated, Posting transaction to the network' });
|
this.setState({
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: Object.assign({}, this.state.events[eventId], {
|
||||||
|
registerState: 'Gas estimated, Posting transaction to the network'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const gasPassed = gas.mul(1.2);
|
const gasPassed = gas.mul(1.2);
|
||||||
options.gas = gasPassed.toFixed(0);
|
options.gas = gasPassed.toFixed(0);
|
||||||
@ -335,19 +404,45 @@ export default class Application extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerUrl (url) {
|
registerUrl (contentUrl) {
|
||||||
const { contentHash, fromAddress, instance } = this.state;
|
const { contentHash, fromAddress, instance } = this.state;
|
||||||
|
|
||||||
this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' });
|
const eventId = nextEventId++;
|
||||||
|
const values = [contentHash, contentUrl];
|
||||||
const values = [contentHash, url];
|
|
||||||
const options = { from: fromAddress };
|
const options = { from: fromAddress };
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
eventIds: [eventId].concat(this.state.eventIds),
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: {
|
||||||
|
contentHash,
|
||||||
|
contentUrl,
|
||||||
|
fromAddress,
|
||||||
|
registerBusy: true,
|
||||||
|
registerState: 'Estimating gas for the transaction',
|
||||||
|
timestamp: new Date()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
url: '',
|
||||||
|
commit: '',
|
||||||
|
repo: '',
|
||||||
|
commitError: null,
|
||||||
|
contentHash: '',
|
||||||
|
contentHashOwner: null,
|
||||||
|
contentHashError: null
|
||||||
|
});
|
||||||
|
|
||||||
this.trackRequest(
|
this.trackRequest(
|
||||||
instance
|
eventId, instance
|
||||||
.hintURL.estimateGas(options, values)
|
.hintURL.estimateGas(options, values)
|
||||||
.then((gas) => {
|
.then((gas) => {
|
||||||
this.setState({ registerState: 'Gas estimated, Posting transaction to the network' });
|
this.setState({
|
||||||
|
events: Object.assign({}, this.state.events, {
|
||||||
|
[eventId]: Object.assign({}, this.state.events[eventId], {
|
||||||
|
registerState: 'Gas estimated, Posting transaction to the network'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const gasPassed = gas.mul(1.2);
|
const gasPassed = gas.mul(1.2);
|
||||||
options.gas = gasPassed.toFixed(0);
|
options.gas = gasPassed.toFixed(0);
|
||||||
|
37
js/src/dapps/githubhint/Events/events.css
Normal file
37
js/src/dapps/githubhint/Events/events.css
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.list {
|
||||||
|
border: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
&[data-busy="true"] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-error="true"] {
|
||||||
|
color: #f66;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
52
js/src/dapps/githubhint/Events/events.js
Normal file
52
js/src/dapps/githubhint/Events/events.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// 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 React, { Component, PropTypes } from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import styles from './events.css';
|
||||||
|
|
||||||
|
export default class Events extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
eventIds: PropTypes.array.isRequired,
|
||||||
|
events: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<table className={ styles.list }>
|
||||||
|
<tbody>
|
||||||
|
{ this.props.eventIds.map((id) => this.renderEvent(id, this.props.events[id])) }
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEvent = (eventId, event) => {
|
||||||
|
return (
|
||||||
|
<tr key={ `event_${eventId}` } data-busy={ event.registerBusy } data-error={ event.registerError }>
|
||||||
|
<td>
|
||||||
|
<div>{ moment(event.timestamp).fromNow() }</div>
|
||||||
|
<div>{ event.registerState }</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>{ event.contentUrl || `${event.contentRepo}/${event.contentCommit}` }</div>
|
||||||
|
<div>{ event.contentHash }</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/githubhint/Events/index.js
Normal file
17
js/src/dapps/githubhint/Events/index.js
Normal 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 './events';
|
Loading…
Reference in New Issue
Block a user