+
diff --git a/app/components/Settings/FeeAssetSettings.jsx b/app/components/Settings/FeeAssetSettings.jsx
index d79fb04ef6..5f309c40dd 100644
--- a/app/components/Settings/FeeAssetSettings.jsx
+++ b/app/components/Settings/FeeAssetSettings.jsx
@@ -50,20 +50,22 @@ class FeeAssetSettings extends React.Component {
>
{counterpart.translate("settings.change_default_fee_asset")}
- {
- this.setState({current_asset: value});
- }}
- close={() => {
- this.setState({showModal: false});
- }}
- />
+ {this.state.showModal && (
+ {
+ this.setState({current_asset: value});
+ }}
+ close={() => {
+ this.setState({showModal: false});
+ }}
+ />
+ )}
);
}
diff --git a/app/components/Utility/AmountSelectorStyleGuide.jsx b/app/components/Utility/AmountSelectorStyleGuide.jsx
index 2d819c30cf..8ba7226672 100644
--- a/app/components/Utility/AmountSelectorStyleGuide.jsx
+++ b/app/components/Utility/AmountSelectorStyleGuide.jsx
@@ -114,18 +114,23 @@ class AmountSelector extends DecimalChecker {
validateStatus={this.props.validateStatus}
help={this.props.help}
>
-
+
+
+ {addonAfter}
+
);
}
diff --git a/app/components/Utility/AssetSelect.jsx b/app/components/Utility/AssetSelect.jsx
index 6ab5ea3271..e56e552b95 100644
--- a/app/components/Utility/AssetSelect.jsx
+++ b/app/components/Utility/AssetSelect.jsx
@@ -8,6 +8,7 @@ import ChainTypes from "../Utility/ChainTypes";
import BindToChainState from "../Utility/BindToChainState";
import {Map} from "immutable";
import AssetName from "../Utility/AssetName";
+import LoadingIndicator from "../LoadingIndicator";
const AssetSelectView = ({
label,
@@ -17,26 +18,30 @@ const AssetSelectView = ({
style,
placeholder,
value,
+ onDropdownVisibleChange,
...props
}) => {
- const onlyOne = assets.filter(Map.isMap).length <= 1;
+ const disableSelect =
+ assets.filter(Map.isMap).length <= 1 && !onDropdownVisibleChange;
+ // if onDropdownVisibleChange given we assume that lazy loading takes place
const select = (
}
- value={
}
+ value={value}
{...props}
optionFilterProp="children"
filterOption={(input, option) =>
option.key.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
- disabled={onlyOne}
+ disabled={disableSelect}
notFoundContent={counterpart.translate("global.not_found")}
>
{assets.filter(Map.isMap).map(asset => {
@@ -51,6 +56,11 @@ const AssetSelectView = ({
);
})}
+ {props.loading && (
+
+
+
+ )}
);
return (
diff --git a/app/components/Utility/FeeAssetSelector.jsx b/app/components/Utility/FeeAssetSelector.jsx
index f13775089e..61f5d44aa9 100644
--- a/app/components/Utility/FeeAssetSelector.jsx
+++ b/app/components/Utility/FeeAssetSelector.jsx
@@ -4,208 +4,256 @@ import Immutable from "immutable";
import counterpart from "counterpart";
import AssetWrapper from "./AssetWrapper";
import PropTypes from "prop-types";
-import {Form, Input, Button, Tooltip} from "bitshares-ui-style-guide";
+import {Form, Input, Button, Tooltip, Icon} from "bitshares-ui-style-guide";
import AssetSelect from "./AssetSelect";
-import {ChainStore} from "bitsharesjs";
+import {FetchChain} from "bitsharesjs";
import SetDefaultFeeAssetModal from "../Modal/SetDefaultFeeAssetModal";
-import {debounce} from "lodash-es";
+import debounceRender from "react-debounce-render";
import {connect} from "alt-react";
import SettingsStore from "../../stores/SettingsStore";
import {checkFeeStatusAsync} from "common/trxHelper";
class FeeAssetSelector extends React.Component {
+ static propTypes = {
+ // injected
+ defaultFeeAsset: PropTypes.any,
+
+ // object wih data required for fee calculation
+ transaction: PropTypes.any,
+
+ // assets to choose from
+ assets: PropTypes.any,
+
+ // a translation key for the input label, defaults to "Fee"
+ label: PropTypes.string,
+
+ // handler for changedFee (asset, or amount)
+ onChange: PropTypes.func,
+
+ // account which pays fee
+ account: PropTypes.any,
+
+ // tab index if needed
+ tabIndex: PropTypes.number,
+
+ // do not allow to switch the asset or amount
+ disabled: PropTypes.bool
+ };
+
+ static defaultProps = {
+ label: "transfer.fee",
+ disabled: false
+ };
+
constructor(props) {
super(props);
this.state = {
- asset: null,
- assets: [],
- fee_amount: 0,
- fee_asset_id:
- ChainStore.assets_by_symbol.get(
- props.settings.get("fee_asset")
- ) || "1.3.0",
- fees: {},
- feeStatus: {},
+ feeAsset: props.defaultFeeAsset,
+
+ calculatedFeeAmount: null,
+
+ assets: null,
+ assetsLoading: false,
+
isModalVisible: false,
- error: null,
- assets_fetched: false,
- last_fee_check_params: {}
+ error: null
};
- this._updateFee = debounce(this._updateFee.bind(this), 250);
}
- async _getFees(assets, account, trxInfo) {
- const accountID = account.get("id");
- let result = this.state.fees;
- for (let asset_id of assets) {
- const {fee} = await checkFeeStatusAsync({
- ...trxInfo,
- accountID,
- feeID: asset_id
- });
- result[asset_id] = fee.getAmount({real: true});
+ async _calculateFee(asset = null) {
+ const {account, transaction} = this.props;
+ const setState = asset == null;
+ if (!asset) {
+ asset = this.state.feeAsset;
}
- this.setState({fees: result});
- }
-
- shouldComponentUpdate(np, ns) {
- return (
- ns.fee_amount !== this.state.fee_amount ||
- ns.fee_asset_id !== this.state.fee_asset_id ||
- ns.isModalVisible !== this.state.isModalVisible ||
- ns.assets_fetched !== this.state.assets_fetched ||
- ns.assets.length !== this.state.assets.length
- );
- }
+ const feeID = typeof asset == "string" ? asset : asset.get("id");
+ try {
+ const {fee, hasPoolBalance} = await checkFeeStatusAsync({
+ ...transaction,
+ accountID: account.get("id"),
+ feeID
+ });
- __are_equal_shallow(o1, o2) {
- for (var p in o1) {
- if (o1.hasOwnProperty(p)) {
- if (o1[p] !== o2[p]) {
- return false;
- }
- }
- }
- for (var p in o2) {
- if (o2.hasOwnProperty(p)) {
- if (o1[p] !== o2[p]) {
- return false;
- }
+ if (setState) {
+ this.setState(
+ {
+ calculatedFeeAmount: fee.getAmount({real: true}),
+ error: !hasPoolBalance
+ ? {
+ key: "noPoolBalanceShort",
+ tooltip: "noPoolBalance"
+ }
+ : false
+ },
+ () => {
+ if (this.props.onChange) {
+ this.props.onChange(fee);
+ }
+ }
+ );
}
- }
- return true;
- }
-
- _updateFee(asset_id, trxInfo, onChange, account = this.props.account) {
- if (!account) return null;
-
- let feeID = asset_id || this.state.fee_asset_id;
- this._getFees(this.state.assets, account, trxInfo);
- const options = {
- ...trxInfo,
- accountID: account.get("id"),
- feeID
- };
- if (
- JSON.stringify(this.state.last_fee_check_params) !==
- JSON.stringify(options)
- ) {
- checkFeeStatusAsync(options)
- .then(({fee, hasPoolBalance}) => {
- this.setState({
- fee_amount: fee.getAmount({real: true}),
- fee_asset_id: fee.asset_id,
- error: !hasPoolBalance,
- last_fee_check_params: options
- });
- if (onChange) {
- onChange(fee);
+ return {
+ fee,
+ hasPoolBalance
+ };
+ } catch (err) {
+ if (setState) {
+ this.setState({
+ calculatedFeeAmount: 0,
+ error: {
+ key: "unknown"
}
- this.setState({
- assets: this._getAvailableAssets(account),
- fee_amount: fee.getAmount({real: true}),
- fee_asset_id: fee.asset_id
- });
- })
- .catch(err => {
- console.warn(err);
});
+ }
+ console.error(err);
+ throw err;
}
}
- componentWillReceiveProps(np, ns) {
- const {fee_amount, fee_asset_id} = this.state;
- const trxInfoChanged = !this.__are_equal_shallow(
- np.trxInfo,
- this.props.trxInfo
- );
- const account_changed =
+ shouldComponentUpdate(np, ns) {
+ const accountChanged =
np.account &&
this.props.account &&
np.account.get("id") !== this.props.account.get("id");
- const needsFeeCalculation =
- trxInfoChanged || !fee_amount || account_changed;
- if (needsFeeCalculation) {
- this._updateFee(fee_asset_id, np.trxInfo, np.onChange, np.account);
+ const transactionChanged =
+ JSON.stringify(np.transaction) !==
+ JSON.stringify(this.props.transaction);
+ if (ns.assets) {
+ if (!this.state.assets) {
+ return true;
+ }
+ if (ns.assets.length !== this.state.assets.length) {
+ return true;
+ }
+ }
+ if (ns.feeAsset) {
+ if (!this.state.feeAsset) {
+ return true;
+ }
+ if (ns.feeAsset.get("id") !== this.state.feeAsset.get("id")) {
+ return true;
+ }
}
+ return (
+ accountChanged ||
+ transactionChanged ||
+ ns.calculatedFeeAmount !== this.state.calculatedFeeAmount ||
+ ns.assetsLoading !== this.state.assetsLoading ||
+ ns.isModalVisible !== this.state.isModalVisible ||
+ ns.error !== this.state.error
+ );
}
_getAsset() {
- const {assets, fee_asset_id} = this.state;
- return ChainStore.getAsset(
- fee_asset_id
- ? fee_asset_id
- : assets.length === 1
- ? assets[0]
- : "1.3.0"
- );
+ const {assets, feeAsset} = this.state;
+ return feeAsset
+ ? feeAsset
+ : assets && assets.length > 0
+ ? assets[0]
+ : null;
}
- _getAvailableAssets(account = this.props.account) {
- if (this.state.assets_fetched && this.state.assets.length > 0) {
+ _getSelectableAssets() {
+ return this.state.assets
+ ? this.state.assets
+ : [this._getAsset().get("symbol")];
+ }
+
+ async _syncAvailableAssets(opened, account = this.props.account) {
+ if (this.state.assets) {
return this.state.assets;
}
- let fee_asset_types = [];
- if (!(account && account.get("balances"))) {
- return fee_asset_types;
- }
- const account_balances = account.get("balances").toJS();
- fee_asset_types = Object.keys(account_balances).sort(utils.sortID);
- for (let key in account_balances) {
- let balanceObject = ChainStore.getObject(account_balances[key]);
- if (balanceObject && balanceObject.get("balance") === 0) {
- if (fee_asset_types.includes(key)) {
- fee_asset_types.splice(fee_asset_types.indexOf(key), 1);
- }
+ this.setState({
+ assetsLoading: true
+ });
+ let possibleAssets = [this._getAsset().get("id")];
+ const accountBalances = account.get("balances").toJS();
+ const sortedKeys = Object.keys(accountBalances).sort(utils.sortID);
+ for (let i = 0, key; (key = sortedKeys[i]); i++) {
+ const balanceObject = await FetchChain(
+ "getObject",
+ accountBalances[key]
+ );
+ const requiredForFee = await this._calculateFee(key);
+ if (
+ balanceObject &&
+ balanceObject.get("balance") >=
+ requiredForFee.fee.getAmount() &&
+ !possibleAssets.includes(key)
+ ) {
+ possibleAssets.push(key);
+ possibleAssets = possibleAssets.sort(utils.sortID);
+ this.setState({
+ assets: possibleAssets
+ });
}
}
this.setState({
- balances: account_balances,
- assets: fee_asset_types,
- assets_fetched: true
+ assetsLoading: false
});
- this._updateFee(
- this.state.fee_asset_id,
- this.props.trxInfo,
- this.props.onChange
- );
- return fee_asset_types;
}
componentDidMount() {
- this.onAssetChange(this.state.fee_asset_id);
+ this._calculateFee();
+ }
+
+ componentDidUpdate(prevProps) {
+ const {calculatedFeeAmount} = this.state;
+ const accountChanged =
+ this.props.account &&
+ prevProps.account.get("id") !== this.props.account.get("id");
+ const transactionChanged =
+ JSON.stringify(prevProps.transaction) !==
+ JSON.stringify(this.props.transaction);
+ const noFeeSetYet = !calculatedFeeAmount;
+ if (accountChanged) {
+ this.setState({assets: null});
+ }
+ if (transactionChanged || accountChanged || noFeeSetYet) {
+ this._calculateFee();
+ }
}
- onAssetChange(selected_asset) {
- this.setState({fee_asset_id: selected_asset});
- this._updateFee(
- selected_asset,
- this.props.trxInfo,
- this.props.onChange
+ componentWillReceiveProps(np, ns) {
+ // don't do async loading in componentWillReceiveProps
+ }
+
+ async onAssetChange(selectedAssetId) {
+ const asset = await FetchChain("getAsset", selectedAssetId);
+ this.setState(
+ {
+ feeAsset: asset
+ },
+ this._calculateFee.bind(this)
);
}
render() {
const currentAsset = this._getAsset();
- const assets =
- this.state.assets.length > 0
- ? this.state.assets
- : [currentAsset.get("id") || "1.3.0"];
-
- let value = this.state.error
- ? counterpart.translate("transfer.errors.insufficient")
- : this.state.fee_amount;
+ // noPoolBalanceShort
+ let feeInputString = this.state.error
+ ? counterpart.translate("transfer.errors." + this.state.error.key)
+ : this.state.calculatedFeeAmount;
const label = this.props.label ? (
{counterpart.translate(this.props.label)}
+ {this.state.error &&
+ this.state.error.tooltip && (
+
+
+
+ )}
) : null;
- const canChangeFeeParams =
- !this.props.selectDisabled && this.props.account;
+ const canChangeFeeParams = !this.props.disabled && !!this.props.account;
const changeDefaultButton = (
);
+ const selectableAssets = this._getSelectableAssets();
+
return (
-
({
- asset,
- fee: this.state.fees[asset]
- }))}
- displayFees={true}
- forceDefault={false}
- current_asset={this.state.fee_asset_id}
- onChange={this.onAssetChange.bind(this)}
- close={() => {
- this.setState({isModalVisible: false});
- }}
- />
+ {this.state.isModalVisible && (
+ ({
+ //asset,
+ //fee: this.state.fees[asset]
+ //}))
+ }
+ displayFees={true}
+ forceDefault={false}
+ current_asset={currentAsset.get("id")}
+ onChange={this.onAssetChange.bind(this)}
+ close={() => {
+ this.setState({isModalVisible: false});
+ }}
+ />
+ )}
);
}
@@ -284,34 +344,13 @@ class FeeAssetSelector extends React.Component {
}
}
-FeeAssetSelector.propTypes = {
- // a translation key for the input label
- label: PropTypes.string,
- // account which pays fee
- account: PropTypes.any,
- // handler for changed Fee (asset, or amount)
- onChange: PropTypes.func,
- tabIndex: PropTypes.number,
- selectDisabled: PropTypes.bool,
- settings: PropTypes.any,
- // Object wih data required for fee calculation
- trxInfo: PropTypes.any
-};
-
-FeeAssetSelector.defaultProps = {
- disabled: true,
- tabIndex: 0,
- selectDisabled: false,
- label: "transfer.fee",
- account: null,
- trxInfo: {
- type: "transfer",
- options: null,
- data: {}
- }
-};
+FeeAssetSelector = debounceRender(FeeAssetSelector, 150, {
+ leading: false
+});
-FeeAssetSelector = AssetWrapper(FeeAssetSelector);
+FeeAssetSelector = AssetWrapper(FeeAssetSelector, {
+ propNames: ["defaultFeeAsset"]
+});
export default connect(
FeeAssetSelector,
@@ -319,9 +358,11 @@ export default connect(
listenTo() {
return [SettingsStore];
},
- getProps(props) {
+ getProps() {
return {
- settings: SettingsStore.getState().settings
+ defaultFeeAsset:
+ SettingsStore.getState().settings.get("fee_asset") ||
+ "1.3.0"
};
}
}
diff --git a/app/lib/common/trxHelper.js b/app/lib/common/trxHelper.js
index bad5142ea6..eea5df8e95 100644
--- a/app/lib/common/trxHelper.js
+++ b/app/lib/common/trxHelper.js
@@ -13,7 +13,11 @@ function estimateFeeAsync(type, options = null, data = {}) {
return new Promise((res, rej) => {
FetchChain("getObject", "2.0.0")
.then(obj => {
- res(estimateFee(type, options, obj, data));
+ try {
+ res(estimateFee(type, options, obj, data));
+ } catch (err) {
+ rej(err);
+ }
})
.catch(rej);
});
@@ -25,17 +29,19 @@ function checkFeePoolAsync({
options = null,
data
} = {}) {
- return new Promise(res => {
+ return new Promise((res, rej) => {
if (assetID === "1.3.0") {
res(true);
} else {
Promise.all([
estimateFeeAsync(type, options, data),
FetchChain("getObject", assetID.replace(/^1\./, "2."))
- ]).then(result => {
- const [fee, dynamicObject] = result;
- res(parseInt(dynamicObject.get("fee_pool"), 10) >= fee);
- });
+ ])
+ .then(result => {
+ const [fee, dynamicObject] = result;
+ res(parseInt(dynamicObject.get("fee_pool"), 10) >= fee);
+ })
+ .catch(rej);
}
});
}
@@ -192,9 +198,9 @@ function checkFeeStatusAsync({
}, feeStatusTTL);
});
})
- .catch(() => {
+ .catch(err => {
asyncCache[key].queue.forEach(promise => {
- promise.rej();
+ promise.rej(err);
});
});
});