Merge branch 'master' into ng-enhanced-multisig

This commit is contained in:
Nicolas Gotchac 2016-12-09 16:40:07 +01:00
commit bace05b154
70 changed files with 554 additions and 708 deletions

2
Cargo.lock generated
View File

@ -1271,7 +1271,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#a59b62ecec8773715d1db7e070bbbe5443eb7378" source = "git+https://github.com/ethcore/js-precompiled.git#8e8e515f958d2d4a5abec07253a51a052f2b744d"
dependencies = [ dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -7,6 +7,7 @@
"transform-runtime", "transform-runtime",
"transform-decorators-legacy", "transform-decorators-legacy",
"transform-class-properties", "transform-class-properties",
"transform-object-rest-spread",
"lodash" "lodash"
], ],
"retainLines": true, "retainLines": true,

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "0.2.99", "version": "0.2.103",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
@ -48,25 +48,26 @@
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "6.18.0", "babel-cli": "6.18.0",
"babel-core": "6.18.2", "babel-core": "6.20.0",
"babel-eslint": "7.1.1", "babel-eslint": "7.1.1",
"babel-loader": "6.2.8", "babel-loader": "6.2.8",
"babel-plugin-lodash": "3.2.10", "babel-plugin-lodash": "3.2.10",
"babel-plugin-transform-class-properties": "6.19.0", "babel-plugin-transform-class-properties": "6.18.0",
"babel-plugin-transform-decorators-legacy": "1.3.4", "babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-object-rest-spread": "6.20.2",
"babel-plugin-transform-react-remove-prop-types": "0.2.11", "babel-plugin-transform-react-remove-prop-types": "0.2.11",
"babel-plugin-transform-runtime": "6.15.0", "babel-plugin-transform-runtime": "6.15.0",
"babel-polyfill": "6.16.0", "babel-polyfill": "6.20.0",
"babel-preset-es2015": "6.18.0", "babel-preset-es2015": "6.18.0",
"babel-preset-es2015-rollup": "1.2.0",
"babel-preset-es2016": "6.16.0", "babel-preset-es2016": "6.16.0",
"babel-preset-es2017": "6.16.0", "babel-preset-es2017": "6.16.0",
"babel-preset-react": "6.16.0", "babel-preset-react": "6.16.0",
"babel-preset-stage-0": "6.16.0", "babel-preset-stage-0": "6.16.0",
"babel-register": "6.18.0", "babel-register": "6.18.0",
"babel-runtime": "6.18.0", "babel-runtime": "6.20.0",
"chai": "3.5.0", "chai": "3.5.0",
"chai-enzyme": "0.6.1", "chai-enzyme": "0.6.1",
"circular-dependency-plugin": "2.0.0",
"copy-webpack-plugin": "4.0.1", "copy-webpack-plugin": "4.0.1",
"core-js": "2.4.1", "core-js": "2.4.1",
"coveralls": "2.11.15", "coveralls": "2.11.15",

View File

@ -1 +1 @@
// test script 4 // test script 6

View File

@ -14,10 +14,14 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export const url = (isTestnet = false) => {
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io`;
};
export const txLink = (hash, isTestnet = false) => { export const txLink = (hash, isTestnet = false) => {
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/tx/${hash}`; return `${url(isTestnet)}/tx/${hash}`;
}; };
export const addressLink = (address, isTestnet = false) => { export const addressLink = (address, isTestnet = false) => {
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/address/${address}`; return `${url(isTestnet)}/address/${address}`;
}; };

View File

@ -262,12 +262,11 @@ export default class Contract {
} }
const options = this._getFilterOptions(event, _options); const options = this._getFilterOptions(event, _options);
options.fromBlock = 0;
options.toBlock = 'latest';
return this._api.eth return this._api.eth
.getLogs({ .getLogs(options)
fromBlock: 0,
toBlock: 'latest',
...options
})
.then((logs) => this.parseEventLogs(logs)); .then((logs) => this.parseEventLogs(logs));
} }

View File

@ -1,12 +0,0 @@
import babel from 'rollup-plugin-babel';
export default {
entry: 'src/index.js',
dest: 'release/index.js',
format: 'cjs',
plugins: [babel({
babelrc: false,
presets: ['es2015-rollup', 'stage-0'],
runtimeHelpers: true
})]
};

View File

@ -19,7 +19,7 @@ import ContentAdd from 'material-ui/svg-icons/content/add';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import { Button, Modal, Form, Input, InputAddress } from '~/ui'; import { Button, Modal, Form, Input, InputAddress } from '~/ui';
import { ERRORS, validateAddress, validateName } from '../../util/validation'; import { ERRORS, validateAddress, validateName } from '~/util/validation';
export default class AddAddress extends Component { export default class AddAddress extends Component {
static contextTypes = { static contextTypes = {

View File

@ -21,7 +21,7 @@ import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forwa
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui'; import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui';
import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation';
import { eip20, wallet } from '~/contracts/abi'; import { eip20, wallet } from '~/contracts/abi';

View File

@ -16,15 +16,14 @@
import { observable, computed, action, transaction } from 'mobx'; import { observable, computed, action, transaction } from 'mobx';
import { validateUint, validateAddress, validateName } from '~/util/validation';
import { ERROR_CODES } from '~/api/transport/error';
import Contract from '~/api/contract'; import Contract from '~/api/contract';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { ERROR_CODES } from '~/api/transport/error';
import { wallet as walletAbi } from '~/contracts/abi'; import { wallet as walletAbi } from '~/contracts/abi';
import { wallet as walletCode } from '~/contracts/code'; import { wallet as walletCode } from '~/contracts/code';
import { walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet'; import { walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
import { validateUint, validateAddress, validateName } from '~/util/validation';
import WalletsUtils from '~/util/wallets'; import WalletsUtils from '~/util/wallets';
const STEPS = { const STEPS = {

View File

@ -18,8 +18,8 @@ import React, { Component, PropTypes } from 'react';
import { MenuItem } from 'material-ui'; import { MenuItem } from 'material-ui';
import { AddressSelect, Form, Input, Select } from '~/ui'; import { AddressSelect, Form, Input, Select } from '~/ui';
import { validateAbi } from '../../../util/validation'; import { validateAbi } from '~/util/validation';
import { parseAbiType } from '../../../util/abi'; import { parseAbiType } from '~/util/abi';
export default class DetailsStep extends Component { export default class DetailsStep extends Component {
static contextTypes = { static contextTypes = {

View File

@ -32,7 +32,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Form, TypedInput } from '~/ui'; import { Form, TypedInput } from '~/ui';
import { parseAbiType } from '../../../util/abi'; import { parseAbiType } from '~/util/abi';
import styles from '../deployContract.css'; import styles from '../deployContract.css';

View File

@ -19,7 +19,7 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui'; import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui';
import { ERRORS, validateAbi, validateCode, validateName } from '../../util/validation'; import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
import DetailsStep from './DetailsStep'; import DetailsStep from './DetailsStep';
import ParametersStep from './ParametersStep'; import ParametersStep from './ParametersStep';

View File

@ -19,7 +19,7 @@ import ContentClear from 'material-ui/svg-icons/content/clear';
import ContentSave from 'material-ui/svg-icons/content/save'; import ContentSave from 'material-ui/svg-icons/content/save';
import { Button, Form, Input, InputChip, Modal } from '~/ui'; import { Button, Form, Input, InputChip, Modal } from '~/ui';
import { validateName } from '../../util/validation'; import { validateName } from '~/util/validation';
export default class EditMeta extends Component { export default class EditMeta extends Component {
static contextTypes = { static contextTypes = {

View File

@ -21,8 +21,8 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui';
import { MAX_GAS_ESTIMATION } from '../../util/constants'; import { MAX_GAS_ESTIMATION } from '~/util/constants';
import { validateAddress, validateUint } from '../../util/validation'; import { validateAddress, validateUint } from '~/util/validation';
import { parseAbiType } from '~/util/abi'; import { parseAbiType } from '~/util/abi';
import DetailsStep from './DetailsStep'; import DetailsStep from './DetailsStep';

View File

@ -21,9 +21,8 @@ import { sha3 } from '~/api/util/sha3';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification';
import { postToServer } from '../../3rdparty/sms-verification'; import { postToServer } from '~/3rdparty/sms-verification';
import checkIfTxFailed from '../../util/check-if-tx-failed'; import { checkIfTxFailed, waitForConfirmations } from '~/util/tx';
import waitForConfirmations from '../../util/wait-for-block-confirmations';
export const LOADING = 'fetching-contract'; export const LOADING = 'fetching-contract';
export const QUERY_DATA = 'query-data'; export const QUERY_DATA = 'query-data';

View File

@ -20,7 +20,7 @@ import SaveIcon from 'material-ui/svg-icons/content/save';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import { Button, Modal, Editor, Form, Input } from '~/ui'; import { Button, Modal, Editor, Form, Input } from '~/ui';
import { ERRORS, validateName } from '../../util/validation'; import { ERRORS, validateName } from '~/util/validation';
import styles from './saveContract.css'; import styles from './saveContract.css';

View File

@ -19,7 +19,7 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentClear from 'material-ui/svg-icons/content/clear';
import { Button, IdentityIcon, Modal } from '~/ui'; import { Button, IdentityIcon, Modal } from '~/ui';
import initShapeshift from '../../3rdparty/shapeshift'; import initShapeshift from '~/3rdparty/shapeshift';
import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png'; import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png';
import AwaitingDepositStep from './AwaitingDepositStep'; import AwaitingDepositStep from './AwaitingDepositStep';

View File

@ -16,96 +16,35 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import Form, { Input } from '~/ui/Form'; import { GasPriceEditor, Form, Input } from '~/ui';
import GasPriceSelector from '../GasPriceSelector';
import styles from '../transfer.css';
export default class Extras extends Component { export default class Extras extends Component {
static propTypes = { static propTypes = {
isEth: PropTypes.bool, isEth: PropTypes.bool,
data: PropTypes.string, data: PropTypes.string,
dataError: PropTypes.string, dataError: PropTypes.string,
gas: PropTypes.string,
gasEst: PropTypes.string,
gasError: PropTypes.string,
gasPrice: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),
gasPriceDefault: PropTypes.string,
gasPriceError: PropTypes.string,
gasPriceHistogram: PropTypes.object,
total: PropTypes.string, total: PropTypes.string,
totalError: PropTypes.string, totalError: PropTypes.string,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired,
gasStore: PropTypes.object.isRequired
} }
render () { render () {
const { gas, gasPrice, gasError, gasEst, gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.props; const { gasStore, onChange, total, totalError } = this.props;
const gasLabel = `gas amount (estimated: ${gasEst})`;
const priceLabel = `gas price (current: ${gasPriceDefault})`;
return ( return (
<Form> <Form>
{ this.renderData() } { this.renderData() }
<GasPriceEditor
<div className={ styles.columns }> store={ gasStore }
<div style={ { flex: 65 } }> onChange={ onChange }>
<GasPriceSelector <Input
gasPriceHistogram={ gasPriceHistogram } disabled
gasPrice={ gasPrice } label='total transaction amount'
onChange={ this.onEditGasPrice } hint='the total amount of the transaction'
/> error={ totalError }
</div> value={ `${total} ETH` } />
</GasPriceEditor>
<div
className={ styles.row }
style={ {
flex: 35, paddingLeft: '1rem',
justifyContent: 'space-around',
paddingBottom: 12
} }
>
<div className={ styles.row }>
<Input
label={ gasLabel }
hint='the amount of gas to use for the transaction'
error={ gasError }
value={ gas }
onChange={ this.onEditGas } />
<Input
label={ priceLabel }
hint='the price of gas to use for the transaction'
error={ gasPriceError }
value={ (gasPrice || '').toString() }
onChange={ this.onEditGasPrice } />
</div>
<div className={ styles.row }>
<Input
disabled
label='total transaction amount'
hint='the total amount of the transaction'
error={ totalError }
value={ `${total} ETH` } />
</div>
</div>
</div>
<div>
<p className={ styles.gasPriceDesc }>
You can choose the gas price based on the
distribution of recent included transactions' 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.
</p>
</div>
</Form> </Form>
); );
} }
@ -129,14 +68,6 @@ export default class Extras extends Component {
); );
} }
onEditGas = (event) => {
this.props.onChange('gas', event.target.value);
}
onEditGasPrice = (event, value) => {
this.props.onChange('gasPrice', value);
}
onEditData = (event) => { onEditData = (event) => {
this.props.onChange('data', event.target.value); this.props.onChange('data', event.target.value);
} }

View File

@ -23,7 +23,8 @@ import { bytesToHex } from '~/api/util/format';
import Contract from '~/api/contract'; import Contract from '~/api/contract';
import ERRORS from './errors'; import ERRORS from './errors';
import { ERROR_CODES } from '~/api/transport/error'; import { ERROR_CODES } from '~/api/transport/error';
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants'; import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants';
import GasPriceStore from '~/ui/GasPriceEditor/store';
const TITLES = { const TITLES = {
transfer: 'transfer details', transfer: 'transfer details',
@ -48,14 +49,6 @@ export default class TransferStore {
@observable data = ''; @observable data = '';
@observable dataError = null; @observable dataError = null;
@observable gas = DEFAULT_GAS;
@observable gasError = null;
@observable gasEst = '0';
@observable gasLimitError = null;
@observable gasPrice = DEFAULT_GASPRICE;
@observable gasPriceError = null;
@observable recipient = ''; @observable recipient = '';
@observable recipientError = ERRORS.requireRecipient; @observable recipientError = ERRORS.requireRecipient;
@ -68,11 +61,8 @@ export default class TransferStore {
@observable value = '0.0'; @observable value = '0.0';
@observable valueError = null; @observable valueError = null;
gasPriceHistogram = {};
account = null; account = null;
balance = null; balance = null;
gasLimit = null;
onClose = null; onClose = null;
senders = null; senders = null;
@ -81,6 +71,8 @@ export default class TransferStore {
isWallet = false; isWallet = false;
wallet = null; wallet = null;
gasStore = null;
@computed get steps () { @computed get steps () {
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC); const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
@ -93,7 +85,7 @@ export default class TransferStore {
@computed get isValid () { @computed get isValid () {
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError; const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
const extrasValid = !this.gasError && !this.gasPriceError && !this.totalError; const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.totalError;
const verifyValid = !this.passwordError; const verifyValid = !this.passwordError;
switch (this.stage) { switch (this.stage) {
@ -118,11 +110,12 @@ export default class TransferStore {
const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props; const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props;
this.account = account; this.account = account;
this.balance = balance; this.balance = balance;
this.gasLimit = gasLimit;
this.onClose = onClose; this.onClose = onClose;
this.isWallet = account && account.wallet; this.isWallet = account && account.wallet;
this.newError = newError; this.newError = newError;
this.gasStore = new GasPriceStore(api, gasLimit);
if (this.isWallet) { if (this.isWallet) {
this.wallet = props.wallet; this.wallet = props.wallet;
this.walletContract = new Contract(this.api, walletAbi); this.walletContract = new Contract(this.api, walletAbi);
@ -179,26 +172,6 @@ export default class TransferStore {
} }
} }
@action getDefaults = () => {
Promise
.all([
this.api.parity.gasPriceHistogram(),
this.api.eth.gasPrice()
])
.then(([gasPriceHistogram, gasPrice]) => {
transaction(() => {
this.gasPrice = gasPrice.toString();
this.gasPriceDefault = gasPrice.toFormat();
this.gasPriceHistogram = gasPriceHistogram;
this.recalculate();
});
})
.catch((error) => {
console.warn('getDefaults', error);
});
}
@action onSend = () => { @action onSend = () => {
this.onNext(); this.onNext();
this.sending = true; this.sending = true;
@ -281,25 +254,11 @@ export default class TransferStore {
} }
@action _onUpdateGas = (gas) => { @action _onUpdateGas = (gas) => {
const gasError = this._validatePositiveNumber(gas); this.recalculate();
transaction(() => {
this.gas = gas;
this.gasError = gasError;
this.recalculate();
});
} }
@action _onUpdateGasPrice = (gasPrice) => { @action _onUpdateGasPrice = (gasPrice) => {
const gasPriceError = this._validatePositiveNumber(gasPrice); this.recalculate();
transaction(() => {
this.gasPrice = gasPrice;
this.gasPriceError = gasPriceError;
this.recalculate();
});
} }
@action _onUpdateRecipient = (recipient) => { @action _onUpdateRecipient = (recipient) => {
@ -362,7 +321,7 @@ export default class TransferStore {
@action recalculateGas = () => { @action recalculateGas = () => {
if (!this.isValid) { if (!this.isValid) {
this.gas = 0; this.gasStore.setGas('0');
return this.recalculate(); return this.recalculate();
} }
@ -370,28 +329,20 @@ export default class TransferStore {
.estimateGas() .estimateGas()
.then((gasEst) => { .then((gasEst) => {
let gas = gasEst; let gas = gasEst;
let gasLimitError = null;
if (gas.gt(DEFAULT_GAS)) { if (gas.gt(DEFAULT_GAS)) {
gas = gas.mul(1.2); gas = gas.mul(1.2);
} }
if (gas.gte(MAX_GAS_ESTIMATION)) {
gasLimitError = ERRORS.gasException;
} else if (gas.gt(this.gasLimit)) {
gasLimitError = ERRORS.gasBlockLimit;
}
transaction(() => { transaction(() => {
this.gas = gas.toFixed(0); this.gasStore.setEstimated(gasEst.toFixed(0));
this.gasEst = gasEst.toFormat(); this.gasStore.setGas(gas.toFixed(0));
this.gasLimitError = gasLimitError;
this.recalculate(); this.recalculate();
}); });
}) })
.catch((error) => { .catch((error) => {
console.error('etimateGas', error); console.warn('etimateGas', error);
this.recalculate(); this.recalculate();
}); });
} }
@ -411,9 +362,9 @@ export default class TransferStore {
return; return;
} }
const { gas, gasPrice, tag, valueAll, isEth, isWallet } = this; const { tag, valueAll, isEth, isWallet } = this;
const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0)); const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0));
const availableEth = new BigNumber(balance.tokens[0].value); const availableEth = new BigNumber(balance.tokens[0].value);
@ -453,7 +404,7 @@ export default class TransferStore {
} }
transaction(() => { transaction(() => {
this.total = this.api.util.fromWei(totalEth).toString(); this.total = this.api.util.fromWei(totalEth).toFixed();
this.totalError = totalError; this.totalError = totalError;
this.value = value; this.value = value;
this.valueError = valueError; this.valueError = valueError;
@ -522,8 +473,8 @@ export default class TransferStore {
}; };
if (!gas) { if (!gas) {
options.gas = this.gas; options.gas = this.gasStore.gas;
options.gasPrice = this.gasPrice; options.gasPrice = this.gasStore.price;
} else { } else {
options.gas = MAX_GAS_ESTIMATION; options.gas = MAX_GAS_ESTIMATION;
} }

View File

@ -144,15 +144,6 @@
font-size: 1.2rem; font-size: 1.2rem;
} }
.chart {
position: absolute;
width: 100%;
}
.gasPriceDesc {
font-size: 0.9em;
}
.warning { .warning {
border-radius: 0.5em; border-radius: 0.5em;
background: #f80; background: #f80;

View File

@ -56,10 +56,6 @@ class Transfer extends Component {
store = new TransferStore(this.context.api, this.props); store = new TransferStore(this.context.api, this.props);
componentDidMount () {
this.store.getDefaults();
}
render () { render () {
const { stage, extras, steps } = this.store; const { stage, extras, steps } = this.store;
@ -186,27 +182,20 @@ class Transfer extends Component {
} }
renderExtrasPage () { renderExtrasPage () {
if (!this.store.gasPriceHistogram) { if (!this.store.gasStore.histogram) {
return null; return null;
} }
const { isEth, data, dataError, gas, gasEst, gasError, gasPrice } = this.store; const { isEth, data, dataError, total, totalError } = this.store;
const { gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.store;
return ( return (
<Extras <Extras
isEth={ isEth } isEth={ isEth }
data={ data } data={ data }
dataError={ dataError } dataError={ dataError }
gas={ gas }
gasEst={ gasEst }
gasError={ gasError }
gasPrice={ gasPrice }
gasPriceDefault={ gasPriceDefault }
gasPriceError={ gasPriceError }
gasPriceHistogram={ gasPriceHistogram }
total={ total } total={ total }
totalError={ totalError } totalError={ totalError }
gasStore={ this.store.gasStore }
onChange={ this.store.onUpdateDetails } /> onChange={ this.store.onUpdateDetails } />
); );
} }
@ -263,15 +252,15 @@ class Transfer extends Component {
} }
renderWarning () { renderWarning () {
const { gasLimitError } = this.store; const { errorEstimated } = this.store.gasStore;
if (!gasLimitError) { if (!errorEstimated) {
return null; return null;
} }
return ( return (
<div className={ styles.warning }> <div className={ styles.warning }>
{ gasLimitError } { errorEstimated }
</div> </div>
); );
} }

View File

@ -17,12 +17,10 @@
import { isEqual, uniq } from 'lodash'; import { isEqual, uniq } from 'lodash';
import Contract from '~/api/contract'; import Contract from '~/api/contract';
import { wallet as WALLET_ABI } from '~/contracts/abi';
import { bytesToHex, toHex } from '~/api/util/format'; import { bytesToHex, toHex } from '~/api/util/format';
import { ERROR_CODES } from '~/api/transport/error'; import { ERROR_CODES } from '~/api/transport/error';
import { MAX_GAS_ESTIMATION } from '../../util/constants'; import { wallet as WALLET_ABI } from '~/contracts/abi';
import { MAX_GAS_ESTIMATION } from '~/util/constants';
import WalletsUtils from '~/util/wallets'; import WalletsUtils from '~/util/wallets';
import { newError } from '~/ui/Errors/actions'; import { newError } from '~/ui/Errors/actions';

View File

@ -22,12 +22,11 @@ import IconButton from 'material-ui/IconButton';
import AddIcon from 'material-ui/svg-icons/content/add'; import AddIcon from 'material-ui/svg-icons/content/add';
import RemoveIcon from 'material-ui/svg-icons/content/remove'; import RemoveIcon from 'material-ui/svg-icons/content/remove';
import { fromWei, toWei } from '~/api/util/wei';
import Input from '~/ui/Form/Input'; import Input from '~/ui/Form/Input';
import InputAddressSelect from '~/ui/Form/InputAddressSelect'; import InputAddressSelect from '~/ui/Form/InputAddressSelect';
import Select from '~/ui/Form/Select'; import Select from '~/ui/Form/Select';
import { ABI_TYPES } from '~/util/abi'; import { ABI_TYPES } from '~/util/abi';
import { fromWei, toWei } from '~/api/util/wei';
import styles from './typedInput.css'; import styles from './typedInput.css';

View File

@ -15,3 +15,13 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.chart {
position: absolute;
width: 100%;
}
.columns {
display: flex;
flex-wrap: wrap;
position: relative;
}

View File

@ -29,10 +29,7 @@ import {
import Slider from 'material-ui/Slider'; import Slider from 'material-ui/Slider';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import componentStyles from './gasPriceSelector.css'; import styles from './gasPriceSelector.css';
import mainStyles from '../transfer.css';
const styles = Object.assign({}, mainStyles, componentStyles);
const COLORS = { const COLORS = {
default: 'rgba(255, 99, 132, 0.2)', default: 'rgba(255, 99, 132, 0.2)',
@ -194,10 +191,7 @@ class CustomizedShape extends Component {
class CustomTooltip extends Component { class CustomTooltip extends Component {
static propTypes = { static propTypes = {
gasPriceHistogram: PropTypes.shape({ gasPriceHistogram: PropTypes.object.isRequired,
bucketBounds: PropTypes.array.isRequired,
counts: PropTypes.array.isRequired
}).isRequired,
type: PropTypes.string, type: PropTypes.string,
payload: PropTypes.array, payload: PropTypes.array,
label: PropTypes.number, label: PropTypes.number,
@ -231,12 +225,16 @@ class CustomTooltip extends Component {
} }
} }
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 { export default class GasPriceSelector extends Component {
static propTypes = { static propTypes = {
gasPriceHistogram: PropTypes.shape({ gasPriceHistogram: PropTypes.object.isRequired,
bucketBounds: PropTypes.array.isRequired,
counts: PropTypes.array.isRequired
}).isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
gasPrice: PropTypes.oneOfType([ gasPrice: PropTypes.oneOfType([
@ -287,21 +285,23 @@ export default class GasPriceSelector extends Component {
renderSlider () { renderSlider () {
const { sliderValue } = this.state; const { sliderValue } = this.state;
return (<div className={ styles.columns }> return (
<Slider <div className={ styles.columns }>
min={ 0 } <Slider
max={ 1 } min={ 0 }
value={ sliderValue } max={ 1 }
onChange={ this.onEditGasPriceSlider } value={ sliderValue }
style={ { onChange={ this.onEditGasPriceSlider }
flex: 1, style={ {
padding: '0 0.3em' flex: 1,
} } padding: '0 0.3em'
sliderStyle={ { } }
marginBottom: 12 sliderStyle={ {
} } marginBottom: 12
/> } }
</div>); />
</div>
);
} }
renderChart () { renderChart () {
@ -316,85 +316,83 @@ export default class GasPriceSelector extends Component {
const countIndex = Math.max(0, Math.min(selectedIndex, gasPriceHistogram.counts.length - 1)); const countIndex = Math.max(0, Math.min(selectedIndex, gasPriceHistogram.counts.length - 1));
const selectedCount = countModifier(gasPriceHistogram.counts[countIndex]); const selectedCount = countModifier(gasPriceHistogram.counts[countIndex]);
return (<div className={ styles.columns }> return (
<div style={ { flex: 1, height } }> <div className={ styles.columns }>
<div className={ styles.chart }> <div style={ { flex: 1, height } }>
<ResponsiveContainer <div className={ styles.chart }>
height={ height } <ResponsiveContainer
> height={ height }
<ScatterChart
margin={ { top: 0, right: 0, left: 0, bottom: 0 } }
> >
<Scatter <ScatterChart
data={ [ margin={ { top: 0, right: 0, left: 0, bottom: 0 } }
{ x: sliderValue, y: 0 }, >
{ x: sliderValue, y: selectedCount }, <Scatter
{ x: sliderValue, y: chartData.yDomain[1] } data={ [
] } { x: sliderValue, y: 0 },
shape={ <CustomizedShape showValue={ selectedCount } /> } { x: sliderValue, y: selectedCount },
line { x: sliderValue, y: chartData.yDomain[1] }
isAnimationActive={ false } ] }
/> shape={ <CustomizedShape showValue={ selectedCount } /> }
line
isAnimationActive={ false }
/>
<XAxis <XAxis
hide hide
height={ 0 } height={ 0 }
dataKey='x' dataKey='x'
domain={ [0, 1] } domain={ [0, 1] }
/> />
<YAxis <YAxis
hide hide
width={ 0 } width={ 0 }
dataKey='y' dataKey='y'
domain={ chartData.yDomain } domain={ chartData.yDomain }
/> />
</ScatterChart> </ScatterChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
<div className={ styles.chart }> <div className={ styles.chart }>
<ResponsiveContainer <ResponsiveContainer
height={ height } height={ height }
>
<BarChart
data={ chartData.values }
margin={ { top: 0, right: 0, left: 0, bottom: 0 } }
barCategoryGap={ 1 }
ref='barChart'
> >
<Bar <BarChart
dataKey='value' data={ chartData.values }
stroke={ COLORS.line } margin={ { top: 0, right: 0, left: 0, bottom: 0 } }
onClick={ this.onClickGasPrice } barCategoryGap={ 1 }
shape={ <CustomBar selected={ selectedIndex } onClick={ this.onClickGasPrice } /> } ref='barChart'
/> >
<Bar
dataKey='value'
stroke={ COLORS.line }
onClick={ this.onClickGasPrice }
shape={ <CustomBar selected={ selectedIndex } onClick={ this.onClickGasPrice } /> }
/>
<Tooltip <Tooltip
wrapperStyle={ { wrapperStyle={ TOOL_STYLE }
backgroundColor: 'rgba(0, 0, 0, 0.75)', cursor={ this.renderCustomCursor() }
padding: '0 0.5em', content={ <CustomTooltip gasPriceHistogram={ gasPriceHistogram } /> }
fontSize: '0.9em' />
} }
cursor={ this.renderCustomCursor() }
content={ <CustomTooltip gasPriceHistogram={ gasPriceHistogram } /> }
/>
<XAxis <XAxis
hide hide
dataKey='index' dataKey='index'
type='category' type='category'
domain={ chartData.xDomain } domain={ chartData.xDomain }
/> />
<YAxis <YAxis
hide hide
type='number' type='number'
domain={ chartData.yDomain } domain={ chartData.yDomain }
/> />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div>
</div> </div>
</div> </div>
</div>); );
} }
renderCustomCursor = () => { renderCustomCursor = () => {

View File

@ -14,11 +14,36 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.signer {
.columns {
display: flex;
flex-wrap: wrap;
position: relative;
} }
.container { .graphColumn {
flex: 65;
} }
.mainContainer { .editColumn {
flex: 35;
padding-left: 1em;
justify-ontent: space-around;
padding-bottom: 12;
display: flex;
flex-wrap: wrap;
position: relative;
flex-direction: column;
}
.gasPriceDesc {
font-size: 0.75em;
opacity: 0.5;
}
.row {
display: flex;
flex-wrap: wrap;
position: relative;
flex-direction: column;
} }

View File

@ -0,0 +1,98 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { observer } from 'mobx-react';
import Input from '../Form/Input';
import GasPriceSelector from './GasPriceSelector';
import Store from './store';
import styles from './gasPriceEditor.css';
@observer
export default class GasPriceEditor extends Component {
static propTypes = {
children: PropTypes.node,
store: PropTypes.object.isRequired,
onChange: PropTypes.func
}
static Store = Store;
render () {
const { children, store } = this.props;
const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice } = store;
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
const priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`;
return (
<div className={ styles.columns }>
<div className={ styles.graphColumn }>
<GasPriceSelector
gasPriceHistogram={ histogram }
gasPrice={ price }
onChange={ this.onEditGasPrice } />
<div className={ styles.gasPriceDesc }>
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.
</div>
</div>
<div className={ styles.editColumn }>
<div className={ styles.row }>
<Input
label={ gasLabel }
hint='the amount of gas to use for the transaction'
error={ errorGas }
value={ gas }
onChange={ this.onEditGas } />
<Input
label={ priceLabel }
hint='the price of gas to use for the transaction'
error={ errorPrice }
value={ price }
onChange={ this.onEditGasPrice } />
</div>
<div className={ styles.row }>
{ children }
</div>
</div>
</div>
);
}
onEditGas = (event, gas) => {
const { store, onChange } = this.props;
store.setGas(gas);
onChange && onChange('gas', gas);
}
onEditGasPrice = (event, price) => {
const { store, onChange } = this.props;
store.setPrice(price);
onChange && onChange('gasPrice', price);
}
}

View File

@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './Animated'; export default from './gasPriceEditor';

View File

@ -0,0 +1,105 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { action, observable, transaction } from 'mobx';
import { ERRORS, validatePositiveNumber } from '~/util/validation';
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
export default class GasPriceEditor {
@observable errorEstimated = null;
@observable errorGas = null;
@observable errorPrice = null;
@observable estimated = DEFAULT_GAS;
@observable histogram = null;
@observable price = DEFAULT_GASPRICE;
@observable priceDefault = DEFAULT_GASPRICE;
@observable gas = DEFAULT_GAS;
@observable gasLimit = 0;
constructor (api, gasLimit, loadDefaults = true) {
this._api = api;
this.gasLimit = gasLimit;
if (loadDefaults) {
this.loadDefaults();
}
}
@action setEstimated = (estimated) => {
transaction(() => {
const bn = new BigNumber(estimated);
this.estimated = estimated;
if (bn.gte(MAX_GAS_ESTIMATION)) {
this.errorEstimated = ERRORS.gasException;
} else if (bn.gte(this.gasLimit)) {
this.errorEstimated = ERRORS.gasBlockLimit;
} else {
this.errorEstimated = null;
}
});
}
@action setHistogram = (gasHistogram) => {
this.histogram = gasHistogram;
}
@action setPrice = (price) => {
transaction(() => {
this.errorPrice = validatePositiveNumber(price).numberError;
this.price = price;
});
}
@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]) => {
transaction(() => {
this.setPrice(gasPrice.toFixed(0));
this.setHistogram(gasPriceHistogram);
this.priceDefault = gasPrice.toFixed();
});
})
.catch((error) => {
console.warn('getDefaults', error);
});
}
}

View File

@ -20,7 +20,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { LinearProgress } from 'material-ui'; import { LinearProgress } from 'material-ui';
import { txLink } from '../../3rdparty/etherscan/links'; import { txLink } from '~/3rdparty/etherscan/links';
import ShortenedHash from '../ShortenedHash'; import ShortenedHash from '../ShortenedHash';
import styles from './txHash.css'; import styles from './txHash.css';

View File

@ -20,7 +20,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { txLink, addressLink } from '../../3rdparty/etherscan/links'; import { txLink, addressLink } from '~/3rdparty/etherscan/links';
import IdentityIcon from '../IdentityIcon'; import IdentityIcon from '../IdentityIcon';
import IdentityName from '../IdentityName'; import IdentityName from '../IdentityName';

View File

@ -31,6 +31,7 @@ import CopyToClipboard from './CopyToClipboard';
import Editor from './Editor'; import Editor from './Editor';
import Errors from './Errors'; import Errors from './Errors';
import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form'; import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
import GasPriceEditor from './GasPriceEditor';
import IdentityIcon from './IdentityIcon'; import IdentityIcon from './IdentityIcon';
import IdentityName from './IdentityName'; import IdentityName from './IdentityName';
import Loading from './Loading'; import Loading from './Loading';
@ -67,7 +68,7 @@ export {
Errors, Errors,
Form, Form,
FormWrap, FormWrap,
TypedInput, GasPriceEditor,
Input, Input,
InputAddress, InputAddress,
InputAddressSelect, InputAddressSelect,
@ -91,5 +92,6 @@ export {
Tooltip, Tooltip,
Tooltips, Tooltips,
TxHash, TxHash,
TxList TxList,
TypedInput
}; };

View File

@ -1,28 +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 <http://www.gnu.org/licenses/>.
const checkIfTxFailed = (api, tx, gasSent) => {
return api.pollMethod('eth_getTransactionReceipt', tx)
.then((receipt) => {
// TODO: Right now, there's no way to tell wether the EVM code crashed.
// Because you usually send a bit more gas than estimated (to make sure
// it gets mined quickly), we transaction probably failed if all the gas
// has been used up.
return receipt.gasUsed.eq(gasSent);
});
};
export default checkIfTxFailed;

View File

@ -1,19 +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 <http://www.gnu.org/licenses/>.
export default (chain) => {
return chain === 'morden' || chain === 'ropsten' || chain === 'testnet';
};

View File

@ -18,7 +18,18 @@ const isValidReceipt = (receipt) => {
return receipt && receipt.blockNumber && receipt.blockNumber.gt(0); return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
}; };
const waitForConfirmations = (api, tx, confirmations) => { export function checkIfTxFailed (api, tx, gasSent) {
return api.pollMethod('eth_getTransactionReceipt', tx)
.then((receipt) => {
// TODO: Right now, there's no way to tell wether the EVM code crashed.
// Because you usually send a bit more gas than estimated (to make sure
// it gets mined quickly), we transaction probably failed if all the gas
// has been used up.
return receipt.gasUsed.eq(gasSent);
});
}
export function waitForConfirmations (api, tx, confirmations) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
api.pollMethod('eth_getTransactionReceipt', tx, isValidReceipt) api.pollMethod('eth_getTransactionReceipt', tx, isValidReceipt)
.then((receipt) => { .then((receipt) => {
@ -39,6 +50,4 @@ const waitForConfirmations = (api, tx, confirmations) => {
.catch(reject); .catch(reject);
}); });
}); });
}; }
export default waitForConfirmations;

View File

@ -20,6 +20,7 @@ import util from '~/api/util';
export const ERRORS = { export const ERRORS = {
invalidAddress: 'address is an invalid network address', invalidAddress: 'address is an invalid network address',
invalidAmount: 'the supplied amount should be a valid positive number',
duplicateAddress: 'the address is already in your address book', duplicateAddress: 'the address is already in your address book',
invalidChecksum: 'address has failed the checksum formatting', invalidChecksum: 'address has failed the checksum formatting',
invalidName: 'name should not be blank and longer than 2', invalidName: 'name should not be blank and longer than 2',
@ -27,7 +28,9 @@ export const ERRORS = {
invalidCode: 'code should be the compiled hex string', invalidCode: 'code should be the compiled hex string',
invalidNumber: 'invalid number format', invalidNumber: 'invalid number format',
negativeNumber: 'input number should be positive', negativeNumber: 'input number should be positive',
decimalNumber: 'input number should not contain decimals' decimalNumber: 'input number should not contain decimals',
gasException: 'the transaction will throw an exception with the current values',
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
}; };
export function validateAbi (abi, api) { export function validateAbi (abi, api) {
@ -133,6 +136,25 @@ export function validateName (name) {
}; };
} }
export function validatePositiveNumber (number) {
let numberError = null;
try {
const v = new BigNumber(number);
if (v.lt(0)) {
numberError = ERRORS.invalidAmount;
}
} catch (e) {
numberError = ERRORS.invalidAmount;
}
return {
number,
numberError
};
}
export function validateUint (value) { export function validateUint (value) {
let valueError = null; let valueError = null;

View File

@ -15,9 +15,6 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.transactions {
}
.infonone { .infonone {
opacity: 0.25; opacity: 0.25;
} }

View File

@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import etherscan from '../../../3rdparty/etherscan'; import etherscan from '~/3rdparty/etherscan';
import { Container, TxList } from '~/ui'; import { Container, TxList } from '~/ui';
import styles from './transactions.css'; import styles from './transactions.css';

View File

@ -14,8 +14,6 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.account {
}
.btnicon { .btnicon {
width: 24px; width: 24px;

View File

@ -105,7 +105,7 @@ class Account extends Component {
} }
return ( return (
<div className={ styles.account }> <div>
{ this.renderDeleteDialog(account) } { this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) } { this.renderEditDialog(account) }
{ this.renderFundDialog() } { this.renderFundDialog() }

View File

@ -14,8 +14,6 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.accounts {
}
.accountTooltip { .accountTooltip {
top: 13.3em; top: 13.3em;

View File

@ -82,7 +82,7 @@ class Accounts extends Component {
render () { render () {
return ( return (
<div className={ styles.accounts }> <div>
{ this.renderNewDialog() } { this.renderNewDialog() }
{ this.renderNewWalletDialog() } { this.renderNewWalletDialog() }
{ this.renderActionbar() } { this.renderActionbar() }

View File

@ -14,37 +14,37 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.address {
}
.delete .hero { .delete {
padding-bottom: 1em; .hero {
} padding-bottom: 1em;
}
.delete .info { .info {
display: inline-block; display: inline-block;
} }
.delete .icon { .icon {
display: inline-block; display: inline-block;
} }
.delete .nameinfo { .nameinfo {
display: inline-block; display: inline-block;
text-align: left; text-align: left;
} }
.delete .header { .header {
text-transform: uppercase; text-transform: uppercase;
font-size: 1.25em; font-size: 1.25em;
padding-bottom: 0.25em; padding-bottom: 0.25em;
} }
.delete .address { .address {
} }
.delete .description { .description {
padding-top: 1em; padding-top: 1em;
font-size: 0.75em; font-size: 0.75em;
color: #aaa; color: #aaa;
}
} }

View File

@ -28,8 +28,6 @@ import Transactions from '../Account/Transactions';
import Delete from './Delete'; import Delete from './Delete';
import { setVisibleAccounts } from '~/redux/providers/personalActions'; import { setVisibleAccounts } from '~/redux/providers/personalActions';
import styles from './address.css';
class Address extends Component { class Address extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired, api: PropTypes.object.isRequired,
@ -85,7 +83,7 @@ class Address extends Component {
} }
return ( return (
<div className={ styles.address }> <div>
{ this.renderEditDialog(contact) } { this.renderEditDialog(contact) }
{ this.renderActionbar(contact) } { this.renderActionbar(contact) }
<Delete <Delete

View File

@ -14,8 +14,6 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.addresses {
}
.list { .list {
display: flex; display: flex;
@ -26,21 +24,21 @@
flex: 0 1 50%; flex: 0 1 50%;
width: 50%; width: 50%;
position: relative; position: relative;
}
.address:nth-child(odd)>div { &:nth-child(odd)>div {
padding-right: 0.5em !important; padding-right: 0.5em !important;
} }
.address:nth-child(even)>div { &:nth-child(even)>div {
padding-left: 0.5em !important; padding-left: 0.5em !important;
}
} }
.empty { .empty {
width: 100%; width: 100%;
display: block; display: block;
}
.empty div { div {
color: #aaa; color: #aaa;
}
} }

View File

@ -76,7 +76,7 @@ class Addresses extends Component {
const { searchValues, sortOrder } = this.state; const { searchValues, sortOrder } = this.state;
return ( return (
<div className={ styles.addresses }> <div>
{ this.renderActionbar() } { this.renderActionbar() }
{ this.renderAddAddress() } { this.renderAddAddress() }
<Page> <Page>

View File

@ -19,7 +19,6 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import ActionCompareArrows from 'material-ui/svg-icons/action/compare-arrows'; import ActionCompareArrows from 'material-ui/svg-icons/action/compare-arrows';
import ActionDashboard from 'material-ui/svg-icons/action/dashboard'; import ActionDashboard from 'material-ui/svg-icons/action/dashboard';
// import CommunicationVpnKey from 'material-ui/svg-icons/communication/vpn-key';
import HardwareDesktopMac from 'material-ui/svg-icons/hardware/desktop-mac'; import HardwareDesktopMac from 'material-ui/svg-icons/hardware/desktop-mac';
import NotificationVpnLock from 'material-ui/svg-icons/notification/vpn-lock'; import NotificationVpnLock from 'material-ui/svg-icons/notification/vpn-lock';

View File

@ -20,7 +20,7 @@ import React, { Component, PropTypes } from 'react';
import { IdentityIcon, IdentityName, Input, InputAddress } from '~/ui'; import { IdentityIcon, IdentityName, Input, InputAddress } from '~/ui';
import ShortenedHash from '~/ui/ShortenedHash'; import ShortenedHash from '~/ui/ShortenedHash';
import { txLink } from '../../../../3rdparty/etherscan/links'; import { txLink } from '~/3rdparty/etherscan/links';
import styles from '../../contract.css'; import styles from '../../contract.css';

View File

@ -15,26 +15,26 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.contract {
}
.events { .events {
width: 100%; width: 100%;
border: none; border: none;
border-spacing: 0; border-spacing: 0;
}
.events tr { tr {
line-height: 32px; line-height: 32px;
vertical-align: top; vertical-align: top;
}
} }
.event { .event {
} td {
vertical-align: top;
padding: 1em 0.5em;
.event td { div {
vertical-align: top; white-space: nowrap;
padding: 1em 0.5em; }
}
} }
.txhash { .txhash {
@ -47,10 +47,6 @@
color: #aaa; color: #aaa;
} }
.event td div {
white-space: nowrap;
}
.mined { .mined {
} }

View File

@ -124,7 +124,7 @@ class Contract extends Component {
} }
return ( return (
<div className={ styles.contract }> <div>
{ this.renderActionbar(account) } { this.renderActionbar(account) }
{ this.renderDeleteDialog(account) } { this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) } { this.renderEditDialog(account) }

View File

@ -1,18 +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 <http://www.gnu.org/licenses/>.
*/
.contracts {
}

View File

@ -28,8 +28,6 @@ import { setVisibleAccounts } from '~/redux/providers/personalActions';
import List from '../Accounts/List'; import List from '../Accounts/List';
import styles from './contracts.css';
class Contracts extends Component { class Contracts extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired
@ -80,7 +78,7 @@ class Contracts extends Component {
const { searchValues, sortOrder } = this.state; const { searchValues, sortOrder } = this.state;
return ( return (
<div className={ styles.contracts }> <div>
{ this.renderActionbar() } { this.renderActionbar() }
{ this.renderAddContract() } { this.renderAddContract() }
{ this.renderAddContract() } { this.renderAddContract() }
@ -159,7 +157,6 @@ class Contracts extends Component {
return ( return (
<Actionbar <Actionbar
className={ styles.toolbar }
title='Contracts' title='Contracts'
buttons={ buttons } /> buttons={ buttons } />
); );

View File

@ -15,9 +15,6 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.layout {
}
.menu { .menu {
display: inline-block; display: inline-block;
} }
@ -35,31 +32,24 @@
padding: 16px 2em !important; padding: 16px 2em !important;
line-height: 24px !important; line-height: 24px !important;
width: auto !important; width: auto !important;
}
.tabactive { &>div {
} height: 24px !important;
.tab>div, &>div {
.tabactive>div { display: inline-block !important;
height: 24px !important; }
} }
.tab>div>div, svg {
.tabactive>div>div { margin-right: 0.5em;
display: inline-block !important; margin-bottom: 0 !important;
} }
.tab svg, .menu {
.tabactive svg { vertical-align: top;
margin-right: 0.5em; display: inline-block;
margin-bottom: 0 !important; }
}
.tab .menu,
.tabactive .menu {
vertical-align: top;
display: inline-block;
} }
.imageIcon { .imageIcon {
@ -68,6 +58,8 @@
opacity: 0.5; opacity: 0.5;
} }
.tabactive .imageIcon { .tabactive {
opacity: 1; .imageIcon {
opacity: 1;
}
} }

View File

@ -45,7 +45,7 @@ export default class Settings extends Component {
} }
return ( return (
<div className={ styles.layout }> <div>
<Actionbar title='settings' className={ styles.bar }> <Actionbar title='settings' className={ styles.bar }>
<Tabs className={ styles.tabs } value={ hash }> <Tabs className={ styles.tabs } value={ hash }>
{ this.renderTab(hash, 'views', <ImageRemoveRedEye />) } { this.renderTab(hash, 'views', <ImageRemoveRedEye />) }

View File

@ -16,7 +16,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { addressLink } from '../../../../../3rdparty/etherscan/links'; import { addressLink } from '~/3rdparty/etherscan/links';
import styles from './AccountLink.css'; import styles from './AccountLink.css';
export default class AccountLink extends Component { export default class AccountLink extends Component {

View File

@ -16,7 +16,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { txLink } from '../../../../3rdparty/etherscan/links'; import { txLink } from '~/3rdparty/etherscan/links';
export default class TxHashLink extends Component { export default class TxHashLink extends Component {

View File

@ -23,9 +23,6 @@
width: $embedWidth; width: $embedWidth;
} }
.pending {
}
.none { .none {
color: #aaa; color: #aaa;
} }

View File

@ -71,7 +71,7 @@ class Embedded extends Component {
const items = pending.sort(this._sortRequests).map(this.renderPending); const items = pending.sort(this._sortRequests).map(this.renderPending);
return ( return (
<div className={ styles.pending }> <div>
{ items } { items }
</div> </div>
); );

View File

@ -15,12 +15,6 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.request {
}
.noRequestsMsg { .noRequestsMsg {
color: #aaa; color: #aaa;
} }
.items {
}

View File

@ -98,9 +98,7 @@ class RequestsPage extends Component {
return ( return (
<Container title='Pending Requests'> <Container title='Pending Requests'>
<div className={ styles.items }> { items }
{ items }
</div>
</Container> </Container>
); );
} }
@ -111,7 +109,6 @@ class RequestsPage extends Component {
return ( return (
<RequestPending <RequestPending
className={ styles.request }
onConfirm={ actions.startConfirmRequest } onConfirm={ actions.startConfirmRequest }
onReject={ actions.startRejectRequest } onReject={ actions.startRejectRequest }
isSending={ isSending || false } isSending={ isSending || false }

View File

@ -19,12 +19,10 @@ import React, { Component } from 'react';
import { Actionbar } from '~/ui'; import { Actionbar } from '~/ui';
import RequestsPage from './containers/RequestsPage'; import RequestsPage from './containers/RequestsPage';
import styles from './signer.css';
export default class Signer extends Component { export default class Signer extends Component {
render () { render () {
return ( return (
<div className={ styles.signer }> <div>
<Actionbar <Actionbar
title='Trusted Signer' /> title='Trusted Signer' />
<RequestsPage /> <RequestsPage />

View File

@ -1,55 +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 <http://www.gnu.org/licenses/>.
*/
/* todo [adgo] - make local */
:global .transition-appear {
opacity: 0.01;
}
:global .transition-appear.transition-appear-active {
opacity: 1;
transition: opacity .3s ease-in-out;
}
:global .transition-enter {
opacity: 0.01;
}
:global .transition-enter.transition-enter-active {
opacity: 1;
transition: opacity .3s ease-in-out;
}
:global .transition-leave {
opacity: 1;
}
:global .transition-leave.transition-leave-active {
opacity: 0.01;
transition: opacity .3s ease-in-out;
}
:global .absoluteAnimationContainer {
position: relative;
}
:global .absoluteAnimationContainer > .transition-leave {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
}

View File

@ -1,28 +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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import AnimateChildren from './children';
export default Wrapped => class Animated extends Component {
render () {
return (
<AnimateChildren>
<Wrapped { ...this.props } />
</AnimateChildren>
);
}
};

View File

@ -1,63 +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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { isReactComponent } from '../../util/react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import './AnimateChildren.css';
export default class AnimateChildren extends Component {
render () {
const className = this.props.absolute ? 'absoluteAnimationContainer' : '';
return (
<ReactCSSTransitionGroup
component='div'
className={ className }
transitionName='transition'
transitionAppear
transitionAppearTimeout={ 0 }
transitionLeaveTimeout={ 0 }
transitionEnterTimeout={ 0 }
>
{ this.renderChildren() }
</ReactCSSTransitionGroup>
);
}
renderChildren () {
const { children, isView } = this.props;
if (isView) {
return React.cloneElement(this.props.children, {
key: this.props.pathname
});
}
if (isReactComponent(children)) {
return React.cloneElement(this.props.children, { ...this.props });
}
return children;
}
static propTypes = {
children: PropTypes.any.isRequired,
pathname: PropTypes.string,
isView: PropTypes.bool,
absolute: PropTypes.bool
}
}

View File

@ -15,7 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import AnimateChildren from '../../components-compositors/Animated/children';
import Call from '../Call'; import Call from '../Call';
import CallsToolbar from '../CallsToolbar'; import CallsToolbar from '../CallsToolbar';
import styles from './Calls.css'; import styles from './Calls.css';
@ -73,13 +72,11 @@ export default class Calls extends Component {
} }
return ( return (
<AnimateChildren> <div { ...this._test('empty-wrapper') }>
<div { ...this._test('empty-wrapper') }> <h3 className={ styles.historyInfo } { ...this._test('empty') }>
<h3 className={ styles.historyInfo } { ...this._test('empty') }> Fire up some calls and the results will be here.
Fire up some calls and the results will be here. </h3>
</h3> </div>
</div>
</AnimateChildren>
); );
} }
@ -90,17 +87,13 @@ export default class Calls extends Component {
return; return;
} }
return ( return calls.map((call, idx) => (
<AnimateChildren> <Call
{ calls.map((call, idx) => ( key={ calls.length - idx }
<Call call={ call }
key={ calls.length - idx } setActiveCall={ this.setActiveCall }
call={ call } />
setActiveCall={ this.setActiveCall } ));
/>
)) }
</AnimateChildren>
);
} }
clearActiveCall = () => { clearActiveCall = () => {

View File

@ -23,8 +23,6 @@ import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from '~/redux/
import Debug from '../../components/Debug'; import Debug from '../../components/Debug';
import Status from '../../components/Status'; import Status from '../../components/Status';
import styles from './statusPage.css';
class StatusPage extends Component { class StatusPage extends Component {
static propTypes = { static propTypes = {
nodeStatus: PropTypes.object.isRequired, nodeStatus: PropTypes.object.isRequired,
@ -41,7 +39,7 @@ class StatusPage extends Component {
render () { render () {
return ( return (
<div className={ styles.body }> <div>
<Status { ...this.props } /> <Status { ...this.props } />
<Debug { ...this.props } /> <Debug { ...this.props } />
</div> </div>

View File

@ -1,18 +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 <http://www.gnu.org/licenses/>.
*/
.body {
}

View File

@ -23,6 +23,7 @@ const postcssImport = require('postcss-import');
const postcssNested = require('postcss-nested'); const postcssNested = require('postcss-nested');
const postcssVars = require('postcss-simple-vars'); const postcssVars = require('postcss-simple-vars');
const rucksack = require('rucksack-css'); const rucksack = require('rucksack-css');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const ENV = process.env.NODE_ENV || 'development'; const ENV = process.env.NODE_ENV || 'development';
const isProd = ENV === 'production'; const isProd = ENV === 'production';
@ -102,7 +103,12 @@ function getPlugins (_isProd = isProd) {
} }
}), }),
new webpack.optimize.OccurrenceOrderPlugin(!_isProd) new webpack.optimize.OccurrenceOrderPlugin(!_isProd),
new CircularDependencyPlugin({
exclude: /node_modules/,
failOnError: true
})
]; ];
if (_isProd) { if (_isProd) {