diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index 4cbcbdce4..4307fc912 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -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]); diff --git a/js/src/api/format/input.spec.js b/js/src/api/format/input.spec.js index 27d200b93..699ac93c2 100644 --- a/js/src/api/format/input.spec.js +++ b/js/src/api/format/input.spec.js @@ -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' }); }); diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index e1887582a..73e2f7220 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -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': diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index ce50c69c1..3fa2f218d 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -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' }); }); diff --git a/js/src/modals/ExecuteContract/AdvancedStep/advancedStep.js b/js/src/modals/ExecuteContract/AdvancedStep/advancedStep.js new file mode 100644 index 000000000..4142aa961 --- /dev/null +++ b/js/src/modals/ExecuteContract/AdvancedStep/advancedStep.js @@ -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 . + +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 ( +
+ + } + label={ + + } + value={ minBlock } + onSubmit={ onMinBlockChange } /> +
+ +
+
+ ); + } +} diff --git a/js/src/modals/ExecuteContract/AdvancedStep/index.js b/js/src/modals/ExecuteContract/AdvancedStep/index.js new file mode 100644 index 000000000..3a4cc1028 --- /dev/null +++ b/js/src/modals/ExecuteContract/AdvancedStep/index.js @@ -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 . + +export default from './advancedStep'; diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index e2d18bf65..818f216f6 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -14,8 +14,9 @@ // 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 { 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 (
@@ -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={ + + } + label={ + + } onChange={ onFromAddressChange } value={ fromAddress } /> { this.renderFunctionSelect() } @@ -70,16 +78,28 @@ export default class DetailsStep extends Component {
+ } + label={ + + } onSubmit={ onAmountChange } value={ amount } />
+ } + onCheck={ onAdvancedClick } style={ CHECK_STYLE } />
@@ -129,9 +149,17 @@ export default class DetailsStep extends Component { return ( + } + label={ + + } + value={ minBlock } + onChange={ this.onEditMinBlock } /> +
+ +
); } @@ -50,18 +71,28 @@ export default class Extras extends Component { } return ( -
- -
+ + } + label={ + + } + onChange={ this.onEditData } + value={ data } /> ); } onEditData = (event) => { this.props.onChange('data', event.target.value); } + + onEditMinBlock = (event) => { + this.props.onChange('minBlock', event.target.value); + } } diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index 679e03609..c65a25fab 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -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); } diff --git a/js/src/modals/Transfer/transfer.css b/js/src/modals/Transfer/transfer.css index 6e3ae9aa2..4c6b2df82 100644 --- a/js/src/modals/Transfer/transfer.css +++ b/js/src/modals/Transfer/transfer.css @@ -15,65 +15,68 @@ /* along with Parity. If not, see . */ +.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 { diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index 19c337e5a..bbeb3fadb 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -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 ( + isEth={ isEth } + minBlock={ minBlock } + minBlockError={ minBlockError } + onChange={ this.store.onUpdateDetails } + total={ total } + totalError={ totalError } /> ); } @@ -208,20 +206,20 @@ class Transfer extends Component { const cancelBtn = (