diff --git a/js/package.json b/js/package.json
index d09f865d7..24df869f3 100644
--- a/js/package.json
+++ b/js/package.json
@@ -152,6 +152,7 @@
"isomorphic-fetch": "2.2.1",
"js-sha3": "0.5.5",
"lodash": "4.17.2",
+ "loglevel": "1.4.1",
"marked": "0.3.6",
"material-ui": "0.16.5",
"material-ui-chip-input": "0.11.1",
diff --git a/js/src/config.js b/js/src/config.js
new file mode 100644
index 000000000..87fedecb8
--- /dev/null
+++ b/js/src/config.js
@@ -0,0 +1,28 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import LogLevel from 'loglevel';
+
+export const LOG_KEYS = {
+ TransferModalStore: {
+ path: 'modals/Transfer/store',
+ desc: 'Transfer Modal MobX Store'
+ }
+};
+
+export const getLogger = (LOG_KEY) => {
+ return LogLevel.getLogger(LOG_KEY.path);
+};
diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js
index d2796b098..338e1f255 100644
--- a/js/src/modals/Transfer/store.js
+++ b/js/src/modals/Transfer/store.js
@@ -25,6 +25,9 @@ import ERRORS from './errors';
import { ERROR_CODES } from '~/api/transport/error';
import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants';
import GasPriceStore from '~/ui/GasPriceEditor/store';
+import { getLogger, LOG_KEYS } from '~/config';
+
+const log = getLogger(LOG_KEYS.TransferModalStore);
const TITLES = {
transfer: 'transfer details',
@@ -332,13 +335,12 @@ export default class TransferStore {
});
}
- @action recalculateGas = () => {
+ @action recalculateGas = (redo = true) => {
if (!this.isValid) {
- this.gasStore.setGas('0');
- return this.recalculate();
+ return this.recalculate(redo);
}
- this
+ return this
.estimateGas()
.then((gasEst) => {
let gas = gasEst;
@@ -351,76 +353,215 @@ export default class TransferStore {
this.gasStore.setEstimated(gasEst.toFixed(0));
this.gasStore.setGas(gas.toFixed(0));
- this.recalculate();
+ this.recalculate(redo);
});
})
.catch((error) => {
console.warn('etimateGas', error);
- this.recalculate();
+ this.recalculate(redo);
});
}
- @action recalculate = () => {
- const { account } = this;
-
- if (!account || !this.balance) {
- return;
+ getBalance (forceSender = false) {
+ if (this.isWallet && !forceSender) {
+ return this.balance;
}
const balance = this.senders
? this.sendersBalances[this.sender]
: this.balance;
+ return balance;
+ }
+
+ getToken (tag = this.tag, forceSender = false) {
+ const balance = this.getBalance(forceSender);
+
+ if (!balance) {
+ return null;
+ }
+
+ const _tag = tag.toLowerCase();
+ const token = balance.tokens.find((b) => b.token.tag.toLowerCase() === _tag);
+
+ return token;
+ }
+
+ /**
+ * Return the balance of the selected token
+ * (in WEI for ETH, without formating for other tokens)
+ */
+ getTokenBalance (tag = this.tag, forceSender = false) {
+ const token = this.getToken(tag, forceSender);
+
+ if (!token) {
+ return new BigNumber(0);
+ }
+
+ const value = new BigNumber(token.value || 0);
+
+ return value;
+ }
+
+ getTokenValue (tag = this.tag, value = this.value, inverse = false) {
+ const token = this.getToken(tag);
+
+ if (!token) {
+ return new BigNumber(0);
+ }
+
+ const format = token.token
+ ? new BigNumber(token.token.format || 1)
+ : new BigNumber(1);
+
+ let _value;
+
+ try {
+ _value = new BigNumber(value || 0);
+ } catch (error) {
+ _value = new BigNumber(0);
+ }
+
+ if (token.token && token.token.tag.toLowerCase() === 'eth') {
+ if (inverse) {
+ return this.api.util.fromWei(_value);
+ }
+
+ return this.api.util.toWei(_value);
+ }
+
+ if (inverse) {
+ return _value.div(format);
+ }
+
+ return _value.mul(format);
+ }
+
+ getValues (_gasTotal) {
+ const gasTotal = new BigNumber(_gasTotal || 0);
+ const { valueAll, isEth, isWallet } = this;
+
+ if (!valueAll) {
+ const value = this.getTokenValue();
+
+ // If it's a token or a wallet, eth is the estimated gas,
+ // and value is the user input
+ if (!isEth || isWallet) {
+ return {
+ eth: gasTotal,
+ token: value
+ };
+ }
+
+ // Otherwise, eth is the sum of the gas and the user input
+ const totalEthValue = gasTotal.plus(value);
+
+ return {
+ eth: totalEthValue,
+ token: value
+ };
+ }
+
+ // If it's the total balance that needs to be sent, send the total balance
+ // if it's not a proper ETH transfer
+ if (!isEth || isWallet) {
+ const tokenBalance = this.getTokenBalance();
+
+ return {
+ eth: gasTotal,
+ token: tokenBalance
+ };
+ }
+
+ // Otherwise, substract the gas estimate
+ const availableEth = this.getTokenBalance('ETH');
+ const totalEthValue = availableEth.gt(gasTotal)
+ ? availableEth.minus(gasTotal)
+ : new BigNumber(0);
+
+ return {
+ eth: totalEthValue.plus(gasTotal),
+ token: totalEthValue
+ };
+ }
+
+ getFormattedTokenValue (tokenValue) {
+ const token = this.getToken();
+
+ if (!token) {
+ return new BigNumber(0);
+ }
+
+ const tag = token.token && token.token.tag || '';
+
+ return this.getTokenValue(tag, tokenValue, true);
+ }
+
+ @action recalculate = (redo = false) => {
+ const { account } = this;
+
+ if (!account || !this.balance) {
+ return;
+ }
+
+ const balance = this.getBalance();
+
if (!balance) {
return;
}
- const { tag, valueAll, isEth, isWallet } = this;
-
const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0));
- const availableEth = new BigNumber(balance.tokens[0].value);
+ const ethBalance = this.getTokenBalance('ETH', true);
+ const tokenBalance = this.getTokenBalance();
+ const { eth, token } = this.getValues(gasTotal);
- const senderBalance = this.balance.tokens.find((b) => tag === b.token.tag);
- const format = new BigNumber(senderBalance.token.format || 1);
- const available = new BigNumber(senderBalance.value).div(format);
-
- let { value, valueError } = this;
let totalEth = gasTotal;
let totalError = null;
+ let valueError = null;
- if (valueAll) {
- if (isEth && !isWallet) {
- const bn = this.api.util.fromWei(availableEth.minus(gasTotal));
- value = (bn.lt(0) ? new BigNumber(0.0) : bn).toString();
- } else if (isEth) {
- value = (available.lt(0) ? new BigNumber(0.0) : available).toString();
- } else {
- value = available.toString();
- }
- }
-
- if (isEth && !isWallet) {
- totalEth = totalEth.plus(this.api.util.toWei(value || 0));
- }
-
- if (new BigNumber(value || 0).gt(available)) {
- valueError = ERRORS.largeAmount;
- } else if (valueError === ERRORS.largeAmount) {
- valueError = null;
- }
-
- if (totalEth.gt(availableEth)) {
+ if (eth.gt(ethBalance)) {
totalError = ERRORS.largeAmount;
}
+ if (token && token.gt(tokenBalance)) {
+ valueError = ERRORS.largeAmount;
+ }
+
+ log.debug('@recalculate', {
+ eth: eth.toFormat(),
+ token: token.toFormat(),
+ ethBalance: ethBalance.toFormat(),
+ tokenBalance: tokenBalance.toFormat(),
+ gasTotal: gasTotal.toFormat()
+ });
+
transaction(() => {
- this.total = this.api.util.fromWei(totalEth).toFixed();
this.totalError = totalError;
- this.value = value;
this.valueError = valueError;
this.gasStore.setErrorTotal(totalError);
this.gasStore.setEthValue(totalEth);
+
+ this.total = this.api.util.fromWei(eth).toFixed();
+
+ const nextValue = this.getFormattedTokenValue(token);
+ let prevValue;
+
+ try {
+ prevValue = new BigNumber(this.value || 0);
+ } catch (error) {
+ prevValue = new BigNumber(0);
+ }
+
+ // Change the input only if necessary
+ if (!nextValue.eq(prevValue)) {
+ this.value = nextValue.toString();
+ }
+
+ // Re Calculate gas once more to be sure
+ if (redo) {
+ return this.recalculateGas(false);
+ }
});
}
@@ -485,8 +626,10 @@ export default class TransferStore {
options.gas = MAX_GAS_ESTIMATION;
}
+ const { token } = this.getValues(options.gas);
+
if (isEth && !isWallet && !forceToken) {
- options.value = this.api.util.toWei(this.value || 0);
+ options.value = token;
options.data = this._getData(gas);
return { options, values: [] };
@@ -494,7 +637,7 @@ export default class TransferStore {
if (isWallet && !forceToken) {
const to = isEth ? this.recipient : this.token.contract.address;
- const value = isEth ? this.api.util.toWei(this.value || 0) : new BigNumber(0);
+ const value = isEth ? token : new BigNumber(0);
const values = [
to, value,
@@ -506,7 +649,7 @@ export default class TransferStore {
const values = [
this.recipient,
- new BigNumber(this.value || 0).mul(this.token.format).toFixed(0)
+ token.toFixed(0)
];
return { options, values };
diff --git a/js/src/ui/Form/Select/select.js b/js/src/ui/Form/Select/select.js
index f79cae58c..fb0940415 100644
--- a/js/src/ui/Form/Select/select.js
+++ b/js/src/ui/Form/Select/select.js
@@ -15,7 +15,7 @@
// along with Parity. If not, see .
import React, { Component, PropTypes } from 'react';
-import { SelectField } from 'material-ui';
+import { MenuItem, SelectField } from 'material-ui';
import { nodeOrStringProptype } from '~/util/proptypes';
@@ -42,11 +42,12 @@ export default class Select extends Component {
onChange: PropTypes.func,
onKeyDown: PropTypes.func,
type: PropTypes.string,
- value: PropTypes.any
+ value: PropTypes.any,
+ values: PropTypes.array
}
render () {
- const { children, className, disabled, error, hint, label, onBlur, onChange, onKeyDown, value } = this.props;
+ const { className, disabled, error, hint, label, onBlur, onChange, onKeyDown, value } = this.props;
return (
- { children }
+ value={ value }
+ >
+ { this.renderChildren() }
);
}
+
+ renderChildren () {
+ const { children, values } = this.props;
+
+ if (children) {
+ return children;
+ }
+
+ if (!values) {
+ return null;
+ }
+
+ return values.map((data, index) => {
+ const { name = index, value = index } = data;
+
+ return (
+
+ );
+ });
+ }
}
diff --git a/js/src/views/Settings/Parity/parity.js b/js/src/views/Settings/Parity/parity.js
index 978ef296f..c52d713cd 100644
--- a/js/src/views/Settings/Parity/parity.js
+++ b/js/src/views/Settings/Parity/parity.js
@@ -17,7 +17,9 @@
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { MenuItem } from 'material-ui';
+import LogLevel from 'loglevel';
+import { LOG_KEYS } from '~/config';
import { Select, Container, LanguageSelector } from '~/ui';
import layout from '../layout.css';
@@ -25,14 +27,54 @@ import layout from '../layout.css';
export default class Parity extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
- }
+ };
state = {
- mode: 'active'
- }
+ loglevels: {},
+ mode: 'active',
+ selectValues: []
+ };
componentWillMount () {
this.loadMode();
+ this.loadLogLevels();
+ this.setSelectValues();
+ }
+
+ loadLogLevels () {
+ if (process.env.NODE_ENV === 'production') {
+ return null;
+ }
+
+ const nextState = { ...this.state.logLevels };
+
+ Object.keys(LOG_KEYS).map((logKey) => {
+ const log = LOG_KEYS[logKey];
+
+ const logger = LogLevel.getLogger(log.path);
+ const level = logger.getLevel();
+
+ nextState[logKey] = { level, log };
+ });
+
+ this.setState({ logLevels: nextState });
+ }
+
+ setSelectValues () {
+ if (process.env.NODE_ENV === 'production') {
+ return null;
+ }
+
+ const selectValues = Object.keys(LogLevel.levels).map((levelName) => {
+ const value = LogLevel.levels[levelName];
+
+ return {
+ name: levelName,
+ value
+ };
+ });
+
+ this.setState({ selectValues });
}
render () {
@@ -45,7 +87,8 @@ export default class Parity extends Component {
+ defaultMessage='Control the Parity node settings and mode of operation via this interface.'
+ />
@@ -53,10 +96,64 @@ export default class Parity extends Component {
{ this.renderModes() }
+
+ { this.renderLogsConfig() }
);
}
+ renderLogsConfig () {
+ if (process.env.NODE_ENV === 'production') {
+ return null;
+ }
+
+ return (
+
+
+
+ { this.renderLogsLevels() }
+
+
+ );
+ }
+
+ renderLogsLevels () {
+ if (process.env.NODE_ENV === 'production') {
+ return null;
+ }
+
+ const { logLevels, selectValues } = this.state;
+
+ return Object.keys(logLevels).map((logKey) => {
+ const { level, log } = logLevels[logKey];
+ const { path, desc } = log;
+
+ const onChange = (_, index) => {
+ const nextLevel = Object.values(selectValues)[index].value;
+ LogLevel.getLogger(path).setLevel(nextLevel);
+ this.loadLogLevels();
+ };
+
+ return (
+
+ );
+ });
+ }
+
renderModes () {
const { mode } = this.state;