diff --git a/js/src/api/rpc/signer/signer.js b/js/src/api/rpc/signer/signer.js index 126ce651a..a24d8b919 100644 --- a/js/src/api/rpc/signer/signer.js +++ b/js/src/api/rpc/signer/signer.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inNumber16, inData } from '../../format/input'; +import { inData, inNumber16, inOptions } from '../../format/input'; import { outSignerRequest } from '../../format/output'; export default class Signer { @@ -24,7 +24,7 @@ export default class Signer { confirmRequest (requestId, options, password) { return this._transport - .execute('signer_confirmRequest', inNumber16(requestId), options, password); + .execute('signer_confirmRequest', inNumber16(requestId), inOptions(options), password); } confirmRequestRaw (requestId, data) { diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index c3ac96490..cdd816ee3 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -66,7 +66,7 @@ class ExecuteContract extends Component { onFromAddressChange: PropTypes.func.isRequired } - gasStore = new GasPriceEditor.Store(this.context.api, this.props.gasLimit); + gasStore = new GasPriceEditor.Store(this.context.api, { gasLimit: this.props.gasLimit }); state = { amount: '0', diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index e08d7203d..412cac60c 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -112,7 +112,7 @@ export default class TransferStore { this.isWallet = account && account.wallet; this.newError = newError; - this.gasStore = new GasPriceStore(api, gasLimit); + this.gasStore = new GasPriceStore(api, { gasLimit }); if (this.isWallet) { this.wallet = props.wallet; diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index 57dc569f2..1ecc60edf 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -80,8 +80,9 @@ class Transfer extends Component {
+ address={ account.address } + center + inline />
@@ -165,18 +166,18 @@ class Transfer extends Component { balance={ balance } extras={ extras } images={ images } - senders={ senders } + onChange={ this.store.onUpdateDetails } recipient={ recipient } recipientError={ recipientError } sender={ sender } senderError={ senderError } + senders={ senders } sendersBalances={ sendersBalances } tag={ tag } total={ total } totalError={ totalError } value={ value } valueError={ valueError } - onChange={ this.store.onUpdateDetails } wallet={ account.wallet && this.props.wallet } /> ); diff --git a/js/src/redux/providers/signerMiddleware.js b/js/src/redux/providers/signerMiddleware.js index 1a6cbb129..c0c5d7613 100644 --- a/js/src/redux/providers/signerMiddleware.js +++ b/js/src/redux/providers/signerMiddleware.js @@ -52,9 +52,9 @@ export default class SignerMiddleware { } onConfirmStart = (store, action) => { - const { id, password, wallet, payload } = action.payload; + const { gas, gasPrice, id, password, payload, wallet } = action.payload; - const handlePromise = promise => { + const handlePromise = (promise) => { promise .then((txHash) => { console.log('confirmRequest', id, txHash); @@ -102,7 +102,7 @@ export default class SignerMiddleware { return; } - handlePromise(this._api.signer.confirmRequest(id, {}, password)); + handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice }, password)); } onRejectStart = (store, action) => { diff --git a/js/src/ui/Editor/mode-solidity.js b/js/src/ui/Editor/mode-solidity.js index f974c9e0f..0d1b95c67 100644 --- a/js/src/ui/Editor/mode-solidity.js +++ b/js/src/ui/Editor/mode-solidity.js @@ -22,6 +22,12 @@ /* eslint-disable */ var ace = window.ace; +if (process.env.NODE_ENV === 'test') { + if (!ace.define) { + ace.define = () => {}; + } +} + ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(acequire, exports, module) { "use strict"; diff --git a/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.js b/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.js deleted file mode 100644 index 893a50188..000000000 --- a/js/src/ui/GasPriceEditor/GasPriceSelector/gasPriceSelector.js +++ /dev/null @@ -1,556 +0,0 @@ -// 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 { - Bar, BarChart, - Rectangle, - Scatter, ScatterChart, - Tooltip, - XAxis, YAxis, - Dot, - ResponsiveContainer -} from 'recharts'; - -import Slider from 'material-ui/Slider'; -import BigNumber from 'bignumber.js'; - -import styles from './gasPriceSelector.css'; - -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; -}; - -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.array, - 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 }); - } -} - -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 ( - - - - - - - ); - } -} - -class CustomizedShape 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 ( - - - - - ); - } -} - -class CustomTooltip extends Component { - static propTypes = { - gasPriceHistogram: PropTypes.object.isRequired, - type: PropTypes.string, - payload: PropTypes.array, - label: PropTypes.number, - active: PropTypes.bool - } - - render () { - const { active, label, gasPriceHistogram } = this.props; - - if (!active) { - return null; - } - - const index = label; - - const count = gasPriceHistogram.counts[index]; - const minGasPrice = gasPriceHistogram.bucketBounds[index]; - const maxGasPrice = gasPriceHistogram.bucketBounds[index + 1]; - - return ( -
-

- { count.toNumber() } transactions - with gas price set from - { minGasPrice.toFormat(0) } - to - { maxGasPrice.toFormat(0) } -

-
- ); - } -} - -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 = { - gasPriceHistogram: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - - gasPrice: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object - ]) - } - - state = { - gasPrice: null, - sliderValue: 0.5, - selectedIndex: 0, - - chartData: { - values: [], - xDomain: [], - yDomain: [], - N: 0 - } - } - - componentWillMount () { - this.computeCharts(); - this.setGasPrice(); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.gasPrice !== this.props.gasPrice) { - this.setGasPrice(nextProps); - } - } - - componentWillUpdate (nextProps, nextState) { - if (Math.floor(nextState.sliderValue) !== Math.floor(this.state.sliderValue)) { - this.updateSelectedBarChart(nextState); - } - } - - render () { - return ( -
- { this.renderChart() } - { this.renderSlider() } -
- ); - } - - renderSlider () { - const { sliderValue } = this.state; - - return ( -
- -
- ); - } - - renderChart () { - const { gasPriceHistogram } = this.props; - const { chartData, sliderValue, selectedIndex } = this.state; - - if (chartData.values.length === 0) { - return null; - } - - const height = 300; - const countIndex = Math.max(0, Math.min(selectedIndex, gasPriceHistogram.counts.length - 1)); - const selectedCount = countModifier(gasPriceHistogram.counts[countIndex]); - - return ( -
-
-
- - - } - line - isAnimationActive={ false } - /> - - - - - -
- -
- - - } - /> - - } - /> - - - - - -
-
-
- ); - } - - renderCustomCursor = () => { - const { gasPriceHistogram } = this.props; - const { chartData } = this.state; - - return ( - - ); - } - - getBarHoverIndex = () => { - const { barChart } = this.refs; - - if (!barChart || !barChart.state) { - return -1; - } - - return barChart.state.activeTooltipIndex; - } - - computeChartsData () { - const { gasPriceChartData } = this.state; - const { gasPriceHistogram } = this.props; - - const values = gasPriceChartData - .map((value, index) => ({ value, index })); - - const N = values.length - 1; - const maxGasCounts = countModifier( - gasPriceHistogram - .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 { gasPriceHistogram } = props; - - const gasPriceChartData = gasPriceHistogram - .counts - .map(count => countModifier(count)); - - this.setState( - { gasPriceChartData }, - () => this.computeChartsData() - ); - } - - updateSelectedBarChart (state = this.state) { - } - - setGasPrice (props = this.props) { - const { gasPrice, gasPriceHistogram } = props; - - // If no gas price yet... - if (!gasPrice) { - return this.setSliderValue(0.5); - } - - const bnGasPrice = (typeof gasPrice === 'string') - ? new BigNumber(gasPrice) - : gasPrice; - - // If gas price hasn't changed - if (this.state.gasPrice && bnGasPrice.equals(this.state.gasPrice)) { - return; - } - - const gasPrices = gasPriceHistogram.bucketBounds; - const startIndex = gasPrices - .findIndex(price => price.greaterThan(bnGasPrice)) - 1; - - // gasPrice Lower than the max in histogram - if (startIndex === -1) { - return this.setSliderValue(0, bnGasPrice); - } - - // gasPrice Greater than the max in histogram - if (startIndex === -2) { - return this.setSliderValue(1, bnGasPrice); - } - - const priceA = gasPrices[startIndex]; - const priceB = gasPrices[startIndex + 1]; - - const sliderValueDec = bnGasPrice - .minus(priceA) - .dividedBy(priceB.minus(priceA)) - .toNumber(); - - const sliderValue = (startIndex + sliderValueDec) / (gasPrices.length - 1); - this.setSliderValue(sliderValue, bnGasPrice); - } - - setSliderValue (value, gasPrice = this.state.gasPrice) { - const { gasPriceHistogram } = this.props; - - const N = gasPriceHistogram.bucketBounds.length - 1; - - const sliderValue = Math.max(0, Math.min(value, 1)); - const selectedIndex = Math.floor(sliderValue * N); - - this.setState({ sliderValue, gasPrice, selectedIndex }); - } - - onBarChartMouseUp = (event) => { - console.log(event); - } - - onClickGasPrice = (bar) => { - const { index } = bar; - - const ratio = (index + 0.5) / (this.state.chartData.N + 1); - - this.onEditGasPriceSlider(null, ratio); - } - - onEditGasPriceSlider = (event, sliderValue) => { - const { gasPriceHistogram } = this.props; - - const gasPrices = gasPriceHistogram.bucketBounds; - const N = gasPrices.length - 1; - const gasPriceAIdx = Math.floor(sliderValue * N); - const gasPriceBIdx = gasPriceAIdx + 1; - - if (gasPriceBIdx === N + 1) { - const gasPrice = gasPrices[gasPriceAIdx].round(); - this.props.onChange(event, gasPrice); - return; - } - - const gasPriceA = gasPrices[gasPriceAIdx]; - const gasPriceB = gasPrices[gasPriceBIdx]; - - const mult = Math.round((sliderValue % 1) * 100) / 100; - const gasPrice = gasPriceA - .plus(gasPriceB.minus(gasPriceA).times(mult)) - .round(); - - this.setSliderValue(sliderValue, gasPrice); - this.props.onChange(event, gasPrice); - } -} diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.css b/js/src/ui/GasPriceEditor/gasPriceEditor.css index cf1fff81c..bcb99a63b 100644 --- a/js/src/ui/GasPriceEditor/gasPriceEditor.css +++ b/js/src/ui/GasPriceEditor/gasPriceEditor.css @@ -15,16 +15,12 @@ /* along with Parity. If not, see . */ -.columns { +.container { display: flex; flex-wrap: wrap; position: relative; } -.graphColumn { - flex: 65; -} - .editColumn { flex: 35; padding-left: 1em; @@ -41,6 +37,10 @@ opacity: 0.5; } +.graphColumn { + flex: 65; +} + .row { display: flex; flex-wrap: wrap; diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.js b/js/src/ui/GasPriceEditor/gasPriceEditor.js index 8c94dfca7..423afc45b 100644 --- a/js/src/ui/GasPriceEditor/gasPriceEditor.js +++ b/js/src/ui/GasPriceEditor/gasPriceEditor.js @@ -15,11 +15,11 @@ // along with Parity. If not, see . import BigNumber from 'bignumber.js'; -import React, { Component, PropTypes } from 'react'; import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; import Input from '../Form/Input'; -import GasPriceSelector from './GasPriceSelector'; +import GasPriceSelector from '../GasPriceSelector'; import Store from './store'; import styles from './gasPriceEditor.css'; @@ -31,62 +31,60 @@ export default class GasPriceEditor extends Component { }; static propTypes = { - store: PropTypes.object.isRequired, - onChange: PropTypes.func + children: PropTypes.node, + onChange: PropTypes.func, + store: PropTypes.object.isRequired } static Store = Store; render () { const { api } = this.context; - const { store } = this.props; - const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice, errorTotal, totalValue } = store; + const { children, store } = this.props; + const { errorGas, errorPrice, errorTotal, estimated, gas, histogram, price, priceDefault, totalValue } = store; const eth = api.util.fromWei(totalValue).toFormat(); const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`; const priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`; return ( -
+
+ histogram={ histogram } + onChange={ this.onEditGasPrice } + price={ price } />
- 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 ( +
+ { this.renderChart() } + { this.renderSlider() } +
+ ); + } + + renderChart () { + const { histogram } = this.props; + const { chartData, sliderValue, selectedIndex } = this.state; + + if (chartData.values.length === 0) { + return null; + } + + const height = 196; + const countIndex = Math.max(0, Math.min(selectedIndex, histogram.counts.length - 1)); + const selectedCount = countModifier(histogram.counts[countIndex]); + + return ( +
+
+ +
+ + + } /> + + + + +
+ +
+ + + }stroke={ COLORS.line } /> + } + cursor={ this.renderCustomCursor() } + wrapperStyle={ TOOL_STYLE } /> + + + + +
+
+
+ ); + } + + renderSlider () { + const { sliderValue } = this.state; + + 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 (
@@ -74,29 +65,74 @@ export default class TransactionMainDetails extends Component {
+ historic={ false } + transaction={ + gasStore + ? gasStore.overrideTransaction(transaction) + : transaction + } /> + { this.renderEditGas() }
{ children }
); } + renderEditGas () { + const { gasStore } = this.props; + + if (!gasStore) { + return null; + } + + return ( +
+
+ ); + } + + renderTotalValue () { + const { id } = this.props; + const { feeEth, totalValueDisplay, totalValueDisplayWei } = this.state; + const labelId = `totalValue${id}`; + + return ( +
+
+ { totalValueDisplay } ETH +
+ + The value of the transaction including the mining fee is { totalValueDisplayWei } WEI.
+ (This includes a mining fee of { feeEth } ETH) +
+
+ ); + } + renderValue () { const { id } = this.props; const { valueDisplay, valueDisplayWei } = this.state; + const labelId = `value${id}`; return (
+ data-for={ labelId } + data-tip> { valueDisplay } ETH
- + The value of the transaction.
{ valueDisplayWei } WEI
@@ -104,25 +140,17 @@ export default class TransactionMainDetails extends Component { ); } - renderTotalValue () { - const { id } = this.props; - const { totalValueDisplay, totalValueDisplayWei, feeEth } = this.state; + updateDisplayValues (value, totalValue) { + this.setState({ + feeEth: tUtil.calcFeeInEth(totalValue, value), + totalValueDisplay: tUtil.getTotalValueDisplay(totalValue), + totalValueDisplayWei: tUtil.getTotalValueDisplayWei(totalValue), + valueDisplay: tUtil.getValueDisplay(value), + valueDisplayWei: tUtil.getValueDisplayWei(value) + }); + } - return ( -
-
- { totalValueDisplay } ETH -
- - The value of the transaction including the mining fee is { totalValueDisplayWei } WEI.
- (This includes a mining fee of { feeEth } ETH) -
-
- ); + toggleGasEditor = () => { + this.props.gasStore.setEditing(true); } } diff --git a/js/src/views/Signer/components/TransactionPending/index.js b/js/src/views/Signer/components/TransactionPending/index.js index 1916d0571..3581c448c 100644 --- a/js/src/views/Signer/components/TransactionPending/index.js +++ b/js/src/views/Signer/components/TransactionPending/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 './TransactionPending'; +export default from './transactionPending'; diff --git a/js/src/views/Signer/components/TransactionPending/TransactionPending.css b/js/src/views/Signer/components/TransactionPending/transactionPending.css similarity index 92% rename from js/src/views/Signer/components/TransactionPending/TransactionPending.css rename to js/src/views/Signer/components/TransactionPending/transactionPending.css index 877066e57..dbda27135 100644 --- a/js/src/views/Signer/components/TransactionPending/TransactionPending.css +++ b/js/src/views/Signer/components/TransactionPending/transactionPending.css @@ -19,13 +19,9 @@ .container { display: flex; - padding: 1em 0 1em; + padding: 1.5em 1em 1.5em 0; & > * { vertical-align: middle; } } - -.container+.container { - padding-top: 2em; -} diff --git a/js/src/views/Signer/components/TransactionPending/TransactionPending.js b/js/src/views/Signer/components/TransactionPending/transactionPending.js similarity index 57% rename from js/src/views/Signer/components/TransactionPending/TransactionPending.js rename to js/src/views/Signer/components/TransactionPending/transactionPending.js index ba3c30802..eea9491ee 100644 --- a/js/src/views/Signer/components/TransactionPending/TransactionPending.js +++ b/js/src/views/Signer/components/TransactionPending/transactionPending.js @@ -17,89 +17,130 @@ import React, { Component, PropTypes } from 'react'; import { observer } from 'mobx-react'; +import { Button, GasPriceEditor } from '~/ui'; + import TransactionMainDetails from '../TransactionMainDetails'; import TransactionPendingForm from '../TransactionPendingForm'; -import styles from './TransactionPending.css'; +import styles from './transactionPending.css'; import * as tUtil from '../util/transaction'; @observer export default class TransactionPending extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + static propTypes = { - id: PropTypes.object.isRequired, - transaction: PropTypes.shape({ - from: PropTypes.string.isRequired, - value: PropTypes.object.isRequired, // wei hex - gasPrice: PropTypes.object.isRequired, // wei hex - gas: PropTypes.object.isRequired, // hex - data: PropTypes.string, // hex - to: PropTypes.string // undefined if it's a contract - }).isRequired, + className: PropTypes.string, date: PropTypes.instanceOf(Date).isRequired, + gasLimit: PropTypes.object, + id: PropTypes.object.isRequired, + isSending: PropTypes.bool.isRequired, + isTest: PropTypes.bool.isRequired, nonce: PropTypes.number, onConfirm: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, - isSending: PropTypes.bool.isRequired, - className: PropTypes.string, - isTest: PropTypes.bool.isRequired, - store: PropTypes.object.isRequired + store: PropTypes.object.isRequired, + transaction: PropTypes.shape({ + data: PropTypes.string, + from: PropTypes.string.isRequired, + gas: PropTypes.object.isRequired, + gasPrice: PropTypes.object.isRequired, + to: PropTypes.string, + value: PropTypes.object.isRequired + }).isRequired }; - static defaultProps = { - isSending: false - }; + gasStore = new GasPriceEditor.Store(this.context.api, { + gas: this.props.transaction.gas.toFixed(), + gasLimit: this.props.gasLimit, + gasPrice: this.props.transaction.gasPrice.toFixed() + }); componentWillMount () { - const { transaction, store } = this.props; - const { gas, gasPrice, value, from, to } = transaction; + const { store, transaction } = this.props; + const { from, gas, gasPrice, to, value } = transaction; const fee = tUtil.getFee(gas, gasPrice); // BigNumber object - const totalValue = tUtil.getTotalValue(fee, value); const gasPriceEthmDisplay = tUtil.getEthmFromWeiDisplay(gasPrice); const gasToDisplay = tUtil.getGasDisplay(gas); + const totalValue = tUtil.getTotalValue(fee, value); this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay }); + this.gasStore.setEthValue(value); store.fetchBalances([from, to]); } render () { - const { className, id, transaction, store, isTest } = this.props; - const { from, value } = transaction; + return this.gasStore.isEditing + ? this.renderGasEditor() + : this.renderTransaction(); + } + + renderTransaction () { + const { className, id, isSending, isTest, store, transaction } = this.props; const { totalValue } = this.state; + const { from, value } = transaction; const fromBalance = store.balances[from]; return ( -
+
+ value={ value } /> + onReject={ this.onReject } />
); } - onConfirm = data => { - const { id, transaction } = this.props; - const { gasPrice } = transaction; - const { password, wallet } = data; + renderGasEditor () { + const { className } = this.props; - this.props.onConfirm({ id, password, wallet, gasPrice }); + return ( +
+ +
+ ); + } + + onConfirm = (data) => { + const { id, transaction } = this.props; + const { password, wallet } = data; + const { gas, gasPrice } = this.gasStore.overrideTransaction(transaction); + + this.props.onConfirm({ + gas, + gasPrice, + id, + password, + wallet + }); } onReject = () => { this.props.onReject(this.props.id); } + + toggleGasEditor = () => { + this.gasStore.setEditing(false); + } } diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/index.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/index.js index ad0f9fd1f..254fb5971 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/index.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/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 './TransactionPendingFormConfirm'; +export default from './transactionPendingFormConfirm'; diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.css similarity index 100% rename from js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.css rename to js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.css diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js similarity index 71% rename from js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js rename to js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js index 7d2d660df..f343c1f31 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/TransactionPendingFormConfirm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js @@ -22,7 +22,7 @@ import ReactTooltip from 'react-tooltip'; import { Form, Input, IdentityIcon } from '~/ui'; -import styles from './TransactionPendingFormConfirm.css'; +import styles from './transactionPendingFormConfirm.css'; class TransactionPendingFormConfirm extends Component { static propTypes = { @@ -35,14 +35,14 @@ class TransactionPendingFormConfirm extends Component { id = Math.random(); // for tooltip state = { - walletError: null, + password: '', wallet: null, - password: '' + walletError: null } render () { const { accounts, address, isSending } = this.props; - const { password, walletError, wallet } = this.state; + const { password, wallet, walletError } = this.state; const account = accounts[address] || {}; const isExternal = !account.uuid; @@ -51,37 +51,54 @@ class TransactionPendingFormConfirm extends Component { : null; const isWalletOk = !isExternal || (walletError === null && wallet !== null); - const keyInput = isExternal ? this.renderKeyInput() : null; + const keyInput = isExternal + ? this.renderKeyInput() + : null; return (
{ keyInput }
{ passwordHint }
+ data-for={ `transactionConfirmForm${this.id}` } + data-place='bottom' + data-tip> } - label={ isSending ? 'Confirming...' : 'Confirm Transaction' } - /> + fullWidth + icon={ + + } + label={ + isSending + ? 'Confirming...' + : 'Confirm Transaction' + } + onTouchTap={ this.onConfirm } + primary />
{ this.renderTooltip() }
@@ -95,11 +112,10 @@ class TransactionPendingFormConfirm extends Component { return ( + onChange={ this.onKeySelect } + type='file' /> ); } @@ -109,34 +125,37 @@ class TransactionPendingFormConfirm extends Component { } return ( - + Please provide a password for this account ); } - onKeySelect = evt => { + onKeySelect = (event) => { const fileReader = new FileReader(); - fileReader.onload = e => { + + fileReader.onload = (e) => { try { const wallet = JSON.parse(e.target.result); + this.setState({ - walletError: null, - wallet: wallet + wallet, + walletError: null }); - } catch (e) { + } catch (error) { this.setState({ - walletError: 'Given wallet file is invalid.', - wallet: null + wallet: null, + walletError: 'Given wallet file is invalid.' }); } }; - fileReader.readAsText(evt.target.files[0]); + fileReader.readAsText(event.target.files[0]); } - onModifyPassword = evt => { - const password = evt.target.value; + onModifyPassword = (event) => { + const password = event.target.value; + this.setState({ password }); @@ -150,8 +169,8 @@ class TransactionPendingFormConfirm extends Component { }); } - onKeyDown = evt => { - if (evt.which !== 13) { + onKeyDown = (event) => { + if (event.which !== 13) { return; } diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/index.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/index.js index 3ac772104..f8c37266f 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/index.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/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 './TransactionPendingFormReject'; +export default from './transactionPendingFormReject'; diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/TransactionPendingFormReject.css b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.css similarity index 100% rename from js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/TransactionPendingFormReject.css rename to js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.css diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/TransactionPendingFormReject.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.js similarity index 96% rename from js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/TransactionPendingFormReject.js rename to js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.js index af06e79b4..890dd3750 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/TransactionPendingFormReject.js +++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormReject/transactionPendingFormReject.js @@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; import RaisedButton from 'material-ui/RaisedButton'; -import styles from './TransactionPendingFormReject.css'; +import styles from './transactionPendingFormReject.css'; export default class TransactionPendingFormReject extends Component { diff --git a/js/src/views/Signer/components/TransactionPendingForm/index.js b/js/src/views/Signer/components/TransactionPendingForm/index.js index 2fe1e9037..dfdcb41ce 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/index.js +++ b/js/src/views/Signer/components/TransactionPendingForm/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 './TransactionPendingForm'; +export default from './transactionPendingForm'; diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingForm.css b/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.css similarity index 100% rename from js/src/views/Signer/components/TransactionPendingForm/TransactionPendingForm.css rename to js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.css diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingForm.js b/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js similarity index 88% rename from js/src/views/Signer/components/TransactionPendingForm/TransactionPendingForm.js rename to js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js index 6f46cf571..41d50f68e 100644 --- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingForm.js +++ b/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js @@ -20,10 +20,9 @@ import BackIcon from 'material-ui/svg-icons/navigation/arrow-back'; import TransactionPendingFormConfirm from './TransactionPendingFormConfirm'; import TransactionPendingFormReject from './TransactionPendingFormReject'; -import styles from './TransactionPendingForm.css'; +import styles from './transactionPendingForm.css'; export default class TransactionPendingForm extends Component { - static propTypes = { address: PropTypes.string.isRequired, isSending: PropTypes.bool.isRequired, @@ -60,8 +59,8 @@ export default class TransactionPendingForm extends Component { return ( + isSending={ isSending } + onConfirm={ onConfirm } /> ); } @@ -72,14 +71,13 @@ export default class TransactionPendingForm extends Component { if (!isRejectOpen) { html = reject transaction; } else { - html = I've changed my mind; + html = { "I've changed my mind" }; } return ( + onClick={ this.onToggleReject }> { html } ); @@ -87,7 +85,9 @@ export default class TransactionPendingForm extends Component { onToggleReject = () => { const { isRejectOpen } = this.state; - this.setState({ isRejectOpen: !isRejectOpen }); - } + this.setState({ + isRejectOpen: !isRejectOpen + }); + } } diff --git a/js/src/views/Signer/components/TxHashLink/index.js b/js/src/views/Signer/components/TxHashLink/index.js index ef7199290..f831c9a61 100644 --- a/js/src/views/Signer/components/TxHashLink/index.js +++ b/js/src/views/Signer/components/TxHashLink/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 './TxHashLink'; +export default from './txHashLink'; diff --git a/js/src/views/Signer/components/TxHashLink/TxHashLink.js b/js/src/views/Signer/components/TxHashLink/txHashLink.js similarity index 84% rename from js/src/views/Signer/components/TxHashLink/TxHashLink.js rename to js/src/views/Signer/components/TxHashLink/txHashLink.js index bce30eded..cea3fe602 100644 --- a/js/src/views/Signer/components/TxHashLink/TxHashLink.js +++ b/js/src/views/Signer/components/TxHashLink/txHashLink.js @@ -19,22 +19,21 @@ import React, { Component, PropTypes } from 'react'; import { txLink } from '~/3rdparty/etherscan/links'; export default class TxHashLink extends Component { - static propTypes = { - txHash: PropTypes.string.isRequired, - isTest: PropTypes.bool.isRequired, children: PropTypes.node, - className: PropTypes.string + className: PropTypes.string, + isTest: PropTypes.bool.isRequired, + txHash: PropTypes.string.isRequired } render () { - const { children, txHash, className, isTest } = this.props; + const { children, className, isTest, txHash } = this.props; return ( + target='_blank'> { children || txHash } ); diff --git a/js/src/views/Signer/containers/Embedded/embedded.css b/js/src/views/Signer/containers/Embedded/embedded.css index ffc6a209f..fcd49a851 100644 --- a/js/src/views/Signer/containers/Embedded/embedded.css +++ b/js/src/views/Signer/containers/Embedded/embedded.css @@ -17,16 +17,22 @@ @import '../../_layout.css'; -.signer { - box-sizing: border-box; - padding: 0; - width: $embedWidth; +.info { + padding: 1em 0; } .none { color: #aaa; } -.info { - padding: 1em 0; +.request { + &:nth-child(even) { + background: rgba(255, 255, 255, 0.04); + } +} + +.signer { + box-sizing: border-box; + padding: 0; + width: $embedWidth; } diff --git a/js/src/views/Signer/containers/Embedded/embedded.js b/js/src/views/Signer/containers/Embedded/embedded.js index 4629a6522..51452520b 100644 --- a/js/src/views/Signer/containers/Embedded/embedded.js +++ b/js/src/views/Signer/containers/Embedded/embedded.js @@ -33,15 +33,16 @@ class Embedded extends Component { }; static propTypes = { - signer: PropTypes.shape({ - pending: PropTypes.array.isRequired, - finished: PropTypes.array.isRequired - }).isRequired, actions: PropTypes.shape({ startConfirmRequest: PropTypes.func.isRequired, startRejectRequest: PropTypes.func.isRequired }).isRequired, - isTest: PropTypes.bool.isRequired + gasLimit: PropTypes.object.isRequired, + isTest: PropTypes.bool.isRequired, + signer: PropTypes.shape({ + finished: PropTypes.array.isRequired, + pending: PropTypes.array.isRequired + }).isRequired }; store = new Store(this.context.api); @@ -78,20 +79,21 @@ class Embedded extends Component { } renderPending = (data) => { - const { actions, isTest } = this.props; - const { payload, id, isSending, date } = data; + const { actions, gasLimit, isTest } = this.props; + const { date, id, isSending, payload } = data; return ( ); @@ -103,13 +105,14 @@ class Embedded extends Component { } function mapStateToProps (state) { - const { isTest } = state.nodeStatus; + const { gasLimit, isTest } = state.nodeStatus; const { actions, signer } = state; return { actions, - signer, - isTest + gasLimit, + isTest, + signer }; } diff --git a/js/src/views/Signer/containers/RequestsPage/index.js b/js/src/views/Signer/containers/RequestsPage/index.js index cb3fdd143..ee3b54f54 100644 --- a/js/src/views/Signer/containers/RequestsPage/index.js +++ b/js/src/views/Signer/containers/RequestsPage/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 './RequestsPage'; +export default from './requestsPage'; diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.css b/js/src/views/Signer/containers/RequestsPage/requestsPage.css similarity index 90% rename from js/src/views/Signer/containers/RequestsPage/RequestsPage.css rename to js/src/views/Signer/containers/RequestsPage/requestsPage.css index 662c45817..f26f66e86 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.css +++ b/js/src/views/Signer/containers/RequestsPage/requestsPage.css @@ -18,3 +18,9 @@ .noRequestsMsg { color: #aaa; } + +.request { + &:nth-child(odd) { + background: rgba(255, 255, 255, 0.04); + } +} diff --git a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js b/js/src/views/Signer/containers/RequestsPage/requestsPage.js similarity index 88% rename from js/src/views/Signer/containers/RequestsPage/RequestsPage.js rename to js/src/views/Signer/containers/RequestsPage/requestsPage.js index f3cd5a267..db52b008e 100644 --- a/js/src/views/Signer/containers/RequestsPage/RequestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/requestsPage.js @@ -26,7 +26,7 @@ import { Container, Page, TxList } from '~/ui'; import RequestPending from '../../components/RequestPending'; -import styles from './RequestsPage.css'; +import styles from './requestsPage.css'; @observer class RequestsPage extends Component { @@ -35,15 +35,16 @@ class RequestsPage extends Component { }; static propTypes = { - signer: PropTypes.shape({ - pending: PropTypes.array.isRequired, - finished: PropTypes.array.isRequired - }).isRequired, actions: PropTypes.shape({ startConfirmRequest: PropTypes.func.isRequired, startRejectRequest: PropTypes.func.isRequired }).isRequired, - isTest: PropTypes.bool.isRequired + gasLimit: PropTypes.object.isRequired, + isTest: PropTypes.bool.isRequired, + signer: PropTypes.shape({ + pending: PropTypes.array.isRequired, + finished: PropTypes.array.isRequired + }).isRequired }; store = new Store(this.context.api, true); @@ -104,19 +105,21 @@ class RequestsPage extends Component { } renderPending = (data) => { - const { actions, isTest } = this.props; - const { payload, id, isSending, date } = data; + const { actions, gasLimit, isTest } = this.props; + const { date, id, isSending, payload } = data; return ( ); @@ -124,13 +127,14 @@ class RequestsPage extends Component { } function mapStateToProps (state) { - const { isTest } = state.nodeStatus; + const { gasLimit, isTest } = state.nodeStatus; const { actions, signer } = state; return { actions, - signer, - isTest + gasLimit, + isTest, + signer }; }