import * as _ from 'lodash';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import GridList from 'material-ui/GridList/GridList';
import GridTile from 'material-ui/GridList/GridTile';
import {colors} from 'material-ui/styles';
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 {
AssetToken,
DialogConfigs,
Styles,
Token,
TokenByAddress,
TokenState,
TokenVisibility,
} from 'ts/types';
import {utils} from 'ts/utils/utils';
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 getTitle() {
switch (this.state.assetView) {
case AssetViews.ASSET_PICKER:
return 'Select token';
case AssetViews.NEW_TOKEN_FORM:
return 'Add an ERC20 token';
case AssetViews.CONFIRM_TRACK_TOKEN:
return 'Tracking confirmation';
default:
throw utils.spawnSwitchErr('assetView', this.state.assetView);
}
}
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 = _.assign({}, 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);
}
}