273 lines
9.9 KiB
TypeScript
273 lines
9.9 KiB
TypeScript
import * as _ from 'lodash';
|
|
import Dialog from 'material-ui/Dialog';
|
|
import FlatButton from 'material-ui/FlatButton';
|
|
import * as React from 'react';
|
|
import {Blockchain} from 'ts/blockchain';
|
|
import {NewTokenForm} from 'ts/components/generate_order/new_token_form';
|
|
import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation';
|
|
import {TokenIcon} from 'ts/components/ui/token_icon';
|
|
import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage';
|
|
import {Dispatcher} from 'ts/redux/dispatcher';
|
|
import {
|
|
DialogConfigs,
|
|
Token,
|
|
TokenByAddress,
|
|
TokenState,
|
|
TokenVisibility,
|
|
} from 'ts/types';
|
|
|
|
const TOKEN_ICON_DIMENSION = 100;
|
|
const TILE_DIMENSION = 146;
|
|
enum AssetViews {
|
|
ASSET_PICKER = 'ASSET_PICKER',
|
|
NEW_TOKEN_FORM = 'NEW_TOKEN_FORM',
|
|
CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN',
|
|
}
|
|
|
|
interface AssetPickerProps {
|
|
userAddress: string;
|
|
blockchain: Blockchain;
|
|
dispatcher: Dispatcher;
|
|
networkId: number;
|
|
isOpen: boolean;
|
|
currentTokenAddress: string;
|
|
onTokenChosen: (tokenAddress: string) => void;
|
|
tokenByAddress: TokenByAddress;
|
|
tokenVisibility?: TokenVisibility;
|
|
}
|
|
|
|
interface AssetPickerState {
|
|
assetView: AssetViews;
|
|
hoveredAddress: string | undefined;
|
|
chosenTrackTokenAddress: string;
|
|
isAddingTokenToTracked: boolean;
|
|
}
|
|
|
|
export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerState> {
|
|
public static defaultProps: Partial<AssetPickerProps> = {
|
|
tokenVisibility: TokenVisibility.ALL,
|
|
};
|
|
private _dialogConfigsByAssetView: {[assetView: string]: DialogConfigs};
|
|
constructor(props: AssetPickerProps) {
|
|
super(props);
|
|
this.state = {
|
|
assetView: AssetViews.ASSET_PICKER,
|
|
hoveredAddress: undefined,
|
|
chosenTrackTokenAddress: undefined,
|
|
isAddingTokenToTracked: false,
|
|
};
|
|
this._dialogConfigsByAssetView = {
|
|
[AssetViews.ASSET_PICKER]: {
|
|
title: 'Select token',
|
|
isModal: false,
|
|
actions: [],
|
|
},
|
|
[AssetViews.NEW_TOKEN_FORM]: {
|
|
title: 'Add an ERC20 token',
|
|
isModal: false,
|
|
actions: [],
|
|
},
|
|
[AssetViews.CONFIRM_TRACK_TOKEN]: {
|
|
title: 'Tracking confirmation',
|
|
isModal: true,
|
|
actions: [
|
|
<FlatButton
|
|
key="noTracking"
|
|
label="No"
|
|
onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)}
|
|
/>,
|
|
<FlatButton
|
|
key="yesTrack"
|
|
label="Yes"
|
|
onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)}
|
|
/>,
|
|
],
|
|
},
|
|
};
|
|
}
|
|
public render() {
|
|
const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView];
|
|
return (
|
|
<Dialog
|
|
title={dialogConfigs.title}
|
|
titleStyle={{fontWeight: 100}}
|
|
modal={dialogConfigs.isModal}
|
|
open={this.props.isOpen}
|
|
actions={dialogConfigs.actions}
|
|
onRequestClose={this._onCloseDialog.bind(this)}
|
|
>
|
|
{this.state.assetView === AssetViews.ASSET_PICKER &&
|
|
this._renderAssetPicker()
|
|
}
|
|
{this.state.assetView === AssetViews.NEW_TOKEN_FORM &&
|
|
<NewTokenForm
|
|
blockchain={this.props.blockchain}
|
|
onNewTokenSubmitted={this._onNewTokenSubmitted.bind(this)}
|
|
tokenByAddress={this.props.tokenByAddress}
|
|
/>
|
|
}
|
|
{this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN &&
|
|
this._renderConfirmTrackToken()
|
|
}
|
|
</Dialog>
|
|
);
|
|
}
|
|
private _renderConfirmTrackToken() {
|
|
const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress];
|
|
return (
|
|
<TrackTokenConfirmation
|
|
tokens={[token]}
|
|
tokenByAddress={this.props.tokenByAddress}
|
|
networkId={this.props.networkId}
|
|
isAddingTokenToTracked={this.state.isAddingTokenToTracked}
|
|
/>
|
|
);
|
|
}
|
|
private _renderAssetPicker() {
|
|
return (
|
|
<div
|
|
className="clearfix flex flex-wrap"
|
|
style={{overflowY: 'auto', maxWidth: 720, maxHeight: 356, marginBottom: 10}}
|
|
>
|
|
{this._renderGridTiles()}
|
|
</div>
|
|
);
|
|
}
|
|
private _renderGridTiles() {
|
|
let isHovered;
|
|
let tileStyles;
|
|
const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => {
|
|
if ((this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) ||
|
|
(this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked)) {
|
|
return null; // Skip
|
|
}
|
|
isHovered = this.state.hoveredAddress === address;
|
|
tileStyles = {
|
|
cursor: 'pointer',
|
|
opacity: isHovered ? 0.6 : 1,
|
|
};
|
|
return (
|
|
<div
|
|
key={address}
|
|
style={{width: TILE_DIMENSION, height: TILE_DIMENSION, ...tileStyles}}
|
|
className="p2 mx-auto"
|
|
onClick={this._onChooseToken.bind(this, address)}
|
|
onMouseEnter={this._onToggleHover.bind(this, address, true)}
|
|
onMouseLeave={this._onToggleHover.bind(this, address, false)}
|
|
>
|
|
<div className="p1 center">
|
|
<TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} />
|
|
</div>
|
|
<div className="center">{token.name}</div>
|
|
</div>
|
|
);
|
|
});
|
|
const otherTokenKey = 'otherToken';
|
|
isHovered = this.state.hoveredAddress === otherTokenKey;
|
|
tileStyles = {
|
|
cursor: 'pointer',
|
|
opacity: isHovered ? 0.6 : 1,
|
|
};
|
|
if (this.props.tokenVisibility !== TokenVisibility.TRACKED) {
|
|
gridTiles.push((
|
|
<div
|
|
key={otherTokenKey}
|
|
style={{width: TILE_DIMENSION, height: TILE_DIMENSION, ...tileStyles}}
|
|
className="p2 mx-auto"
|
|
onClick={this._onCustomAssetChosen.bind(this)}
|
|
onMouseEnter={this._onToggleHover.bind(this, otherTokenKey, true)}
|
|
onMouseLeave={this._onToggleHover.bind(this, otherTokenKey, false)}
|
|
>
|
|
<div className="p1 center">
|
|
<i
|
|
style={{fontSize: 105, paddingLeft: 1, paddingRight: 1}}
|
|
className="zmdi zmdi-plus-circle"
|
|
/>
|
|
</div>
|
|
<div className="center">Other ERC20 Token</div>
|
|
</div>
|
|
));
|
|
}
|
|
return gridTiles;
|
|
}
|
|
private _onToggleHover(address: string, isHovered: boolean) {
|
|
const hoveredAddress = isHovered ? address : undefined;
|
|
this.setState({
|
|
hoveredAddress,
|
|
});
|
|
}
|
|
private _onCloseDialog() {
|
|
this.setState({
|
|
assetView: AssetViews.ASSET_PICKER,
|
|
});
|
|
this.props.onTokenChosen(this.props.currentTokenAddress);
|
|
}
|
|
private _onChooseToken(tokenAddress: string) {
|
|
const token = this.props.tokenByAddress[tokenAddress];
|
|
if (token.isTracked) {
|
|
this.props.onTokenChosen(tokenAddress);
|
|
} else {
|
|
this.setState({
|
|
assetView: AssetViews.CONFIRM_TRACK_TOKEN,
|
|
chosenTrackTokenAddress: tokenAddress,
|
|
});
|
|
}
|
|
}
|
|
private _onCustomAssetChosen() {
|
|
this.setState({
|
|
assetView: AssetViews.NEW_TOKEN_FORM,
|
|
});
|
|
}
|
|
private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) {
|
|
this.props.dispatcher.updateTokenStateByAddress({
|
|
[newToken.address]: newTokenState,
|
|
});
|
|
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken);
|
|
this.props.dispatcher.addTokenToTokenByAddress(newToken);
|
|
this.setState({
|
|
assetView: AssetViews.ASSET_PICKER,
|
|
});
|
|
this.props.onTokenChosen(newToken.address);
|
|
}
|
|
private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) {
|
|
if (!didUserAcceptTracking) {
|
|
this.setState({
|
|
isAddingTokenToTracked: false,
|
|
assetView: AssetViews.ASSET_PICKER,
|
|
chosenTrackTokenAddress: undefined,
|
|
});
|
|
this._onCloseDialog();
|
|
return;
|
|
}
|
|
this.setState({
|
|
isAddingTokenToTracked: true,
|
|
});
|
|
const tokenAddress = this.state.chosenTrackTokenAddress;
|
|
const token = this.props.tokenByAddress[tokenAddress];
|
|
const newTokenEntry = {
|
|
...token,
|
|
};
|
|
|
|
newTokenEntry.isTracked = true;
|
|
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
|
|
|
|
const [
|
|
balance,
|
|
allowance,
|
|
] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address);
|
|
this.props.dispatcher.updateTokenStateByAddress({
|
|
[token.address]: {
|
|
balance,
|
|
allowance,
|
|
},
|
|
});
|
|
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
|
|
this.setState({
|
|
isAddingTokenToTracked: false,
|
|
assetView: AssetViews.ASSET_PICKER,
|
|
chosenTrackTokenAddress: undefined,
|
|
});
|
|
this.props.onTokenChosen(tokenAddress);
|
|
}
|
|
}
|