+ renderConditions () {
+ const { conditionType, condition, conditionBlockError } = this.props.store;
+
+ if (conditionType === CONDITIONS.NONE) {
+ return null;
+ }
+
+ if (conditionType === CONDITIONS.BLOCK) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+ }
+ label={
+
+ }
+ onChange={ this.onChangeConditionDateTime }
+ value={ condition.time }
+ />
+
+
+
+ }
+ label={
+
+ }
+ onChange={ this.onChangeConditionDateTime }
+ value={ condition.time }
+ />
);
@@ -107,4 +238,16 @@ export default class GasPriceEditor extends Component {
store.setPrice(price);
onChange && onChange('gasPrice', price);
}
+
+ onChangeConditionType = (conditionType) => {
+ this.props.store.setConditionType(conditionType.key);
+ }
+
+ onChangeConditionBlock = (event, blockNumber) => {
+ this.props.store.setConditionBlockNumber(blockNumber);
+ }
+
+ onChangeConditionDateTime = (event, datetime) => {
+ this.props.store.setConditionDateTime(datetime);
+ }
}
diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.spec.js b/js/src/ui/GasPriceEditor/gasPriceEditor.spec.js
index 46cf5a5f4..94d3076fa 100644
--- a/js/src/ui/GasPriceEditor/gasPriceEditor.spec.js
+++ b/js/src/ui/GasPriceEditor/gasPriceEditor.spec.js
@@ -21,26 +21,64 @@ import sinon from 'sinon';
import GasPriceEditor from './';
-const api = {
- util: {
- fromWei: (value) => new BigNumber(value)
- }
-};
+let api;
+let component;
+let store;
-const store = {
- estimated: '123',
- histogram: {},
- priceDefault: '456',
- totalValue: '789',
- setGas: sinon.stub(),
- setPrice: sinon.stub()
-};
+function createApi () {
+ api = {
+ eth: {
+ blockNumber: sinon.stub().resolves(new BigNumber(3))
+ },
+ util: {
+ fromWei: (value) => new BigNumber(value)
+ }
+ };
+
+ return api;
+}
+
+function createStore () {
+ createApi();
+
+ store = {
+ _api: api,
+ conditionType: 'none',
+ estimated: '123',
+ histogram: {},
+ priceDefault: '456',
+ totalValue: '789',
+ setGas: sinon.stub(),
+ setPrice: sinon.stub()
+ };
+
+ return store;
+}
+
+function render (props = {}) {
+ createStore();
+
+ component = shallow(
+
,
+ {
+ context: {
+ api
+ }
+ }
+ );
+
+ return component;
+}
describe('ui/GasPriceEditor', () => {
+ beforeEach(() => {
+ render();
+ });
+
it('renders', () => {
- expect(shallow(
-
,
- { context: { api } }
- )).to.be.ok;
+ expect(component).to.be.ok;
});
});
diff --git a/js/src/ui/GasPriceEditor/store.js b/js/src/ui/GasPriceEditor/store.js
index c050a44ce..867fca11a 100644
--- a/js/src/ui/GasPriceEditor/store.js
+++ b/js/src/ui/GasPriceEditor/store.js
@@ -20,7 +20,17 @@ import { action, computed, observable, transaction } from 'mobx';
import { ERRORS, validatePositiveNumber } from '~/util/validation';
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
+const CONDITIONS = {
+ NONE: 'none',
+ BLOCK: 'blockNumber',
+ TIME: 'timestamp'
+};
+
export default class GasPriceEditor {
+ @observable blockNumber = 0;
+ @observable condition = {};
+ @observable conditionBlockError = null;
+ @observable conditionType = CONDITIONS.NONE;
@observable errorEstimated = null;
@observable errorGas = null;
@observable errorPrice = null;
@@ -34,13 +44,23 @@ export default class GasPriceEditor {
@observable priceDefault;
@observable weiValue = '0';
- constructor (api, { gas, gasLimit, gasPrice }) {
+ constructor (api, { gas, gasLimit, gasPrice, condition = null }) {
this._api = api;
this.gas = gas;
this.gasLimit = gasLimit;
this.price = gasPrice;
+ if (condition) {
+ if (condition.block) {
+ this.condition = { block: condition.block.toFixed(0) };
+ this.conditionType = CONDITIONS.BLOCK;
+ } else if (condition.time) {
+ this.condition = { time: condition.time };
+ this.conditionType = CONDITIONS.TIME;
+ }
+ }
+
if (api) {
this.loadDefaults();
}
@@ -54,6 +74,39 @@ export default class GasPriceEditor {
}
}
+ @action setConditionType = (conditionType = CONDITIONS.NONE) => {
+ transaction(() => {
+ this.conditionBlockError = null;
+ this.conditionType = conditionType;
+
+ switch (conditionType) {
+ case CONDITIONS.BLOCK:
+ this.condition = Object.assign({}, this.condition, { block: this.blockNumber || 1 });
+ break;
+
+ case CONDITIONS.TIME:
+ this.condition = Object.assign({}, this.condition, { time: new Date() });
+ break;
+
+ case CONDITIONS.NONE:
+ default:
+ this.condition = {};
+ break;
+ }
+ });
+ }
+
+ @action setConditionBlockNumber = (block) => {
+ transaction(() => {
+ this.conditionBlockError = validatePositiveNumber(block).numberError;
+ this.condition = Object.assign({}, this.condition, { block });
+ });
+ }
+
+ @action setConditionDateTime = (time) => {
+ this.condition = Object.assign({}, this.condition, { time });
+ }
+
@action setEditing = (isEditing) => {
this.isEditing = isEditing;
}
@@ -130,9 +183,10 @@ export default class GasPriceEditor {
bucket_bounds: [],
counts: []
})),
- this._api.eth.gasPrice()
+ this._api.eth.gasPrice(),
+ this._api.eth.blockNumber()
])
- .then(([histogram, _price]) => {
+ .then(([histogram, _price, blockNumber]) => {
transaction(() => {
const price = _price.toFixed(0);
@@ -142,6 +196,7 @@ export default class GasPriceEditor {
this.setHistogram(histogram);
this.priceDefault = price;
+ this.blockNumber = blockNumber.toNumber();
});
})
.catch((error) => {
@@ -150,13 +205,37 @@ export default class GasPriceEditor {
}
overrideTransaction = (transaction) => {
- if (this.errorGas || this.errorPrice) {
+ if (this.errorGas || this.errorPrice || this.conditionBlockError) {
return transaction;
}
- return Object.assign({}, transaction, {
+ const override = {
+ condition: this.condition,
gas: new BigNumber(this.gas || DEFAULT_GAS),
gasPrice: new BigNumber(this.price || DEFAULT_GASPRICE)
- });
+ };
+
+ const result = Object.assign({}, transaction, override);
+
+ switch (this.conditionType) {
+ case CONDITIONS.BLOCK:
+ result.condition = { block: new BigNumber(this.condition.block || 0) };
+ break;
+
+ case CONDITIONS.TIME:
+ result.condition = { time: this.condition.time };
+ break;
+
+ case CONDITIONS.NONE:
+ default:
+ delete result.condition;
+ break;
+ }
+
+ return result;
}
}
+
+export {
+ CONDITIONS
+};
diff --git a/js/src/ui/GasPriceEditor/store.spec.js b/js/src/ui/GasPriceEditor/store.spec.js
index 34b4d69f8..e4c0f0849 100644
--- a/js/src/ui/GasPriceEditor/store.spec.js
+++ b/js/src/ui/GasPriceEditor/store.spec.js
@@ -21,6 +21,7 @@ import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/consta
import { ERRORS } from '~/util/validation';
import GasPriceEditor from './gasPriceEditor';
+import { CONDITIONS } from './store';
const { Store } = GasPriceEditor;
@@ -31,18 +32,30 @@ const HISTOGRAM = {
counts: [3, 4]
};
-const api = {
- eth: {
- gasPrice: sinon.stub().resolves(GASPRICE)
- },
- parity: {
- gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
- }
-};
+let api;
-describe('ui/GasPriceEditor/store', () => {
+// TODO: share with gasPriceEditor.spec.js
+function createApi () {
+ api = {
+ eth: {
+ blockNumber: sinon.stub().resolves(new BigNumber(2)),
+ gasPrice: sinon.stub().resolves(GASPRICE)
+ },
+ parity: {
+ gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
+ }
+ };
+
+ return api;
+}
+
+describe('ui/GasPriceEditor/Store', () => {
let store = null;
+ beforeEach(() => {
+ createApi();
+ });
+
it('is available via GasPriceEditor.Store', () => {
expect(new Store(null, {})).to.be.ok;
});
@@ -65,6 +78,7 @@ describe('ui/GasPriceEditor/store', () => {
describe('constructor (defaults) when histogram not available', () => {
const api = {
eth: {
+ blockNumber: sinon.stub().resolves(new BigNumber(2)),
gasPrice: sinon.stub().resolves(GASPRICE)
},
parity: {
@@ -92,6 +106,67 @@ describe('ui/GasPriceEditor/store', () => {
store = new Store(null, { gasLimit: GASLIMIT });
});
+ describe('setConditionType', () => {
+ it('sets the actual type', () => {
+ store.setConditionType('testingType');
+ expect(store.conditionType).to.equal('testingType');
+ });
+
+ it('clears any block error on changing type', () => {
+ store.setConditionBlockNumber(-1);
+ expect(store.conditionBlockError).not.to.be.null;
+ store.setConditionType(CONDITIONS.BLOCK);
+ expect(store.conditionBlockError).to.be.null;
+ });
+
+ it('sets condition.block when type === CONDITIONS.BLOCK', () => {
+ store.setConditionType(CONDITIONS.BLOCK);
+ expect(store.condition.block).to.be.ok;
+ });
+
+ it('clears condition when type === CONDITIONS.NONE', () => {
+ store.setConditionType(CONDITIONS.BLOCK);
+ store.setConditionType(CONDITIONS.NONE);
+ expect(store.condition).to.deep.equal({});
+ });
+
+ it('sets condition.time when type === CONDITIONS.TIME', () => {
+ store.setConditionType(CONDITIONS.TIME);
+ expect(store.condition.time).to.be.ok;
+ });
+ });
+
+ describe('setConditionBlockNumber', () => {
+ beforeEach(() => {
+ store.setConditionBlockNumber('testingBlock');
+ });
+
+ it('sets the blockNumber', () => {
+ expect(store.condition.block).to.equal('testingBlock');
+ });
+
+ it('sets the error on invalid numbers', () => {
+ expect(store.conditionBlockError).not.to.be.null;
+ });
+
+ it('sets the error on negative numbers', () => {
+ store.setConditionBlockNumber(-1);
+ expect(store.conditionBlockError).not.to.be.null;
+ });
+
+ it('clears the error on positive numbers', () => {
+ store.setConditionBlockNumber(1000);
+ expect(store.conditionBlockError).to.be.null;
+ });
+ });
+
+ describe('setConditionDateTime', () => {
+ it('sets the datatime', () => {
+ store.setConditionDateTime('testingDateTime');
+ expect(store.condition.time).to.equal('testingDateTime');
+ });
+ });
+
describe('setEditing', () => {
it('sets the value', () => {
expect(store.isEditing).to.be.false;
diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js
index fa3c9641f..25f031c62 100644
--- a/js/src/ui/MethodDecoding/methodDecoding.js
+++ b/js/src/ui/MethodDecoding/methodDecoding.js
@@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see
.
+import { CircularProgress } from 'material-ui';
+import moment from 'moment';
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
-import CircularProgress from 'material-ui/CircularProgress';
import { TypedInput, InputAddress } from '../Form';
import MethodDecodingStore from './methodDecodingStore';
@@ -128,15 +129,25 @@ class MethodDecoding extends Component {
renderMinBlock () {
const { historic, transaction } = this.props;
- const { minBlock } = transaction;
+ const { condition } = transaction;
- if (!minBlock || minBlock.eq(0)) {
+ if (!condition) {
return null;
}
- return (
-
, { historic ? 'Submitted' : 'Submission' } at block #{ minBlock.toFormat(0) }
- );
+ if (condition.block && condition.block.gt(0)) {
+ return (
+
, { historic ? 'Submitted' : 'Submission' } at block #{ condition.block.toFormat(0) }
+ );
+ }
+
+ if (condition.time) {
+ return (
+
, { historic ? 'Submitted' : 'Submission' } at { moment(condition.time).format('LLLL') }
+ );
+ }
+
+ return null;
}
renderAction () {
diff --git a/js/src/ui/index.js b/js/src/ui/index.js
index 0994d53d9..eed56d369 100644
--- a/js/src/ui/index.js
+++ b/js/src/ui/index.js
@@ -35,7 +35,7 @@ import DappIcon from './DappIcon';
import Editor from './Editor';
import Errors from './Errors';
import Features, { FEATURES, FeaturesStore } from './Features';
-import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
+import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form';
import GasPriceEditor from './GasPriceEditor';
import GasPriceSelector from './GasPriceSelector';
import Icons from './Icons';
@@ -95,9 +95,12 @@ export {
InputAddress,
InputAddressSelect,
InputChip,
+ InputDate,
InputInline,
+ InputTime,
IdentityIcon,
IdentityName,
+ Label,
LanguageSelector,
Loading,
MethodDecoding,
diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css
index 0bd752b82..6693f5727 100644
--- a/js/src/views/ParityBar/parityBar.css
+++ b/js/src/views/ParityBar/parityBar.css
@@ -15,6 +15,9 @@
/* along with Parity. If not, see
.
*/
+$overlayZ: 10000;
+$modalZ: 10001;
+
.account {
display: flex;
flex: 1;
@@ -61,7 +64,7 @@
bottom: 0;
left: 0;
background: rgba(255, 255, 255, 0.5);
- z-index: 10000;
+ z-index: $overlayZ;
user-select: none;
}
@@ -70,7 +73,7 @@
position: fixed;
font-size: 16px;
font-family: 'Roboto', sans-serif;
- z-index: 10001;
+ z-index: $modalZ;
user-select: none;
}
@@ -109,7 +112,8 @@
border-radius: 4px 4px 0 0;
display: flex;
flex-direction: column;
- max-height: 50vh;
+ min-height: 30vh;
+ max-height: 80vh;
max-width: calc(100vw - 1em);
.content {
diff --git a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js
index be4887fec..812d1fb35 100644
--- a/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js
+++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js
@@ -73,14 +73,14 @@ export default class TransactionMainDetails extends Component {
: transaction
}
/>
- { this.renderEditGas() }
+ { this.renderEditTx() }