// Copyright 2015-2017 Parity Technologies (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 } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import ReactDOM from 'react-dom'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { hideRequest } from '@parity/shared/redux/providers/requestsActions'; import MethodDecoding from '@parity/ui/MethodDecoding'; import IdentityIcon from '@parity/ui/IdentityIcon'; import Progress from '@parity/ui/Progress'; import ScrollableText from '@parity/ui/ScrollableText'; import ShortenedHash from '@parity/ui/ShortenedHash'; import styles from './requests.css'; const ERROR_STATE = 'ERROR_STATE'; const DONE_STATE = 'DONE_STATE'; const WAITING_STATE = 'WAITING_STATE'; class Requests extends Component { static propTypes = { requests: PropTypes.object.isRequired, onHideRequest: PropTypes.func.isRequired }; state = { extras: {} }; render () { const { requests } = this.props; const { extras } = this.state; return ( <div className={ styles.requests }> { Object .values(requests) .map((request) => this.renderRequest(request, extras[request.requestId])) } </div> ); } renderRequest (request, extras = {}) { const { show, transaction } = request; if (!transaction) { return null; } const state = this.getTransactionState(request); const displayedTransaction = { ...transaction }; // Don't show gas and gasPrice delete displayedTransaction.gas; delete displayedTransaction.gasPrice; const requestClasses = [ styles.request ]; const statusClasses = [ styles.status ]; const requestStyle = {}; const handleHideRequest = () => { this.handleHideRequest(request.requestId); }; if (state.type === ERROR_STATE) { statusClasses.push(styles.error); } if (!show) { requestClasses.push(styles.hide); } // Set the Request height (for animation) if found if (extras.height) { requestStyle.height = extras.height; } return ( <div className={ requestClasses.join(' ') } key={ request.requestId } ref={ `request_${request.requestId}` } onClick={ handleHideRequest } style={ requestStyle } > <div className={ statusClasses.join(' ') }> { this.renderStatus(request) } </div> { state.type === ERROR_STATE ? null : ( <Progress max={ 6 } isDeterminate={ state.type !== WAITING_STATE } value={ state.type === DONE_STATE ? +request.blockHeight : 6 } /> ) } <div className={ styles.container }> <div className={ styles.identity } title={ transaction.from } > <IdentityIcon address={ transaction.from } inline center className={ styles.icon } /> </div> <MethodDecoding address={ transaction.from } compact historic={ state.type === DONE_STATE } transaction={ displayedTransaction } /> </div> </div> ); } renderStatus (request) { const { error, transactionHash, transactionReceipt } = request; if (error) { return ( <div className={ styles.inline } title={ error.message } > <FormattedMessage id='requests.status.error' defaultMessage='An error occured:' /> <div className={ styles.fill }> <ScrollableText text={ error.text || error.message || error.toString() } /> </div> </div> ); } if (transactionReceipt) { return ( <FormattedMessage id='requests.status.transactionMined' defaultMessage='Transaction mined at block #{blockNumber} ({blockHeight} confirmations)' values={ { blockHeight: (+request.blockHeight || 0).toString(), blockNumber: +transactionReceipt.blockNumber } } /> ); } if (transactionHash) { return ( <div className={ styles.inline }> <FormattedMessage id='requests.status.transactionSent' defaultMessage='Transaction sent to network with hash' /> <div className={ [ styles.fill, styles.hash ].join(' ') }> <ShortenedHash data={ transactionHash } /> </div> </div> ); } return ( <FormattedMessage id='requests.status.waitingForSigner' defaultMessage='Waiting for authorization in the Parity Signer' /> ); } getTransactionState (request) { const { error, transactionReceipt } = request; if (error) { return { type: ERROR_STATE }; } if (transactionReceipt) { return { type: DONE_STATE }; } return { type: WAITING_STATE }; } handleHideRequest = (requestId) => { const requestElement = ReactDOM.findDOMNode(this.refs[`request_${requestId}`]); // Try to get the request element height, to have a nice transition effect if (requestElement) { const { height } = requestElement.getBoundingClientRect(); const prevExtras = this.state.extras; const nextExtras = { ...prevExtras, [ requestId ]: { ...prevExtras[requestId], height } }; return this.setState({ extras: nextExtras }, () => { return this.props.onHideRequest(requestId); }); } return this.props.onHideRequest(requestId); } } const mapStateToProps = (state) => { const { requests } = state; return { requests }; }; function mapDispatchToProps (dispatch) { return bindActionCreators({ onHideRequest: hideRequest }, dispatch); } export default connect( mapStateToProps, mapDispatchToProps )(Requests);