diff options
Diffstat (limited to 'packages/website/ts/components/wallet/wallet.tsx')
-rw-r--r-- | packages/website/ts/components/wallet/wallet.tsx | 123 |
1 files changed, 103 insertions, 20 deletions
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index d1ae38550..079c0e3b3 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -9,8 +9,11 @@ import { import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import FlatButton from 'material-ui/FlatButton'; +import FloatingActionButton from 'material-ui/FloatingActionButton'; import { List, ListItem } from 'material-ui/List'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; +import ContentAdd from 'material-ui/svg-icons/content/add'; +import ContentRemove from 'material-ui/svg-icons/content/remove'; import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward'; import NavigationArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward'; import Close from 'material-ui/svg-icons/navigation/close'; @@ -55,11 +58,14 @@ export interface WalletProps { injectedProviderName: string; providerType: ProviderType; onToggleLedgerDialog: () => void; + onAddToken: () => void; + onRemoveToken: () => void; } interface WalletState { trackedTokenStateByAddress: TokenStateByAddress; wrappedEtherDirection?: Side; + isHoveringSidebar: boolean; } interface AllowanceToggleConfig { @@ -94,6 +100,9 @@ const styles: Styles = { }, footerItemInnerDiv: { paddingLeft: 24, + borderTopColor: colors.walletBorder, + borderTopStyle: 'solid', + borderWidth: 1, }, borderedItem: { borderBottomColor: colors.walletBorder, @@ -114,7 +123,17 @@ const styles: Styles = { paddingTop: 8, paddingBottom: 8, }, - accessoryItemsContainer: { width: 150, right: 8 }, + accessoryItemsContainer: { + width: 150, + right: 8, + }, + bodyInnerDiv: { + padding: 0, + // TODO: make this completely responsive + maxHeight: 475, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + }, }; const ETHER_ICON_PATH = '/images/ether.png'; @@ -123,6 +142,7 @@ const ZRX_TOKEN_SYMBOL = 'ZRX'; const ETHER_SYMBOL = 'ETH'; const ICON_DIMENSION = 24; const TOKEN_AMOUNT_DISPLAY_PRECISION = 3; +const BODY_ITEM_KEY = 'BODY'; const HEADER_ITEM_KEY = 'HEADER'; const FOOTER_ITEM_KEY = 'FOOTER'; const DISCONNECTED_ITEM_KEY = 'DISCONNECTED'; @@ -139,6 +159,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { this.state = { trackedTokenStateByAddress: initialTrackedTokenStateByAddress, wrappedEtherDirection: undefined, + isHoveringSidebar: false, }; } public componentWillMount() { @@ -184,12 +205,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { return ( <List style={styles.list}> {isAddressAvailable - ? _.concat( - this._renderConnectedHeaderRows(), - this._renderEthRows(), - this._renderTokenRows(), - this._renderFooterRows(), - ) + ? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows()) : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())} </List> ); @@ -230,9 +246,61 @@ export class Wallet extends React.Component<WalletProps, WalletState> { /> ); } + private _renderBody() { + const bodyStyle: React.CSSProperties = { + ...styles.bodyInnerDiv, + overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden', + }; + return ( + <ListItem + key={BODY_ITEM_KEY} + innerDivStyle={bodyStyle} + onMouseEnter={this._onSidebarHover.bind(this)} + onMouseLeave={this._onSidebarHoverOff.bind(this)} + > + {this._renderEthRows()} + {this._renderTokenRows()} + </ListItem> + ); + } + private _onSidebarHover(event: React.FormEvent<HTMLInputElement>) { + this.setState({ + isHoveringSidebar: true, + }); + } + private _onSidebarHoverOff() { + this.setState({ + isHoveringSidebar: false, + }); + } private _renderFooterRows() { - const primaryText = '+ other tokens'; - return <ListItem key={FOOTER_ITEM_KEY} primaryText={primaryText} innerDivStyle={styles.footerItemInnerDiv} />; + return ( + <ListItem + key={FOOTER_ITEM_KEY} + primaryText={ + <div className="flex"> + <FloatingActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}> + <ContentAdd /> + </FloatingActionButton> + <FloatingActionButton mini={true} zDepth={0} className="px1" onClick={this.props.onRemoveToken}> + <ContentRemove /> + </FloatingActionButton> + <div + style={{ + paddingLeft: 10, + position: 'relative', + top: '50%', + transform: 'translateY(33%)', + }} + > + add/remove tokens + </div> + </div> + } + disabled={true} + innerDivStyle={styles.footerItemInnerDiv} + /> + ); } private _renderEthRows() { const primaryText = this._renderAmount( @@ -293,7 +361,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this)); } - private _renderTokenRow(token: Token) { + private _renderTokenRow(token: Token, index: number) { const tokenState = this.state.trackedTokenStateByAddress[token.address]; const tokenLink = sharedUtils.getEtherScanLinkIfExists( token.address, @@ -310,12 +378,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> { tokenState, }, }; + // if this is the last item in the list, do not render the border, it is rendered by the footer + const borderedStyle = index !== this.props.trackedTokens.length - 1 ? styles.borderedItem : {}; const shouldShowWrapEtherItem = !_.isUndefined(this.state.wrappedEtherDirection) && this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection; const style = shouldShowWrapEtherItem ? { ...walletItemStyles.focusedItem, ...styles.paddedItem } - : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem }; + : { ...styles.tokenItem, ...borderedStyle, ...styles.paddedItem }; const etherToken = this._getEthToken(); return ( <div key={token.address}> @@ -461,16 +531,16 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); balanceAndAllowanceTupleByAddress[tokenAddress] = balanceAndAllowanceTuple; } - const pricesByAddress = await this._getPricesByAddressAsync(tokenAddresses); + const priceByAddress = await this._getPriceByAddressAsync(tokenAddresses); const trackedTokenStateByAddress = _.reduce( tokenAddresses, (acc, address) => { const [balance, allowance] = balanceAndAllowanceTupleByAddress[address]; - const price = pricesByAddress[address]; + const priceIfExists = _.get(priceByAddress, address); acc[address] = { balance, allowance, - price, + price: priceIfExists, isLoaded: true, }; return acc; @@ -487,16 +557,29 @@ export class Wallet extends React.Component<WalletProps, WalletState> { private async _refetchTokenStateAsync(tokenAddress: string) { await this._fetchBalancesAndAllowancesAsync([tokenAddress]); } - private async _getPricesByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { + private async _getPriceByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { if (_.isEmpty(tokenAddresses)) { return {}; } + // for each input token address, search for the corresponding symbol in this.props.tokenByAddress, if it exists + // create a mapping from existing symbols -> address + const tokenAddressBySymbol: { [symbol: string]: string } = {}; + _.each(tokenAddresses, address => { + const tokenIfExists = _.get(this.props.tokenByAddress, address); + if (!_.isUndefined(tokenIfExists)) { + const symbol = tokenIfExists.symbol; + tokenAddressBySymbol[symbol] = address; + } + }); + const tokenSymbols = _.keys(tokenAddressBySymbol); try { - const websiteBackendPriceInfos = await backendClient.getPriceInfosAsync(tokenAddresses); - const addresses = _.map(websiteBackendPriceInfos, info => info.address); - const prices = _.map(websiteBackendPriceInfos, info => new BigNumber(info.price)); - const pricesByAddress = _.zipObject(addresses, prices); - return pricesByAddress; + const priceBySymbol = await backendClient.getPriceInfoAsync(tokenSymbols); + const priceByAddress = _.mapKeys(priceBySymbol, (value, symbol) => _.get(tokenAddressBySymbol, symbol)); + const result = _.mapValues(priceByAddress, price => { + const priceBigNumber = new BigNumber(price); + return priceBigNumber; + }); + return result; } catch (err) { return {}; } |