- You can choose the gas price based on the
- distribution of recent included transaction gas prices.
- The lower the gas price is, the cheaper the transaction will
- be. The higher the gas price is, the faster it should
- get mined by the network.
+ You can choose the gas price based on the distribution of recent included transaction gas prices. The lower the gas price is, the cheaper the transaction will be. The higher the gas price is, the faster it should get mined by the network.
-
+ hint='the amount of gas to use for the transaction'
+ label={ gasLabel }
+ onChange={ this.onEditGas }
+ value={ gas } />
+ hint='the price of gas to use for the transaction'
+ label={ priceLabel }
+ onChange={ this.onEditGasPrice }
+ value={ price } />
-
+
+ { children }
+
);
diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.spec.js b/js/src/ui/GasPriceEditor/gasPriceEditor.spec.js
new file mode 100644
index 000000000..4197bc876
--- /dev/null
+++ b/js/src/ui/GasPriceEditor/gasPriceEditor.spec.js
@@ -0,0 +1,45 @@
+// 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 BigNumber from 'bignumber.js';
+import { shallow } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+
+import GasPriceEditor from './';
+
+const api = {
+ util: {
+ fromWei: (value) => new BigNumber(value)
+ }
+};
+
+const store = {
+ estimated: '123',
+ priceDefault: '456',
+ totalValue: '789',
+ setGas: sinon.stub(),
+ setPrice: sinon.stub()
+};
+
+describe('ui/GasPriceEditor', () => {
+ it('renders', () => {
+ expect(shallow(
+ ,
+ { context: { api } }
+ )).to.be.ok;
+ });
+});
diff --git a/js/src/ui/GasPriceEditor/store.js b/js/src/ui/GasPriceEditor/store.js
index afa5e15b2..4fc16b83c 100644
--- a/js/src/ui/GasPriceEditor/store.js
+++ b/js/src/ui/GasPriceEditor/store.js
@@ -26,18 +26,22 @@ export default class GasPriceEditor {
@observable errorPrice = null;
@observable errorTotal = null;
@observable estimated = DEFAULT_GAS;
+ @observable gas;
+ @observable gasLimit;
@observable histogram = null;
- @observable price = DEFAULT_GASPRICE;
- @observable priceDefault = DEFAULT_GASPRICE;
- @observable gas = DEFAULT_GAS;
- @observable gasLimit = 0;
+ @observable isEditing = false;
+ @observable price;
+ @observable priceDefault;
@observable weiValue = '0';
- constructor (api, gasLimit, loadDefaults = true) {
+ constructor (api, { gas, gasLimit, gasPrice }) {
this._api = api;
- this.gasLimit = gasLimit;
- if (loadDefaults) {
+ this.gas = gas;
+ this.gasLimit = gasLimit;
+ this.price = gasPrice;
+
+ if (api) {
this.loadDefaults();
}
}
@@ -50,6 +54,10 @@ export default class GasPriceEditor {
}
}
+ @action setEditing = (isEditing) => {
+ this.isEditing = isEditing;
+ }
+
@action setErrorTotal = (errorTotal) => {
this.errorTotal = errorTotal;
}
@@ -74,6 +82,30 @@ export default class GasPriceEditor {
this.weiValue = weiValue;
}
+ @action setGas = (gas) => {
+ transaction(() => {
+ const { numberError } = validatePositiveNumber(gas);
+
+ this.gas = gas;
+
+ if (numberError) {
+ this.errorGas = numberError;
+ } else {
+ const bn = new BigNumber(gas);
+
+ if (bn.gte(this.gasLimit)) {
+ this.errorGas = ERRORS.gasBlockLimit;
+ } else {
+ this.errorGas = null;
+ }
+ }
+ });
+ }
+
+ @action setGasLimit = (gasLimit) => {
+ this.gasLimit = gasLimit;
+ }
+
@action setHistogram = (gasHistogram) => {
this.histogram = gasHistogram;
}
@@ -85,39 +117,37 @@ export default class GasPriceEditor {
});
}
- @action setGas = (gas) => {
- transaction(() => {
- const { numberError } = validatePositiveNumber(gas);
- const bn = new BigNumber(gas);
-
- this.gas = gas;
-
- if (numberError) {
- this.errorGas = numberError;
- } else if (bn.gte(this.gasLimit)) {
- this.errorGas = ERRORS.gasBlockLimit;
- } else {
- this.errorGas = null;
- }
- });
- }
-
@action loadDefaults () {
Promise
.all([
this._api.parity.gasPriceHistogram(),
this._api.eth.gasPrice()
])
- .then(([gasPriceHistogram, gasPrice]) => {
+ .then(([histogram, _price]) => {
transaction(() => {
- this.setPrice(gasPrice.toFixed(0));
- this.setHistogram(gasPriceHistogram);
+ const price = _price.toFixed(0);
- this.priceDefault = gasPrice.toFixed();
+ if (!this.price) {
+ this.setPrice(price);
+ }
+ this.setHistogram(histogram);
+
+ this.priceDefault = price;
});
})
.catch((error) => {
console.warn('getDefaults', error);
});
}
+
+ overrideTransaction = (transaction) => {
+ if (this.errorGas || this.errorPrice) {
+ return transaction;
+ }
+
+ return Object.assign({}, transaction, {
+ gas: new BigNumber(this.gas || DEFAULT_GAS),
+ gasPrice: new BigNumber(this.price || DEFAULT_GASPRICE)
+ });
+ }
}
diff --git a/js/src/ui/GasPriceEditor/store.spec.js b/js/src/ui/GasPriceEditor/store.spec.js
new file mode 100644
index 000000000..bbee1b8c4
--- /dev/null
+++ b/js/src/ui/GasPriceEditor/store.spec.js
@@ -0,0 +1,197 @@
+// 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 BigNumber from 'bignumber.js';
+import sinon from 'sinon';
+
+import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
+import { ERRORS } from '~/util/validation';
+
+import GasPriceEditor from './gasPriceEditor';
+
+const { Store } = GasPriceEditor;
+
+const GASPRICE = new BigNumber(123456);
+const GASLIMIT = 100000;
+const HISTOGRAM = {
+ bucketBounds: [1, 2],
+ counts: [3, 4]
+};
+
+const api = {
+ eth: {
+ gasPrice: sinon.stub().resolves(GASPRICE)
+ },
+ parity: {
+ gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
+ }
+};
+
+describe('ui/GasPriceEditor/store', () => {
+ let store = null;
+
+ it('is available via GasPriceEditor.Store', () => {
+ expect(new Store(null, {})).to.be.ok;
+ });
+
+ describe('constructor (defaults)', () => {
+ beforeEach(() => {
+ store = new Store(api, { gasLimit: GASLIMIT });
+ });
+
+ it('retrieves the histogram and gasPrice', () => {
+ expect(api.eth.gasPrice).to.have.been.called;
+ expect(api.parity.gasPriceHistogram).to.have.been.called;
+ });
+
+ it('sets the gasLimit as passed', () => {
+ expect(store.gasLimit).to.equal(GASLIMIT);
+ });
+ });
+
+ describe('setters', () => {
+ beforeEach(() => {
+ store = new Store(null, { gasLimit: GASLIMIT });
+ });
+
+ describe('setEditing', () => {
+ it('sets the value', () => {
+ expect(store.isEditing).to.be.false;
+ store.setEditing(true);
+ expect(store.isEditing).to.be.true;
+ });
+ });
+
+ describe('setErrorTotal', () => {
+ it('sets the value', () => {
+ store.setErrorTotal('errorTotal');
+ expect(store.errorTotal).to.equal('errorTotal');
+ });
+ });
+
+ describe('setEstimated', () => {
+ it('sets the value', () => {
+ store.setEstimated('789');
+ expect(store.estimated).to.equal('789');
+ });
+
+ it('sets error when above exception max', () => {
+ store.setEstimated(MAX_GAS_ESTIMATION);
+ expect(store.errorEstimated).to.equal(ERRORS.gasException);
+ });
+
+ it('sets error when above gaslimit', () => {
+ store.setEstimated(GASLIMIT);
+ expect(store.errorEstimated).to.equal(ERRORS.gasBlockLimit);
+ });
+ });
+
+ describe('setEthValue', () => {
+ it('sets the value', () => {
+ store.setEthValue('123');
+ expect(store.weiValue).to.equal('123');
+ });
+ });
+
+ describe('setGas', () => {
+ it('sets the value', () => {
+ store.setGas('123');
+ expect(store.gas).to.equal('123');
+ expect(store.errorGas).to.be.null;
+ });
+
+ it('sets error on negative numbers', () => {
+ store.setGas(-123);
+ expect(store.errorGas).not.to.be.null;
+ });
+
+ it('sets error when above block limit', () => {
+ store.setGas(GASLIMIT);
+ expect(store.errorGas).to.equal(ERRORS.gasBlockLimit);
+ });
+ });
+
+ describe('setGasLimit', () => {
+ it('sets the value', () => {
+ store.setGasLimit('123');
+ expect(store.gasLimit).to.equal('123');
+ });
+ });
+
+ describe('setHistogram', () => {
+ it('sets the value', () => {
+ store.setHistogram(HISTOGRAM);
+ expect(store.histogram).to.deep.equal(HISTOGRAM);
+ });
+ });
+
+ describe('setPrice', () => {
+ it('sets the value', () => {
+ store.setPrice('123');
+ expect(store.price).to.equal('123');
+ expect(store.errorPrice).to.be.null;
+ });
+
+ it('sets error on negative numbers', () => {
+ store.setPrice(-123);
+ expect(store.errorPrice).not.to.be.null;
+ });
+ });
+ });
+
+ describe('computed', () => {
+ beforeEach(() => {
+ store = new Store(null, { gasLimit: GASLIMIT });
+ });
+
+ describe('totalValue', () => {
+ it('holds the total including eth, price & gas', () => {
+ store.setPrice('123');
+ store.setGas('123');
+ store.setEthValue('123');
+ expect(store.totalValue).to.deep.equal(new BigNumber(123 + 123 * 123));
+ });
+ });
+ });
+
+ describe('methods', () => {
+ beforeEach(() => {
+ store = new Store(null, { gasLimit: GASLIMIT });
+ });
+
+ describe('overrideTransaction', () => {
+ const TRANSACTION = { gas: '123', gasPrice: '456' };
+
+ it('overrides gas & gasPrice with values', () => {
+ store.setGas(DEFAULT_GAS);
+ const transaction = store.overrideTransaction(TRANSACTION);
+
+ expect(transaction.gas).to.deep.equal(new BigNumber(DEFAULT_GAS));
+ expect(transaction.gasPrice).to.deep.equal(new BigNumber(DEFAULT_GASPRICE));
+ });
+
+ it('does not override with invalid gas', () => {
+ store.setGas(-123);
+ expect(store.overrideTransaction(TRANSACTION)).to.deep.equal(TRANSACTION);
+ });
+
+ it('does not override with invalid price', () => {
+ store.setPrice(-123);
+ expect(store.overrideTransaction(TRANSACTION)).to.deep.equal(TRANSACTION);
+ });
+ });
+ });
+});
diff --git a/js/src/ui/GasPriceSelector/CustomBar/customBar.js b/js/src/ui/GasPriceSelector/CustomBar/customBar.js
new file mode 100644
index 000000000..60f066f97
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomBar/customBar.js
@@ -0,0 +1,77 @@
+// 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 { Rectangle } from 'recharts';
+
+import { COLORS } from '../util';
+
+export default class CustomBar extends Component {
+ static propTypes = {
+ selected: PropTypes.number,
+ x: PropTypes.number,
+ y: PropTypes.number,
+ width: PropTypes.number,
+ height: PropTypes.number,
+ index: PropTypes.number,
+ onClick: PropTypes.func
+ }
+
+ render () {
+ const { x, y, selected, index, width, height, onClick } = this.props;
+
+ const fill = selected === index
+ ? COLORS.selected
+ : COLORS.default;
+
+ const borderWidth = 0.5;
+ const borderColor = 'rgba(255, 255, 255, 0.5)';
+
+ return (
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/js/src/ui/GasPriceSelector/CustomBar/index.js b/js/src/ui/GasPriceSelector/CustomBar/index.js
new file mode 100644
index 000000000..fa4008e42
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomBar/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 './customBar';
diff --git a/js/src/ui/GasPriceSelector/CustomCursor/customCursor.js b/js/src/ui/GasPriceSelector/CustomCursor/customCursor.js
new file mode 100644
index 000000000..8ce9a0e42
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomCursor/customCursor.js
@@ -0,0 +1,73 @@
+// 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 { Rectangle } from 'recharts';
+
+import { COLORS, countModifier } from '../util';
+
+export default class CustomCursor extends Component {
+ static propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ width: PropTypes.number,
+ height: PropTypes.number,
+ onClick: PropTypes.func,
+ getIndex: PropTypes.func,
+ counts: PropTypes.object,
+ yDomain: PropTypes.array
+ }
+
+ render () {
+ const { x, y, width, height, getIndex, counts, yDomain } = this.props;
+
+ const index = getIndex();
+
+ if (index === -1) {
+ return null;
+ }
+
+ const count = countModifier(counts[index]);
+ const barHeight = (count / yDomain[1]) * (y + height);
+
+ return (
+
+
+
+
+ );
+ }
+
+ onClick = () => {
+ const { onClick, getIndex } = this.props;
+ const index = getIndex();
+ onClick({ index });
+ }
+}
diff --git a/js/src/ui/GasPriceSelector/CustomCursor/index.js b/js/src/ui/GasPriceSelector/CustomCursor/index.js
new file mode 100644
index 000000000..8129d97f0
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomCursor/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 './customCursor';
diff --git a/js/src/ui/GasPriceSelector/CustomShape/customShape.js b/js/src/ui/GasPriceSelector/CustomShape/customShape.js
new file mode 100644
index 000000000..1c721ab35
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomShape/customShape.js
@@ -0,0 +1,52 @@
+// 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 { Dot } from 'recharts';
+
+export default class CustomShape extends Component {
+ static propTypes = {
+ showValue: PropTypes.number.isRequired,
+ cx: PropTypes.number,
+ cy: PropTypes.number,
+ payload: PropTypes.object
+ }
+
+ render () {
+ const { cx, cy, showValue, payload } = this.props;
+
+ if (showValue !== payload.y) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ );
+ }
+}
diff --git a/js/src/ui/GasPriceSelector/CustomShape/index.js b/js/src/ui/GasPriceSelector/CustomShape/index.js
new file mode 100644
index 000000000..663c6b91b
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomShape/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 './customShape';
diff --git a/js/src/ui/GasPriceSelector/CustomTooltip/customTooltip.js b/js/src/ui/GasPriceSelector/CustomTooltip/customTooltip.js
new file mode 100644
index 000000000..4fd165cb0
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomTooltip/customTooltip.js
@@ -0,0 +1,53 @@
+// 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';
+
+export default class CustomTooltip extends Component {
+ static propTypes = {
+ active: PropTypes.bool,
+ histogram: PropTypes.object.isRequired,
+ label: PropTypes.number,
+ payload: PropTypes.array,
+ type: PropTypes.string
+ }
+
+ render () {
+ const { active, label, histogram } = this.props;
+
+ if (!active) {
+ return null;
+ }
+
+ const index = label;
+
+ const count = histogram.counts[index];
+ const minprice = histogram.bucketBounds[index];
+ const maxprice = histogram.bucketBounds[index + 1];
+
+ return (
+
+
+ { count.toNumber() } transactions
+ with gas price set from
+ { minprice.toFormat(0) }
+ to
+ { maxprice.toFormat(0) }
+
+
+ );
+ }
+}
diff --git a/js/src/ui/GasPriceSelector/CustomTooltip/index.js b/js/src/ui/GasPriceSelector/CustomTooltip/index.js
new file mode 100644
index 000000000..a16f9d2f9
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/CustomTooltip/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 './customTooltip';
diff --git a/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.css b/js/src/ui/GasPriceSelector/gasPriceSelector.css
similarity index 92%
rename from js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.css
rename to js/src/ui/GasPriceSelector/gasPriceSelector.css
index 247211c50..ce5bd55e8 100644
--- a/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.css
+++ b/js/src/ui/GasPriceSelector/gasPriceSelector.css
@@ -20,8 +20,12 @@
width: 100%;
}
-.columns {
+.chartRow, .sliderRow {
display: flex;
flex-wrap: wrap;
position: relative;
}
+
+.chartRow {
+ margin-bottom: -24px;
+}
diff --git a/js/src/ui/GasPriceSelector/gasPriceSelector.js b/js/src/ui/GasPriceSelector/gasPriceSelector.js
new file mode 100644
index 000000000..e37dc8b39
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/gasPriceSelector.js
@@ -0,0 +1,341 @@
+// 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 BigNumber from 'bignumber.js';
+import { Slider } from 'material-ui';
+import React, { Component, PropTypes } from 'react';
+import { Bar, BarChart, ResponsiveContainer, Scatter, ScatterChart, Tooltip, XAxis, YAxis } from 'recharts';
+
+import CustomBar from './CustomBar';
+import CustomCursor from './CustomCursor';
+import CustomShape from './CustomShape';
+import CustomTooltip from './CustomTooltip';
+
+import { COLORS, countModifier } from './util';
+
+import styles from './gasPriceSelector.css';
+
+const TOOL_STYLE = {
+ color: 'rgba(255,255,255,0.5)',
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
+ padding: '0 0.5em',
+ fontSize: '0.75em'
+};
+
+export default class GasPriceSelector extends Component {
+ static propTypes = {
+ histogram: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ price: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object
+ ])
+ }
+
+ state = {
+ price: null,
+ sliderValue: 0.5,
+ selectedIndex: 0,
+
+ chartData: {
+ values: [],
+ xDomain: [],
+ yDomain: [],
+ N: 0
+ }
+ }
+
+ componentWillMount () {
+ this.computeCharts();
+ this.setprice();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.price !== this.props.price) {
+ this.setprice(nextProps);
+ }
+ }
+
+ componentWillUpdate (nextProps, nextState) {
+ if (Math.floor(nextState.sliderValue) !== Math.floor(this.state.sliderValue)) {
+ this.updateSelectedBarChart(nextState);
+ }
+ }
+
+ render () {
+ return (
+
+ );
+ }
+
+ renderCustomCursor = () => {
+ const { histogram } = this.props;
+ const { chartData } = this.state;
+
+ return (
+
+ );
+ }
+
+ getBarHoverIndex = () => {
+ const { barChart } = this.refs;
+
+ if (!barChart || !barChart.state) {
+ return -1;
+ }
+
+ return barChart.state.activeTooltipIndex;
+ }
+
+ computeChartsData () {
+ const { priceChartData } = this.state;
+ const { histogram } = this.props;
+
+ const values = priceChartData
+ .map((value, index) => ({ value, index }));
+
+ const N = values.length - 1;
+ const maxGasCounts = countModifier(
+ histogram
+ .counts
+ .reduce((max, count) => count.greaterThan(max) ? count : max, 0)
+ );
+
+ const xDomain = [0, N];
+ const yDomain = [0, maxGasCounts * 1.1];
+
+ const chartData = {
+ values, N,
+ xDomain, yDomain
+ };
+
+ this.setState({ chartData }, () => {
+ this.updateSelectedBarChart();
+ });
+ }
+
+ computeCharts (props = this.props) {
+ const { histogram } = props;
+
+ const priceChartData = histogram
+ .counts
+ .map(count => countModifier(count));
+
+ this.setState(
+ { priceChartData },
+ () => this.computeChartsData()
+ );
+ }
+
+ updateSelectedBarChart (state = this.state) {
+ }
+
+ setprice (props = this.props) {
+ const { price, histogram } = props;
+
+ // If no gas price yet...
+ if (!price) {
+ return this.setSliderValue(0.5);
+ }
+
+ const bnprice = (typeof price === 'string')
+ ? new BigNumber(price)
+ : price;
+
+ // If gas price hasn't changed
+ if (this.state.price && bnprice.equals(this.state.price)) {
+ return;
+ }
+
+ const prices = histogram.bucketBounds;
+ const startIndex = prices
+ .findIndex(price => price.greaterThan(bnprice)) - 1;
+
+ // price Lower than the max in histogram
+ if (startIndex === -1) {
+ return this.setSliderValue(0, bnprice);
+ }
+
+ // price Greater than the max in histogram
+ if (startIndex === -2) {
+ return this.setSliderValue(1, bnprice);
+ }
+
+ const priceA = prices[startIndex];
+ const priceB = prices[startIndex + 1];
+
+ const sliderValueDec = bnprice
+ .minus(priceA)
+ .dividedBy(priceB.minus(priceA))
+ .toNumber();
+
+ const sliderValue = (startIndex + sliderValueDec) / (prices.length - 1);
+ this.setSliderValue(sliderValue, bnprice);
+ }
+
+ setSliderValue (value, price = this.state.price) {
+ const { histogram } = this.props;
+
+ const N = histogram.bucketBounds.length - 1;
+
+ const sliderValue = Math.max(0, Math.min(value, 1));
+ const selectedIndex = Math.floor(sliderValue * N);
+
+ this.setState({ sliderValue, price, selectedIndex });
+ }
+
+ onBarChartMouseUp = (event) => {
+ console.log(event);
+ }
+
+ onClickprice = (bar) => {
+ const { index } = bar;
+
+ const ratio = (index + 0.5) / (this.state.chartData.N + 1);
+
+ this.onEditpriceSlider(null, ratio);
+ }
+
+ onEditpriceSlider = (event, sliderValue) => {
+ const { histogram } = this.props;
+
+ const prices = histogram.bucketBounds;
+ const N = prices.length - 1;
+ const priceAIdx = Math.floor(sliderValue * N);
+ const priceBIdx = priceAIdx + 1;
+
+ if (priceBIdx === N + 1) {
+ const price = prices[priceAIdx].round();
+ this.props.onChange(event, price);
+ return;
+ }
+
+ const priceA = prices[priceAIdx];
+ const priceB = prices[priceBIdx];
+
+ const mult = Math.round((sliderValue % 1) * 100) / 100;
+ const price = priceA
+ .plus(priceB.minus(priceA).times(mult))
+ .round();
+
+ this.setSliderValue(sliderValue, price);
+ this.props.onChange(event, price.toFixed());
+ }
+}
diff --git a/js/src/ui/GasPriceEditor/GasPriceSelector/index.js b/js/src/ui/GasPriceSelector/index.js
similarity index 100%
rename from js/src/ui/GasPriceEditor/GasPriceSelector/index.js
rename to js/src/ui/GasPriceSelector/index.js
diff --git a/js/src/ui/GasPriceSelector/util.js b/js/src/ui/GasPriceSelector/util.js
new file mode 100644
index 000000000..63c3e89b0
--- /dev/null
+++ b/js/src/ui/GasPriceSelector/util.js
@@ -0,0 +1,34 @@
+// 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 .
+
+const COLORS = {
+ default: 'rgba(255, 99, 132, 0.2)',
+ selected: 'rgba(255, 99, 132, 0.5)',
+ hover: 'rgba(255, 99, 132, 0.15)',
+ grid: 'rgba(255, 99, 132, 0.5)',
+ line: 'rgb(255, 99, 132)',
+ intersection: '#fff'
+};
+
+const countModifier = (count) => {
+ const val = count.toNumber ? count.toNumber() : count;
+ return Math.log10(val + 1) + 0.1;
+};
+
+export {
+ COLORS,
+ countModifier
+};
diff --git a/js/src/ui/index.js b/js/src/ui/index.js
index 7cf3e7ee5..636685410 100644
--- a/js/src/ui/index.js
+++ b/js/src/ui/index.js
@@ -32,6 +32,7 @@ import Editor from './Editor';
import Errors from './Errors';
import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
import GasPriceEditor from './GasPriceEditor';
+import GasPriceSelector from './GasPriceSelector';
import IdentityIcon from './IdentityIcon';
import IdentityName from './IdentityName';
import LanguageSelector from './LanguageSelector';
@@ -70,6 +71,7 @@ export {
Form,
FormWrap,
GasPriceEditor,
+ GasPriceSelector,
Input,
InputAddress,
InputAddressSelect,
diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css
index dab138238..d6fd3a538 100644
--- a/js/src/views/ParityBar/parityBar.css
+++ b/js/src/views/ParityBar/parityBar.css
@@ -46,7 +46,7 @@
border-radius: 4px 4px 0 0;
display: flex;
flex-direction: column;
- max-height: 19em;
+ max-height: 50vh;
}
.expanded .content {
diff --git a/js/src/views/Signer/components/Account/AccountLink/AccountLink.css b/js/src/views/Signer/components/Account/AccountLink/accountLink.css
similarity index 100%
rename from js/src/views/Signer/components/Account/AccountLink/AccountLink.css
rename to js/src/views/Signer/components/Account/AccountLink/accountLink.css
diff --git a/js/src/views/Signer/components/Account/AccountLink/AccountLink.js b/js/src/views/Signer/components/Account/AccountLink/accountLink.js
similarity index 97%
rename from js/src/views/Signer/components/Account/AccountLink/AccountLink.js
rename to js/src/views/Signer/components/Account/AccountLink/accountLink.js
index f42675474..4a5578acf 100644
--- a/js/src/views/Signer/components/Account/AccountLink/AccountLink.js
+++ b/js/src/views/Signer/components/Account/AccountLink/accountLink.js
@@ -17,7 +17,7 @@
import React, { Component, PropTypes } from 'react';
import { addressLink } from '~/3rdparty/etherscan/links';
-import styles from './AccountLink.css';
+import styles from './accountLink.css';
export default class AccountLink extends Component {
static propTypes = {
diff --git a/js/src/views/Signer/components/Account/AccountLink/index.js b/js/src/views/Signer/components/Account/AccountLink/index.js
index 90ae8634a..6c50400f5 100644
--- a/js/src/views/Signer/components/Account/AccountLink/index.js
+++ b/js/src/views/Signer/components/Account/AccountLink/index.js
@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-export default from './AccountLink';
+export default from './accountLink';
diff --git a/js/src/views/Signer/components/Account/Account.css b/js/src/views/Signer/components/Account/account.css
similarity index 100%
rename from js/src/views/Signer/components/Account/Account.css
rename to js/src/views/Signer/components/Account/account.css
diff --git a/js/src/views/Signer/components/Account/Account.js b/js/src/views/Signer/components/Account/account.js
similarity index 98%
rename from js/src/views/Signer/components/Account/Account.js
rename to js/src/views/Signer/components/Account/account.js
index 05ca180c6..79fe04d55 100644
--- a/js/src/views/Signer/components/Account/Account.js
+++ b/js/src/views/Signer/components/Account/account.js
@@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
import { IdentityIcon, IdentityName } from '~/ui';
import AccountLink from './AccountLink';
-import styles from './Account.css';
+import styles from './account.css';
export default class Account extends Component {
static propTypes = {
diff --git a/js/src/views/Signer/components/Account/index.js b/js/src/views/Signer/components/Account/index.js
index c42a8a810..55c215f7c 100644
--- a/js/src/views/Signer/components/Account/index.js
+++ b/js/src/views/Signer/components/Account/index.js
@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-export default from './Account';
+export default from './account';
diff --git a/js/src/views/Signer/components/RequestPending/requestPending.js b/js/src/views/Signer/components/RequestPending/requestPending.js
index d50e5cde5..67195888a 100644
--- a/js/src/views/Signer/components/RequestPending/requestPending.js
+++ b/js/src/views/Signer/components/RequestPending/requestPending.js
@@ -21,21 +21,26 @@ import SignRequest from '../SignRequest';
export default class RequestPending extends Component {
static propTypes = {
+ className: PropTypes.string,
+ date: PropTypes.instanceOf(Date).isRequired,
+ gasLimit: PropTypes.object.isRequired,
id: PropTypes.object.isRequired,
+ isSending: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool.isRequired,
onConfirm: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired,
- isSending: PropTypes.bool.isRequired,
- date: PropTypes.instanceOf(Date).isRequired,
payload: PropTypes.oneOfType([
- PropTypes.shape({ signTransaction: PropTypes.object.isRequired }),
PropTypes.shape({ sendTransaction: PropTypes.object.isRequired }),
- PropTypes.shape({ sign: PropTypes.object.isRequired })
+ PropTypes.shape({ sign: PropTypes.object.isRequired }),
+ PropTypes.shape({ signTransaction: PropTypes.object.isRequired })
]).isRequired,
- className: PropTypes.string,
- isTest: PropTypes.bool.isRequired,
store: PropTypes.object.isRequired
};
+ static defaultProps = {
+ isSending: false
+ };
+
onConfirm = data => {
const { onConfirm, payload } = this.props;
@@ -44,24 +49,23 @@ export default class RequestPending extends Component {
};
render () {
- const { payload, id, className, isSending, date, onReject, isTest, store } = this.props;
+ const { className, date, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
if (payload.sign) {
const { sign } = payload;
return (
+ store={ store } />
);
}
@@ -70,19 +74,19 @@ export default class RequestPending extends Component {
return (
+ transaction={ transaction } />
);
}
- // Unknown payload
+ console.error('RequestPending: Unknown payload', payload);
return null;
}
}
diff --git a/js/src/views/Signer/components/SignRequest/index.js b/js/src/views/Signer/components/SignRequest/index.js
index 4aec1d19c..0fe59540e 100644
--- a/js/src/views/Signer/components/SignRequest/index.js
+++ b/js/src/views/Signer/components/SignRequest/index.js
@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-export default from './SignRequest';
+export default from './signRequest';
diff --git a/js/src/views/Signer/components/SignRequest/SignRequest.css b/js/src/views/Signer/components/SignRequest/signRequest.css
similarity index 100%
rename from js/src/views/Signer/components/SignRequest/SignRequest.css
rename to js/src/views/Signer/components/SignRequest/signRequest.css
diff --git a/js/src/views/Signer/components/SignRequest/SignRequest.js b/js/src/views/Signer/components/SignRequest/signRequest.js
similarity index 97%
rename from js/src/views/Signer/components/SignRequest/SignRequest.js
rename to js/src/views/Signer/components/SignRequest/signRequest.js
index ae4159c71..91acddc34 100644
--- a/js/src/views/Signer/components/SignRequest/SignRequest.js
+++ b/js/src/views/Signer/components/SignRequest/signRequest.js
@@ -21,7 +21,7 @@ import Account from '../Account';
import TransactionPendingForm from '../TransactionPendingForm';
import TxHashLink from '../TxHashLink';
-import styles from './SignRequest.css';
+import styles from './signRequest.css';
@observer
export default class SignRequest extends Component {
@@ -49,7 +49,7 @@ export default class SignRequest extends Component {
const { className } = this.props;
return (
-
+
{ this.renderDetails() }
{ this.renderActions() }
diff --git a/js/src/views/Signer/components/SignRequest/signRequest.spec.js b/js/src/views/Signer/components/SignRequest/signRequest.spec.js
new file mode 100644
index 000000000..53099bb9c
--- /dev/null
+++ b/js/src/views/Signer/components/SignRequest/signRequest.spec.js
@@ -0,0 +1,34 @@
+// 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 { shallow } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+
+import SignRequest from './';
+
+const store = {
+ balances: {},
+ fetchBalance: sinon.stub()
+};
+
+describe('views/Signer/components/SignRequest', () => {
+ it('renders', () => {
+ expect(shallow(
+ ,
+ )).to.be.ok;
+ });
+});
diff --git a/js/src/views/Signer/components/TransactionMainDetails/index.js b/js/src/views/Signer/components/TransactionMainDetails/index.js
index f7b053acd..73420d333 100644
--- a/js/src/views/Signer/components/TransactionMainDetails/index.js
+++ b/js/src/views/Signer/components/TransactionMainDetails/index.js
@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-export default from './TransactionMainDetails';
+export default from './transactionMainDetails';
diff --git a/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.css b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css
similarity index 95%
rename from js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.css
rename to js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css
index 92ed968d1..2a42789ea 100644
--- a/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.css
+++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.css
@@ -17,20 +17,25 @@
@import '../../_layout.css';
-.transaction {
- flex: 1;
- overflow: auto;
-}
-
-.transaction > * {
- display: inline-block;
-}
-
.account {
text-align: center;
}
+.contractIcon {
+ background: #eee;
+ width: 50px !important;
+ height: 50px !important;
+ box-sizing: border-box;
+ border-radius: 50%;
+ padding: 13px;
+}
+
+.editButtonRow {
+ text-align: right;
+}
+
.from {
+ display: inline-block;
width: 40%;
vertical-align: top;
@@ -47,6 +52,7 @@
}
.method {
+ display: inline-block;
width: 60%;
vertical-align: top;
line-height: 1em;
@@ -66,11 +72,7 @@
opacity: .5;
}
-.contractIcon {
- background: #eee;
- width: 50px !important;
- height: 50px !important;
- box-sizing: border-box;
- border-radius: 50%;
- padding: 13px;
+.transaction {
+ flex: 1;
+ overflow: auto;
}
diff --git a/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.js b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js
similarity index 65%
rename from js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.js
rename to js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js
index 0a30991de..1999cfe7f 100644
--- a/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.js
+++ b/js/src/views/Signer/components/TransactionMainDetails/transactionMainDetails.js
@@ -14,52 +14,43 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+import MapsLocalGasStation from 'material-ui/svg-icons/maps/local-gas-station';
import React, { Component, PropTypes } from 'react';
-
import ReactTooltip from 'react-tooltip';
-import { MethodDecoding } from '~/ui';
+import { Button, MethodDecoding } from '~/ui';
import * as tUtil from '../util/transaction';
import Account from '../Account';
-import styles from './TransactionMainDetails.css';
+import styles from './transactionMainDetails.css';
export default class TransactionMainDetails extends Component {
static propTypes = {
- id: PropTypes.object.isRequired,
+ children: PropTypes.node,
from: PropTypes.string.isRequired,
- fromBalance: PropTypes.object, // eth BigNumber, not required since it might take time to fetch
- value: PropTypes.object.isRequired, // wei hex
- totalValue: PropTypes.object.isRequired, // wei BigNumber
+ fromBalance: PropTypes.object,
+ gasStore: PropTypes.object,
+ id: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired,
+ totalValue: PropTypes.object.isRequired,
transaction: PropTypes.object.isRequired,
- children: PropTypes.node
+ value: PropTypes.object.isRequired
};
componentWillMount () {
- const { value, totalValue } = this.props;
+ const { totalValue, value } = this.props;
this.updateDisplayValues(value, totalValue);
}
componentWillReceiveProps (nextProps) {
- const { value, totalValue } = nextProps;
+ const { totalValue, value } = nextProps;
this.updateDisplayValues(value, totalValue);
}
- updateDisplayValues (value, totalValue) {
- this.setState({
- feeEth: tUtil.calcFeeInEth(totalValue, value),
- valueDisplay: tUtil.getValueDisplay(value),
- valueDisplayWei: tUtil.getValueDisplayWei(value),
- totalValueDisplay: tUtil.getTotalValueDisplay(totalValue),
- totalValueDisplayWei: tUtil.getTotalValueDisplayWei(totalValue)
- });
- }
-
render () {
- const { children, from, fromBalance, transaction, isTest } = this.props;
+ const { children, from, fromBalance, gasStore, isTest, transaction } = this.props;
return (