diff --git a/js/src/dapps/githubhint/Application/application.css b/js/src/dapps/githubhint/Application/application.css index be04ecf34..61929f552 100644 --- a/js/src/dapps/githubhint/Application/application.css +++ b/js/src/dapps/githubhint/Application/application.css @@ -15,12 +15,17 @@ /* along with Parity. If not, see . */ -.container { +.body { + text-align: center; background: #333; + color: #fff; +} + +.container { font-family: 'Roboto'; vertical-align: middle; padding: 4em 0; - text-align: center; + margin: 0 0 2em 0; } .form { @@ -98,7 +103,7 @@ color: #333; background: #eee; border: none; - border-radius: 5px; + border-radius: 0.5em; width: 100%; font-size: 1em; text-align: center; @@ -113,20 +118,29 @@ } .hashError, .hashWarning, .hashOk { - padding-top: 0.5em; + margin: 0.5em 0; text-align: center; + padding: 1em 0; + border: 0.25em solid #333; + border-radius: 0.5em; } .hashError { + border-color: #f66; color: #f66; + background: rgba(255, 102, 102, 0.25); } .hashWarning { + border-color: #f80; color: #f80; + background: rgba(255, 236, 0, 0.25); } .hashOk { - opacity: 0.5; + border-color: #6f6; + color: #6f6; + background: rgba(102, 255, 102, 0.25); } .typeButtons { diff --git a/js/src/dapps/githubhint/Application/application.js b/js/src/dapps/githubhint/Application/application.js index 5a7494928..1690bf1c4 100644 --- a/js/src/dapps/githubhint/Application/application.js +++ b/js/src/dapps/githubhint/Application/application.js @@ -19,6 +19,7 @@ import React, { Component } from 'react'; import { api } from '../parity'; import { attachInterface } from '../services'; import Button from '../Button'; +import Events from '../Events'; import IdentityIcon from '../IdentityIcon'; import Loading from '../Loading'; @@ -27,6 +28,8 @@ import styles from './application.css'; const INVALID_URL_HASH = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; +let nextEventId = 0; + export default class Application extends Component { state = { fromAddress: null, @@ -43,7 +46,9 @@ export default class Application extends Component { registerState: '', registerType: 'file', repo: '', - repoError: null + repoError: null, + events: {}, + eventIds: [] } componentDidMount () { @@ -75,7 +80,7 @@ export default class Application extends Component { let hashClass = null; if (contentHashError) { hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning; - } else { + } else if (contentHash) { hashClass = styles.hashOk; } @@ -116,29 +121,34 @@ export default class Application extends Component { } return ( -
-
-
- - -
-
-
- 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. +
+
+
+
+ +
- { valueInputs } -
- { 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. +
+ { valueInputs } +
+ { contentHashError || contentHash } +
+ { registerBusy ? this.renderProgress() : this.renderButtons() }
- { registerBusy ? this.renderProgress() : this.renderButtons() }
+
); } @@ -285,15 +295,29 @@ export default class Application extends Component { } } - trackRequest (promise) { + trackRequest (eventId, promise) { return promise .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); }) .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) => { if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) { @@ -304,27 +328,72 @@ export default class Application extends Component { }); }) .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) => { 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; + contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`; - this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); - - const values = [contentHash, repo, commit.substr(0, 2) === '0x' ? commit : `0x${commit}`]; + const eventId = nextEventId++; + const values = [contentHash, contentRepo, contentCommit]; 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( - instance + eventId, instance .hint.estimateGas(options, values) .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); 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; - this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); - - const values = [contentHash, url]; + const eventId = nextEventId++; + const values = [contentHash, contentUrl]; 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( - instance + eventId, instance .hintURL.estimateGas(options, values) .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); options.gas = gasPassed.toFixed(0); diff --git a/js/src/dapps/githubhint/Events/events.css b/js/src/dapps/githubhint/Events/events.css new file mode 100644 index 000000000..a33c09236 --- /dev/null +++ b/js/src/dapps/githubhint/Events/events.css @@ -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 . +*/ + +.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; + } +} diff --git a/js/src/dapps/githubhint/Events/events.js b/js/src/dapps/githubhint/Events/events.js new file mode 100644 index 000000000..f3b48292b --- /dev/null +++ b/js/src/dapps/githubhint/Events/events.js @@ -0,0 +1,50 @@ +// 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 . + +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 ( + + { this.props.eventIds.map((id) => this.renderEvent(id, this.props.events[id])) } +
+ ); + } + + renderEvent = (eventId, event) => { + return ( + + +
{ moment(event.timestamp).fromNow() }
+
{ event.registerState }
+ + +
{ event.contentUrl || `${event.contentRepo}/${event.contentCommit}` }
+
{ event.contentHash }
+ + + ); + } +} diff --git a/js/src/dapps/githubhint/Events/index.js b/js/src/dapps/githubhint/Events/index.js new file mode 100644 index 000000000..88ad6d407 --- /dev/null +++ b/js/src/dapps/githubhint/Events/index.js @@ -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 . + +export default from './events';