Allow setting of minBlock on sending (#3921)

* minBlock value formatting

* Allow Contract execute to specify minBock

* Transfer allows minBlock

* Cleanups

* Check errors, verify via testing

* Display Submitted/Submission block in MethodDecoding
This commit is contained in:
Jaco Greeff 2016-12-23 15:31:19 +01:00 committed by GitHub
parent 74efb22230
commit fc620d0d3e
16 changed files with 463 additions and 187 deletions

View File

@ -137,6 +137,10 @@ export function inOptions (options) {
options[key] = inNumber16((new BigNumber(options[key])).round());
break;
case 'minBlock':
options[key] = options[key] ? inNumber16(options[key]) : null;
break;
case 'value':
case 'nonce':
options[key] = inNumber16(options[key]);

View File

@ -204,7 +204,7 @@ describe('api/format/input', () => {
});
});
['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => {
['gas', 'gasPrice', 'value', 'minBlock', 'nonce'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
@ -214,6 +214,10 @@ describe('api/format/input', () => {
});
});
it('passes minBlock as null when specified as such', () => {
expect(inOptions({ minBlock: null })).to.deep.equal({ minBlock: null });
});
it('ignores and passes through unknown keys', () => {
expect(inOptions({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});

View File

@ -205,6 +205,10 @@ export function outTransaction (tx) {
tx[key] = outNumber(tx[key]);
break;
case 'minBlock':
tx[key] = tx[key] ? outNumber(tx[key]) : null;
break;
case 'creates':
case 'from':
case 'to':

View File

@ -283,7 +283,7 @@ describe('api/format/output', () => {
});
});
['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
['blockNumber', 'gasPrice', 'gas', 'minBlock', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
@ -294,6 +294,10 @@ describe('api/format/output', () => {
});
});
it('passes minBlock as null when null', () => {
expect(outTransaction({ minBlock: null })).to.deep.equal({ minBlock: null });
});
it('ignores and passes through unknown keys', () => {
expect(outTransaction({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});

View File

@ -0,0 +1,57 @@
// Copyright 2015, 2016 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, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { Input, GasPriceEditor } from '~/ui';
import styles from '../executeContract.css';
export default class AdvancedStep extends Component {
static propTypes = {
gasStore: PropTypes.object.isRequired,
minBlock: PropTypes.string,
minBlockError: PropTypes.string,
onMinBlockChange: PropTypes.func
};
render () {
const { gasStore, minBlock, minBlockError, onMinBlockChange } = this.props;
return (
<div>
<Input
error={ minBlockError }
hint={
<FormattedMessage
id='executeContract.advanced.minBlock.hint'
defaultMessage='Only post the transaction after this block' />
}
label={
<FormattedMessage
id='executeContract.advanced.minBlock.label'
defaultMessage='BlockNumber to send from' />
}
value={ minBlock }
onSubmit={ onMinBlockChange } />
<div className={ styles.gaseditor }>
<GasPriceEditor store={ gasStore } />
</div>
</div>
);
}
}

View File

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

View File

@ -14,8 +14,9 @@
// 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 { Checkbox, MenuItem } from 'material-ui';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui';
@ -29,29 +30,28 @@ const CHECK_STYLE = {
export default class DetailsStep extends Component {
static propTypes = {
advancedOptions: PropTypes.bool,
accounts: PropTypes.object.isRequired,
contract: PropTypes.object.isRequired,
onAmountChange: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired,
onValueChange: PropTypes.func.isRequired,
values: PropTypes.array.isRequired,
valuesError: PropTypes.array.isRequired,
amount: PropTypes.string,
amountError: PropTypes.string,
balances: PropTypes.object,
contract: PropTypes.object.isRequired,
fromAddress: PropTypes.string,
fromAddressError: PropTypes.string,
func: PropTypes.object,
funcError: PropTypes.string,
gasEdit: PropTypes.bool,
onAdvancedClick: PropTypes.func,
onAmountChange: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired,
onFuncChange: PropTypes.func,
onGasEditClick: PropTypes.func,
onValueChange: PropTypes.func.isRequired,
values: PropTypes.array.isRequired,
valuesError: PropTypes.array.isRequired,
warning: PropTypes.string
}
render () {
const { accounts, amount, amountError, balances, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
const { accounts, advancedOptions, amount, amountError, balances, fromAddress, fromAddressError, onAdvancedClick, onAmountChange, onFromAddressChange } = this.props;
return (
<Form>
@ -60,8 +60,16 @@ export default class DetailsStep extends Component {
accounts={ accounts }
balances={ balances }
error={ fromAddressError }
hint='the account to transact with'
label='from account'
hint={
<FormattedMessage
id='executeContract.details.address.label'
defaultMessage='the account to transact with' />
}
label={
<FormattedMessage
id='executeContract.details.address.hint'
defaultMessage='from account' />
}
onChange={ onFromAddressChange }
value={ fromAddress } />
{ this.renderFunctionSelect() }
@ -70,16 +78,28 @@ export default class DetailsStep extends Component {
<div>
<Input
error={ amountError }
hint='the amount to send to with the transaction'
label='transaction value (in ETH)'
hint={
<FormattedMessage
id='executeContract.details.amount.hint'
defaultMessage='the amount to send to with the transaction' />
}
label={
<FormattedMessage
id='executeContract.details.amount.label'
defaultMessage='transaction value (in ETH)' />
}
onSubmit={ onAmountChange }
value={ amount } />
</div>
<div>
<Checkbox
checked={ gasEdit }
label='edit gas price or value'
onCheck={ onGasEditClick }
checked={ advancedOptions }
label={
<FormattedMessage
id='executeContract.details.advancedCheck.label'
defaultMessage='advanced sending options' />
}
onCheck={ onAdvancedClick }
style={ CHECK_STYLE } />
</div>
</div>
@ -129,9 +149,17 @@ export default class DetailsStep extends Component {
return (
<Select
label='function to execute'
hint='the function to call on the contract'
error={ funcError }
hint={
<FormattedMessage
id='executeContract.details.function.hint'
defaultMessage='the function to call on the contract' />
}
label={
<FormattedMessage
id='executeContract.details.function.label'
defaultMessage='function to execute' />
}
onChange={ this.onFuncChange }
value={ func.signature }>
{ functions }

View File

@ -14,6 +14,19 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.funcparams {
padding-left: 3em;
}
.gaseditor {
margin-top: 1em;
}
.paramname {
color: #aaa;
}
.modalbody,
.modalcenter {
}
@ -22,14 +35,6 @@
text-align: center;
}
.funcparams {
padding-left: 3em;
}
.paramname {
color: #aaa;
}
.txhash {
word-break: break-all;
}

View File

@ -14,40 +14,54 @@
// 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 { pick } from 'lodash';
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { observer } from 'mobx-react';
import { pick } from 'lodash';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { toWei } from '~/api/util/wei';
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui';
import { CancelIcon, DoneIcon, NextIcon, PrevIcon } from '~/ui/Icons';
import { MAX_GAS_ESTIMATION } from '~/util/constants';
import { validateAddress, validateUint } from '~/util/validation';
import { parseAbiType } from '~/util/abi';
import AdvancedStep from './AdvancedStep';
import DetailsStep from './DetailsStep';
import { ERROR_CODES } from '~/api/transport/error';
const STEP_DETAILS = 0;
const STEP_BUSY_OR_GAS = 1;
const STEP_BUSY_OR_ADVANCED = 1;
const STEP_BUSY = 2;
const TITLES = {
transfer: 'function details',
sending: 'sending',
complete: 'complete',
gas: 'gas selection',
rejected: 'rejected'
transfer:
<FormattedMessage
id='executeContract.steps.transfer'
defaultMessage='function details' />,
sending:
<FormattedMessage
id='executeContract.steps.sending'
defaultMessage='sending' />,
complete:
<FormattedMessage
id='executeContract.steps.complete'
defaultMessage='complete' />,
advanced:
<FormattedMessage
id='executeContract.steps.advanced'
defaultMessage='advanced options' />,
rejected:
<FormattedMessage
id='executeContract.steps.rejected'
defaultMessage='rejected' />
};
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
const STAGES_GAS = [TITLES.transfer, TITLES.gas, TITLES.sending, TITLES.complete];
const STAGES_ADVANCED = [TITLES.transfer, TITLES.advanced, TITLES.sending, TITLES.complete];
@observer
class ExecuteContract extends Component {
@ -70,13 +84,15 @@ class ExecuteContract extends Component {
gasStore = new GasPriceEditor.Store(this.context.api, { gasLimit: this.props.gasLimit });
state = {
advancedOptions: false,
amount: '0',
amountError: null,
busyState: null,
fromAddressError: null,
func: null,
funcError: null,
gasEdit: false,
minBlock: '0',
minBlockError: null,
rejected: false,
sending: false,
step: STEP_DETAILS,
@ -101,8 +117,8 @@ class ExecuteContract extends Component {
}
render () {
const { sending, step, gasEdit, rejected } = this.state;
const steps = gasEdit ? STAGES_GAS : STAGES_BASIC;
const { advancedOptions, rejected, sending, step } = this.state;
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
if (rejected) {
steps[steps.length - 1] = TITLES.rejected;
@ -115,7 +131,7 @@ class ExecuteContract extends Component {
current={ step }
steps={ steps }
visible
waiting={ gasEdit ? [STEP_BUSY] : [STEP_BUSY_OR_GAS] }>
waiting={ advancedOptions ? [STEP_BUSY] : [STEP_BUSY_OR_ADVANCED] }>
{ this.renderStep() }
</Modal>
);
@ -123,20 +139,28 @@ class ExecuteContract extends Component {
renderDialogActions () {
const { onClose, fromAddress } = this.props;
const { gasEdit, sending, step, fromAddressError, valuesError } = this.state;
const hasError = fromAddressError || valuesError.find((error) => error);
const { advancedOptions, sending, step, fromAddressError, minBlockError, valuesError } = this.state;
const hasError = fromAddressError || minBlockError || valuesError.find((error) => error);
const cancelBtn = (
<Button
key='cancel'
label='Cancel'
icon={ <ContentClear /> }
label={
<FormattedMessage
id='executeContract.button.cancel'
defaultMessage='cancel' />
}
icon={ <CancelIcon /> }
onClick={ onClose } />
);
const postBtn = (
<Button
key='postTransaction'
label='post transaction'
label={
<FormattedMessage
id='executeContract.button.post'
defaultMessage='post transaction' />
}
disabled={ !!(sending || hasError) }
icon={ <IdentityIcon address={ fromAddress } button /> }
onClick={ this.postTransaction } />
@ -144,28 +168,36 @@ class ExecuteContract extends Component {
const nextBtn = (
<Button
key='nextStep'
label='next'
icon={ <NavigationArrowForward /> }
label={
<FormattedMessage
id='executeContract.button.next'
defaultMessage='next' />
}
icon={ <NextIcon /> }
onClick={ this.onNextClick } />
);
const prevBtn = (
<Button
key='prevStep'
label='prev'
icon={ <NavigationArrowBack /> }
label={
<FormattedMessage
id='executeContract.button.prev'
defaultMessage='prev' />
}
icon={ <PrevIcon /> }
onClick={ this.onPrevClick } />
);
if (step === STEP_DETAILS) {
return [
cancelBtn,
gasEdit ? nextBtn : postBtn
advancedOptions ? nextBtn : postBtn
];
} else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
} else if (step === (advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED)) {
return [
cancelBtn
];
} else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
return [
cancelBtn,
prevBtn,
@ -176,23 +208,34 @@ class ExecuteContract extends Component {
return [
<Button
key='close'
label='Done'
icon={ <ActionDoneAll /> }
label={
<FormattedMessage
id='executeContract.button.done'
defaultMessage='done' />
}
icon={ <DoneIcon /> }
onClick={ onClose } />
];
}
renderStep () {
const { onFromAddressChange } = this.props;
const { gasEdit, step, busyState, txhash, rejected } = this.state;
const { advancedOptions, step, busyState, minBlock, minBlockError, txhash, rejected } = this.state;
const { errorEstimated } = this.gasStore;
if (rejected) {
return (
<BusyStep
title='The execution has been rejected'
state='You can safely close this window, the function execution will not occur.'
/>
title={
<FormattedMessage
id='executeContract.rejected.title'
defaultMessage='The execution has been rejected' />
}
state={
<FormattedMessage
id='executeContract.rejected.state'
defaultMessage='You can safely close this window, the function execution will not occur.' />
} />
);
}
@ -205,19 +248,26 @@ class ExecuteContract extends Component {
onAmountChange={ this.onAmountChange }
onFromAddressChange={ onFromAddressChange }
onFuncChange={ this.onFuncChange }
onGasEditClick={ this.onGasEditClick }
onAdvancedClick={ this.onAdvancedClick }
onValueChange={ this.onValueChange } />
);
} else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
} else if (step === (advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED)) {
return (
<BusyStep
title='The function execution is in progress'
title={
<FormattedMessage
id='executeContract.busy.title'
defaultMessage='The function execution is in progress' />
}
state={ busyState } />
);
} else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
return (
<GasPriceEditor
store={ this.gasStore } />
<AdvancedStep
gasStore={ this.gasStore }
minBlock={ minBlock }
minBlockError={ minBlockError }
onMinBlockChange={ this.onMinBlockChange } />
);
}
@ -245,6 +295,15 @@ class ExecuteContract extends Component {
}, this.estimateGas);
}
onMinBlockChange = (minBlock) => {
const minBlockError = validateUint(minBlock).valueError;
this.setState({
minBlock,
minBlockError
});
}
onValueChange = (event, index, _value) => {
const { func, values, valuesError } = this.state;
const input = func.inputs.find((input, _index) => index === _index);
@ -305,22 +364,28 @@ class ExecuteContract extends Component {
postTransaction = () => {
const { api, store } = this.context;
const { fromAddress } = this.props;
const { amount, func, gasEdit, values } = this.state;
const steps = gasEdit ? STAGES_GAS : STAGES_BASIC;
const { advancedOptions, amount, func, minBlock, values } = this.state;
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
const finalstep = steps.length - 1;
const options = {
gas: this.gasStore.gas,
gasPrice: this.gasStore.price,
from: fromAddress,
minBlock: new BigNumber(minBlock || 0).gt(0) ? minBlock : null,
value: api.util.toWei(amount || 0)
};
this.setState({ sending: true, step: gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS });
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });
func
.postTransaction(options, values)
.then((requestId) => {
this.setState({ busyState: 'Waiting for authorization in the Parity Signer' });
this.setState({
busyState:
<FormattedMessage
id='executeContract.busy.waitAuth'
defaultMessage='Waiting for authorization in the Parity Signer' />
});
return api
.pollMethod('parity_checkRequest', requestId)
@ -334,7 +399,15 @@ class ExecuteContract extends Component {
});
})
.then((txhash) => {
this.setState({ sending: false, step: finalstep, txhash, busyState: 'Your transaction has been posted to the network' });
this.setState({
sending: false,
step: finalstep,
txhash,
busyState:
<FormattedMessage
id='executeContract.busy.posted'
defaultMessage='Your transaction has been posted to the network' />
});
})
.catch((error) => {
console.error('postTransaction', error);
@ -342,9 +415,9 @@ class ExecuteContract extends Component {
});
}
onGasEditClick = () => {
onAdvancedClick = () => {
this.setState({
gasEdit: !this.state.gasEdit
advancedOptions: !this.state.advancedOptions
});
}

View File

@ -15,29 +15,50 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { GasPriceEditor, Form, Input } from '~/ui';
import styles from '../transfer.css';
export default class Extras extends Component {
static propTypes = {
isEth: PropTypes.bool,
data: PropTypes.string,
dataError: PropTypes.string,
total: PropTypes.string,
totalError: PropTypes.string,
gasStore: PropTypes.object.isRequired,
isEth: PropTypes.bool,
minBlock: PropTypes.string,
minBlockError: PropTypes.string,
onChange: PropTypes.func.isRequired,
gasStore: PropTypes.object.isRequired
total: PropTypes.string,
totalError: PropTypes.string
}
render () {
const { gasStore, onChange } = this.props;
const { gasStore, minBlock, minBlockError, onChange } = this.props;
return (
<Form>
{ this.renderData() }
<GasPriceEditor
store={ gasStore }
onChange={ onChange } />
<Input
error={ minBlockError }
hint={
<FormattedMessage
id='executeContract.advanced.minBlock.hint'
defaultMessage='Only post the transaction after this block' />
}
label={
<FormattedMessage
id='executeContract.advanced.minBlock.label'
defaultMessage='BlockNumber to send from' />
}
value={ minBlock }
onChange={ this.onEditMinBlock } />
<div className={ styles.gaseditor }>
<GasPriceEditor
store={ gasStore }
onChange={ onChange } />
</div>
</Form>
);
}
@ -50,18 +71,28 @@ export default class Extras extends Component {
}
return (
<div>
<Input
hint='the data to pass through with the transaction'
label='transaction data'
value={ data }
error={ dataError }
onChange={ this.onEditData } />
</div>
<Input
error={ dataError }
hint={
<FormattedMessage
id='transfer.advanced.data.hint'
defaultMessage='the data to pass through with the transaction' />
}
label={
<FormattedMessage
id='transfer.advanced.data.label'
defaultMessage='transaction data' />
}
onChange={ this.onEditData }
value={ data } />
);
}
onEditData = (event) => {
this.props.onChange('data', event.target.value);
}
onEditMinBlock = (event) => {
this.props.onChange('minBlock', event.target.value);
}
}

View File

@ -49,6 +49,9 @@ export default class TransferStore {
@observable data = '';
@observable dataError = null;
@observable minBlock = '0';
@observable minBlockError = null;
@observable recipient = '';
@observable recipientError = ERRORS.requireRecipient;
@ -84,7 +87,7 @@ export default class TransferStore {
@computed get isValid () {
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.totalError;
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.minBlockError && !this.totalError;
const verifyValid = !this.passwordError;
switch (this.stage) {
@ -92,7 +95,9 @@ export default class TransferStore {
return detailsValid;
case 1:
return this.extras ? extrasValid : verifyValid;
return this.extras
? extrasValid
: verifyValid;
case 2:
return verifyValid;
@ -155,6 +160,9 @@ export default class TransferStore {
case 'gasPrice':
return this._onUpdateGasPrice(value);
case 'minBlock':
return this._onUpdateMinBlock(value);
case 'recipient':
return this._onUpdateRecipient(value);
@ -254,6 +262,14 @@ export default class TransferStore {
this.recalculate();
}
@action _onUpdateMinBlock = (minBlock) => {
console.log('minBlock', minBlock);
transaction(() => {
this.minBlock = minBlock;
this.minBlockError = this._validatePositiveNumber(minBlock);
});
}
@action _onUpdateGasPrice = (gasPrice) => {
this.recalculate();
}
@ -412,6 +428,9 @@ export default class TransferStore {
send () {
const { options, values } = this._getTransferParams();
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
return this._getTransferMethod().postTransaction(options, values);
}

View File

@ -15,65 +15,68 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.columns {
display: flex;
position: relative;
flex-wrap: wrap;
&>div {
flex: 0 1 50%;
position: relative;
width: 50%;
}
}
.gaseditor {
margin-top: 1em;
}
.info {
line-height: 1.618em;
width: 100%;
}
.columns {
display: flex;
flex-wrap: wrap;
position: relative;
}
.row {
display: flex;
flex-wrap: wrap;
position: relative;
flex-direction: column;
}
.columns>div {
flex: 0 1 50%;
width: 50%;
flex-wrap: wrap;
position: relative;
}
.floatbutton {
text-align: right;
float: right;
margin-left: -100%;
margin-top: 28px;
}
text-align: right;
.floatbutton>div {
margin-right: 0.5em;
}
.tokenSelect {
&>div {
margin-right: 0.5em;
}
}
.token {
height: 32px;
padding: 4px 0;
img {
height: 32px;
width: 32px;
margin: 0 16px 0 0;
z-index: 10;
}
div {
height: 32px;
line-height: 32px;
display: inline-block;
vertical-align: top;
}
}
.tokenSelect .token {
margin-top: 10px;
}
.token img {
height: 32px;
width: 32px;
margin: 0 16px 0 0;
z-index: 10;
}
.token div {
height: 32px;
line-height: 32px;
display: inline-block;
vertical-align: top;
.tokenSelect {
.token {
margin-top: 10px;
}
}
.tokenbalance {

View File

@ -20,13 +20,9 @@ import { bindActionCreators } from 'redux';
import { observer } from 'mobx-react';
import { pick } from 'lodash';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { newError } from '~/ui/Errors/actions';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash, Input } from '~/ui';
import { newError } from '~/ui/Errors/actions';
import { CancelIcon, DoneIcon, NextIcon, PrevIcon } from '~/ui/Icons';
import { nullableProptype } from '~/util/proptypes';
import Details from './Details';
@ -188,17 +184,19 @@ class Transfer extends Component {
return null;
}
const { isEth, data, dataError, total, totalError } = this.store;
const { isEth, data, dataError, minBlock, minBlockError, total, totalError } = this.store;
return (
<Extras
isEth={ isEth }
data={ data }
dataError={ dataError }
total={ total }
totalError={ totalError }
gasStore={ this.store.gasStore }
onChange={ this.store.onUpdateDetails } />
isEth={ isEth }
minBlock={ minBlock }
minBlockError={ minBlockError }
onChange={ this.store.onUpdateDetails }
total={ total }
totalError={ totalError } />
);
}
@ -208,20 +206,20 @@ class Transfer extends Component {
const cancelBtn = (
<Button
icon={ <ContentClear /> }
icon={ <CancelIcon /> }
label='Cancel'
onClick={ this.handleClose } />
);
const nextBtn = (
<Button
disabled={ !this.store.isValid }
icon={ <NavigationArrowForward /> }
icon={ <NextIcon /> }
label='Next'
onClick={ this.store.onNext } />
);
const prevBtn = (
<Button
icon={ <NavigationArrowBack /> }
icon={ <PrevIcon /> }
label='Back'
onClick={ this.store.onPrev } />
);
@ -234,7 +232,7 @@ class Transfer extends Component {
);
const doneBtn = (
<Button
icon={ <ActionDoneAll /> }
icon={ <DoneIcon /> }
label='Close'
onClick={ this.handleClose } />
);

View File

@ -14,16 +14,18 @@
// 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 { MenuItem } from 'material-ui';
import { isEqual, pick } from 'lodash';
import { MenuItem } from 'material-ui';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { fromWei } from '~/api/util/wei';
import { nodeOrStringProptype } from '~/util/proptypes';
import AutoComplete from '../AutoComplete';
import IdentityIcon from '../../IdentityIcon';
import IdentityName from '../../IdentityName';
import { fromWei } from '~/api/util/wei';
import styles from './addressSelect.css';
export default class AddressSelect extends Component {
@ -40,9 +42,9 @@ export default class AddressSelect extends Component {
contacts: PropTypes.object,
contracts: PropTypes.object,
disabled: PropTypes.bool,
error: PropTypes.string,
hint: PropTypes.string,
label: PropTypes.string,
error: nodeOrStringProptype(),
hint: nodeOrStringProptype(),
label: nodeOrStringProptype(),
tokens: PropTypes.object,
value: PropTypes.string,
wallets: PropTypes.object
@ -116,18 +118,27 @@ export default class AddressSelect extends Component {
<AutoComplete
className={ !icon ? '' : styles.paddedInput }
disabled={ disabled }
label={ label }
hint={ hint ? `search for ${hint}` : 'search for an address' }
error={ error }
onChange={ this.onChange }
onBlur={ this.onBlur }
onUpdateInput={ allowInput && this.onUpdateInput }
value={ searchText }
filter={ this.handleFilter }
entries={ autocompleteEntries }
entry={ this.getEntry() || {} }
error={ error }
filter={ this.handleFilter }
hint={
<FormattedMessage
id='ui.addressSelect.search.hint'
defaultMessage='search for {hint}'
values={ {
hint: hint ||
<FormattedMessage
id='ui.addressSelect.search.address'
defaultMessage='address' />
} } />
}
label={ label }
onBlur={ this.onBlur }
onChange={ this.onChange }
onUpdateInput={ allowInput && this.onUpdateInput }
renderItem={ this.renderItem }
/>
value={ searchText } />
{ icon }
</div>
);
@ -148,9 +159,10 @@ export default class AddressSelect extends Component {
return (
<IdentityIcon
address={ value }
center
className={ classes.join(' ') }
inline center
address={ value } />
inline />
);
}
@ -162,9 +174,10 @@ export default class AddressSelect extends Component {
if (!this.items[address] || this.items[address].balance !== balance) {
this.items[address] = {
address,
balance,
text: name && name.toUpperCase() || address,
value: this.renderMenuItem(address),
address, balance
value: this.renderMenuItem(address)
};
}
@ -189,7 +202,7 @@ export default class AddressSelect extends Component {
}
renderBalance (address) {
const balance = this.getBalance(address);
const balance = this.getBalance(address) || 0;
const value = fromWei(balance);
return (
@ -207,12 +220,13 @@ export default class AddressSelect extends Component {
const item = (
<div className={ styles.account }>
<IdentityIcon
address={ address }
center
className={ styles.image }
inline center
address={ address } />
inline />
<IdentityName
className={ styles.name }
address={ address } />
address={ address }
className={ styles.name } />
{ balance }
</div>
);
@ -221,8 +235,8 @@ export default class AddressSelect extends Component {
<MenuItem
className={ styles.menuItem }
key={ address }
value={ address }
label={ item }>
label={ item }
value={ address }>
{ item }
</MenuItem>
);

View File

@ -14,12 +14,13 @@
// 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 keycode from 'keycode';
import { isEqual } from 'lodash';
import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui';
import { PopoverAnimationVertical } from 'material-ui/Popover';
import React, { Component, PropTypes } from 'react';
import { isEqual } from 'lodash';
import { nodeOrStringProptype } from '~/util/proptypes';
import styles from './autocomplete.css';
@ -41,21 +42,21 @@ class Divider extends Component {
export default class AutoComplete extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
onUpdateInput: PropTypes.func,
disabled: PropTypes.bool,
label: PropTypes.string,
hint: PropTypes.string,
error: PropTypes.string,
value: PropTypes.string,
className: PropTypes.string,
filter: PropTypes.func,
renderItem: PropTypes.func,
disabled: PropTypes.bool,
entry: PropTypes.object,
entries: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object
])
]),
error: nodeOrStringProptype(),
filter: PropTypes.func,
hint: nodeOrStringProptype(),
label: nodeOrStringProptype(),
onChange: PropTypes.func.isRequired,
onUpdateInput: PropTypes.func,
renderItem: PropTypes.func,
value: PropTypes.string
};
state = {

View File

@ -122,10 +122,24 @@ class MethodDecoding extends Component {
</span>
<span> for a total transaction value of </span>
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
{ this.renderMinBlock() }
</div>
);
}
renderMinBlock () {
const { historic, transaction } = this.props;
const { minBlock } = transaction;
if (!minBlock || minBlock.eq(0)) {
return null;
}
return (
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ minBlock.toFormat(0) }</span></span>
);
}
renderAction () {
const { token } = this.props;
const { methodName, methodInputs, methodSignature, isDeploy, isReceived, isContract } = this.state;