aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/components
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-01-28 17:29:15 +0800
committerFabio Berger <me@fabioberger.com>2018-01-28 17:29:15 +0800
commitdd9f5adc2e771f3602461ae708d44536f146b902 (patch)
treef5d11f3bc288a9711af4ff3c5091f78a8ccc8eb4 /packages/website/ts/components
parent748d805a32ad7a1ee5f5a212d383b95460d3d828 (diff)
downloaddexon-sol-tools-dd9f5adc2e771f3602461ae708d44536f146b902.tar.gz
dexon-sol-tools-dd9f5adc2e771f3602461ae708d44536f146b902.tar.zst
dexon-sol-tools-dd9f5adc2e771f3602461ae708d44536f146b902.zip
Initial Ledger support implementation
Diffstat (limited to 'packages/website/ts/components')
-rw-r--r--packages/website/ts/components/dialogs/ledger_config_dialog.tsx46
-rw-r--r--packages/website/ts/components/dropdowns/network_drop_down.tsx40
-rw-r--r--packages/website/ts/components/portal.tsx35
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx149
-rw-r--r--packages/website/ts/components/top_bar/provider_picker.tsx88
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx (renamed from packages/website/ts/components/top_bar.tsx)52
-rw-r--r--packages/website/ts/components/top_bar/top_bar_menu_item.tsx (renamed from packages/website/ts/components/top_bar_menu_item.tsx)0
-rw-r--r--packages/website/ts/components/ui/drop_down.tsx (renamed from packages/website/ts/components/ui/drop_down_menu_item.tsx)54
8 files changed, 419 insertions, 45 deletions
diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
index 60db93c52..aff3f67b1 100644
--- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
+++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
@@ -7,8 +7,10 @@ import TextField from 'material-ui/TextField';
import * as React from 'react';
import ReactTooltip = require('react-tooltip');
import { Blockchain } from 'ts/blockchain';
+import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down';
import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button';
import { Dispatcher } from 'ts/redux/dispatcher';
+import { ProviderType } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
@@ -27,27 +29,30 @@ interface LedgerConfigDialogProps {
dispatcher: Dispatcher;
blockchain: Blockchain;
networkId: number;
+ providerType: ProviderType;
}
interface LedgerConfigDialogState {
- didConnectFail: boolean;
+ connectionErrMsg: string;
stepIndex: LedgerSteps;
userAddresses: string[];
addressBalances: BigNumber[];
derivationPath: string;
derivationErrMsg: string;
+ preferredNetworkId: number;
}
export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> {
constructor(props: LedgerConfigDialogProps) {
super(props);
this.state = {
- didConnectFail: false,
+ connectionErrMsg: '',
stepIndex: LedgerSteps.CONNECT,
userAddresses: [],
addressBalances: [],
derivationPath: configs.DEFAULT_DERIVATION_PATH,
derivationErrMsg: '',
+ preferredNetworkId: props.networkId,
};
}
public render() {
@@ -77,7 +82,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
return (
<div>
<div className="h4 pt3">Follow these instructions before proceeding:</div>
- <ol>
+ <ol className="mb0">
<li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li>
<li className="pb1">Verify that Browser Support is enabled in Settings</li>
<li className="pb1">
@@ -86,7 +91,15 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
Firmware >1.2
</a>
</li>
+ <li>Choose your desired network:</li>
</ol>
+ <div className="pb2">
+ <NetworkDropDown
+ updateSelectedNetwork={this._onSelectedNetworkUpdated.bind(this)}
+ selectedNetworkId={this.state.preferredNetworkId}
+ avialableNetworkIds={[1, 42]}
+ />
+ </div>
<div className="center pb3">
<LifeCycleRaisedButton
isPrimary={true}
@@ -95,9 +108,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
labelComplete="Connected!"
onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)}
/>
- {this.state.didConnectFail && (
+ {!_.isEmpty(this.state.connectionErrMsg) && (
<div className="pt2 left-align" style={{ color: colors.red200 }}>
- Failed to connect. Follow the instructions and try again.
+ {this.state.connectionErrMsg}
</div>
)}
</div>
@@ -172,7 +185,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
}
private _onClose() {
this.setState({
- didConnectFail: false,
+ connectionErrMsg: '',
});
const isOpen = false;
this.props.toggleDialogFn(isOpen);
@@ -184,6 +197,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
this.props.dispatcher.updateUserAddress(selectedAddress);
this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
+ this.props.blockchain.fetchTokenInformationAsync(); // fire and forget
this.props.dispatcher.updateUserEtherBalance(selectAddressBalance);
this.setState({
stepIndex: LedgerSteps.CONNECT,
@@ -219,7 +233,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
} catch (err) {
utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`);
this.setState({
- didConnectFail: true,
+ connectionErrMsg: 'Failed to connect. Follow the instructions and try again.',
});
return false;
}
@@ -241,6 +255,19 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
});
}
private async _onConnectLedgerClickAsync() {
+ const isU2FSupported = await utils.isU2FSupportedAsync();
+ if (!isU2FSupported) {
+ utils.consoleLog(`U2F not supported in this browser`);
+ this.setState({
+ connectionErrMsg: 'U2F not supported by this browser. Try using Chrome.',
+ });
+ return false;
+ }
+
+ if (this.props.providerType !== ProviderType.Ledger) {
+ await this.props.blockchain.updateProviderToLedgerAsync(this.state.preferredNetworkId);
+ }
+
const didSucceed = await this._fetchAddressesAndBalancesAsync();
if (didSucceed) {
this.setState({
@@ -258,4 +285,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
}
return userAddresses;
}
+ private _onSelectedNetworkUpdated(e: any, index: number, networkId: number) {
+ this.setState({
+ preferredNetworkId: networkId,
+ });
+ }
}
diff --git a/packages/website/ts/components/dropdowns/network_drop_down.tsx b/packages/website/ts/components/dropdowns/network_drop_down.tsx
new file mode 100644
index 000000000..28ec28ed5
--- /dev/null
+++ b/packages/website/ts/components/dropdowns/network_drop_down.tsx
@@ -0,0 +1,40 @@
+import * as _ from 'lodash';
+import DropDownMenu from 'material-ui/DropDownMenu';
+import MenuItem from 'material-ui/MenuItem';
+import * as React from 'react';
+import { constants } from 'ts/utils/constants';
+
+interface NetworkDropDownProps {
+ updateSelectedNetwork: (e: any, index: number, value: number) => void;
+ selectedNetworkId: number;
+ avialableNetworkIds: number[];
+}
+
+interface NetworkDropDownState {}
+
+export class NetworkDropDown extends React.Component<NetworkDropDownProps, NetworkDropDownState> {
+ public render() {
+ return (
+ <div className="mx-auto" style={{ width: 120 }}>
+ <DropDownMenu value={this.props.selectedNetworkId} onChange={this.props.updateSelectedNetwork}>
+ {this._renderDropDownItems()}
+ </DropDownMenu>
+ </div>
+ );
+ }
+ private _renderDropDownItems() {
+ const items = _.map(this.props.avialableNetworkIds, networkId => {
+ const networkName = constants.NETWORK_NAME_BY_ID[networkId];
+ const primaryText = (
+ <div className="flex">
+ <div className="pr1" style={{ width: 14, paddingTop: 2 }}>
+ <img src={`/images/network_icons/${networkName.toLowerCase()}.png`} style={{ width: 14 }} />
+ </div>
+ <div>{networkName}</div>
+ </div>
+ );
+ return <MenuItem key={networkId} value={networkId} primaryText={primaryText} />;
+ });
+ return items;
+ }
+}
diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx
index e2e28e8b6..5975569c4 100644
--- a/packages/website/ts/components/portal.tsx
+++ b/packages/website/ts/components/portal.tsx
@@ -6,6 +6,7 @@ import * as DocumentTitle from 'react-document-title';
import { Route, Switch } from 'react-router-dom';
import { Blockchain } from 'ts/blockchain';
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
+import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog';
import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog';
import { WrappedEthSectionNoticeDialog } from 'ts/components/dialogs/wrapped_eth_section_notice_dialog';
import { EthWrappers } from 'ts/components/eth_wrappers';
@@ -13,19 +14,21 @@ import { FillOrder } from 'ts/components/fill_order';
import { Footer } from 'ts/components/footer';
import { PortalMenu } from 'ts/components/portal_menu';
import { TokenBalances } from 'ts/components/token_balances';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { FlashMessage } from 'ts/components/ui/flash_message';
import { Loading } from 'ts/components/ui/loading';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
import { localStorage } from 'ts/local_storage/local_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
+import { State } from 'ts/redux/reducer';
import { orderSchema } from 'ts/schemas/order_schema';
import { SchemaValidator } from 'ts/schemas/validator';
import {
BlockchainErrs,
HashData,
Order,
+ ProviderType,
ScreenWidths,
Token,
TokenByAddress,
@@ -46,9 +49,11 @@ export interface PortalAllProps {
blockchainIsLoaded: boolean;
dispatcher: Dispatcher;
hashData: HashData;
+ injectedProviderName: string;
networkId: number;
nodeVersion: string;
orderFillAmount: BigNumber;
+ providerType: ProviderType;
screenWidth: ScreenWidths;
tokenByAddress: TokenByAddress;
tokenStateByAddress: TokenStateByAddress;
@@ -67,6 +72,7 @@ interface PortalAllState {
prevPathname: string;
isDisclaimerDialogOpen: boolean;
isWethNoticeDialogOpen: boolean;
+ isLedgerDialogOpen: boolean;
}
export class Portal extends React.Component<PortalAllProps, PortalAllState> {
@@ -96,6 +102,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
prevPathname: this.props.location.pathname,
isDisclaimerDialogOpen: !hasAcceptedDisclaimer,
isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances,
+ isLedgerDialogOpen: false,
};
}
public componentDidMount() {
@@ -127,8 +134,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress);
if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) {
const tokens = _.values(nextProps.tokenByAddress);
+ const trackedTokens = _.filter(tokens, t => t.isTracked);
// tslint:disable-next-line:no-floating-promises
- this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens);
+ this._updateBalanceAndAllowanceWithLoadingScreenAsync(trackedTokens);
}
this.setState({
prevUserAddress: nextProps.userAddress,
@@ -167,8 +175,14 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
<DocumentTitle title="0x Portal DApp" />
<TopBar
userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ injectedProviderName={this.props.injectedProviderName}
+ onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)}
+ dispatcher={this.props.dispatcher}
+ providerType={this.props.providerType}
blockchainIsLoaded={this.props.blockchainIsLoaded}
location={this.props.location}
+ blockchain={this._blockchain}
/>
<div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}>
<Paper className="mb3 mt2">
@@ -239,11 +253,26 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
/>
<FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
+ {this.props.blockchainIsLoaded && (
+ <LedgerConfigDialog
+ providerType={this.props.providerType}
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ dispatcher={this.props.dispatcher}
+ toggleDialogFn={this.onToggleLedgerDialog.bind(this)}
+ isOpen={this.state.isLedgerDialogOpen}
+ />
+ )}
</div>
- <Footer />
+ <Footer />;
</div>
);
}
+ public onToggleLedgerDialog() {
+ this.setState({
+ isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
+ });
+ }
private _renderEthWrapper() {
return (
<EthWrappers
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
new file mode 100644
index 000000000..c7b6a4743
--- /dev/null
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -0,0 +1,149 @@
+import * as _ from 'lodash';
+import Menu from 'material-ui/Menu';
+import MenuItem from 'material-ui/MenuItem';
+import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
+import RaisedButton from 'material-ui/RaisedButton';
+import * as React from 'react';
+import { Link } from 'react-router-dom';
+import { Blockchain } from 'ts/blockchain';
+import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
+import { DropDown } from 'ts/components/ui/drop_down';
+import { Identicon } from 'ts/components/ui/identicon';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { ProviderType } from 'ts/types';
+import { colors } from 'ts/utils/colors';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
+
+const IDENTICON_DIAMETER = 32;
+
+interface ProviderDisplayProps {
+ dispatcher: Dispatcher;
+ userAddress: string;
+ networkId: number;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ onToggleLedgerDialog: () => void;
+ blockchain: Blockchain;
+}
+
+interface ProviderDisplayState {}
+
+export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
+ public render() {
+ const isAddressAvailable = !_.isEmpty(this.props.userAddress);
+ const isExternallyInjectedProvider = ProviderType.Injected && this.props.injectedProviderName !== '0x Public';
+ const displayAddress = isAddressAvailable
+ ? utils.getAddressBeginAndEnd(this.props.userAddress)
+ : isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000';
+ // If the "injected" provider is our fallback public node, then we want to
+ // show the "connect a wallet" message instead of the providerName
+ const injectedProviderName = isExternallyInjectedProvider
+ ? this.props.injectedProviderName
+ : 'Connect a wallet';
+ const providerTitle =
+ this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
+ const hoverActiveNode = (
+ <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}>
+ <div>
+ <Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} />
+ </div>
+ <div style={{ marginLeft: 12, paddingTop: 1 }}>
+ <div style={{ fontSize: 12, color: '#FF7F00' }}>{providerTitle}</div>
+ <div style={{ fontSize: 14 }}>{displayAddress}</div>
+ </div>
+ <div style={{ borderLeft: '1px solid #E0E0E0', marginLeft: 17, paddingTop: 1 }} className="px2">
+ <i style={{ fontSize: 30, color: '#E0E0E0' }} className="zmdi zmdi zmdi-chevron-down" />
+ </div>
+ </div>
+ );
+ const hasInjectedProvider =
+ this.props.injectedProviderName !== '0x Public' && this.props.providerType === ProviderType.Injected;
+ const hasLedgerProvider = this.props.providerType === ProviderType.Ledger;
+ const horizontalPosition = hasInjectedProvider || hasLedgerProvider ? 'left' : 'middle';
+ return (
+ <div style={{ width: 'fit-content', height: 48, float: 'right' }}>
+ <DropDown
+ hoverActiveNode={hoverActiveNode}
+ popoverContent={this.renderPopoverContent(hasInjectedProvider, hasLedgerProvider)}
+ anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }}
+ targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }}
+ zDepth={1}
+ />
+ </div>
+ );
+ }
+ public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean) {
+ if (hasInjectedProvider || hasLedgerProvider) {
+ return (
+ <ProviderPicker
+ dispatcher={this.props.dispatcher}
+ networkId={this.props.networkId}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ blockchain={this.props.blockchain}
+ />
+ );
+ } else {
+ // Nothing to connect to, show install/info popover
+ return (
+ <div className="px2" style={{ maxWidth: 420 }}>
+ <div className="center h4 py2" style={{ color: colors.grey700 }}>
+ Choose a wallet:
+ </div>
+ <div className="flex pb3">
+ <div className="center px2">
+ <div style={{ color: colors.darkGrey }}>Install a browser wallet</div>
+ <div className="py2">
+ <img src="/images/metamask_or_parity.png" width="135" />
+ </div>
+ <div>
+ Use{' '}
+ <a
+ href={constants.URL_METAMASK_CHROME_STORE}
+ target="_blank"
+ style={{ color: colors.lightBlueA700 }}
+ >
+ Metamask
+ </a>{' '}
+ or{' '}
+ <a
+ href={constants.URL_PARITY_CHROME_STORE}
+ target="_blank"
+ style={{ color: colors.lightBlueA700 }}
+ >
+ Parity Signer
+ </a>
+ </div>
+ </div>
+ <div>
+ <div
+ className="pl1 ml1"
+ style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }}
+ />
+ <div className="py1">or</div>
+ <div
+ className="pl1 ml1"
+ style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }}
+ />
+ </div>
+ <div className="px2 center">
+ <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div>
+ <div style={{ paddingTop: 21, paddingBottom: 29 }}>
+ <img src="/images/ledger_icon.png" style={{ width: 80 }} />
+ </div>
+ <div>
+ <RaisedButton
+ style={{ width: '100%' }}
+ label="Use Ledger"
+ onClick={this.props.onToggleLedgerDialog}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ }
+}
diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx
new file mode 100644
index 000000000..ca98d8d05
--- /dev/null
+++ b/packages/website/ts/components/top_bar/provider_picker.tsx
@@ -0,0 +1,88 @@
+import * as _ from 'lodash';
+import Menu from 'material-ui/Menu';
+import MenuItem from 'material-ui/MenuItem';
+import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
+import * as React from 'react';
+import { Link } from 'react-router-dom';
+import { Blockchain } from 'ts/blockchain';
+import { DropDown } from 'ts/components/ui/drop_down';
+import { Identicon } from 'ts/components/ui/identicon';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { ProviderType } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
+
+const IDENTICON_DIAMETER = 32;
+const SELECTED_BG_COLOR = '#F7F7F7';
+
+interface ProviderPickerProps {
+ networkId: number;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ onToggleLedgerDialog: () => void;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+}
+
+interface ProviderPickerState {}
+
+export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> {
+ public render() {
+ const isLedgerSelected = this.props.providerType === ProviderType.Ledger;
+ const menuStyle = {
+ padding: 10,
+ paddingTop: 15,
+ paddingBottom: 15,
+ };
+ const injectedLabel = (
+ <div className="flex">
+ <div>{this.props.injectedProviderName}</div>
+ {this._renderNetwork()}
+ </div>
+ );
+ // Show dropdown with two options
+ return (
+ <div style={{ width: 225, overflow: 'hidden' }}>
+ <RadioButtonGroup
+ name="provider"
+ defaultSelected={this.props.providerType}
+ onChange={this._onProviderRadioChanged.bind(this)}
+ >
+ <RadioButton
+ style={{ ...menuStyle, backgroundColor: !isLedgerSelected && SELECTED_BG_COLOR }}
+ value={ProviderType.Injected}
+ label={injectedLabel}
+ />
+ <RadioButton
+ style={{ ...menuStyle, backgroundColor: isLedgerSelected && SELECTED_BG_COLOR }}
+ value={ProviderType.Ledger}
+ label="Ledger Nano S"
+ />
+ </RadioButtonGroup>
+ </div>
+ );
+ }
+ private _renderNetwork() {
+ const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId];
+ return (
+ <div className="flex">
+ <div className="pr1 relative" style={{ width: 14, paddingLeft: 14 }}>
+ <img
+ src={`/images/network_icons/${networkName.toLowerCase()}.png`}
+ className="absolute"
+ style={{ top: 4, width: 14 }}
+ />
+ </div>
+ <div style={{ color: '#BBBBBB' }}>{networkName}</div>
+ </div>
+ );
+ }
+ private _onProviderRadioChanged(e: any, value: string) {
+ if (value === ProviderType.Ledger) {
+ this.props.onToggleLedgerDialog();
+ } else {
+ // Fire and forget
+ this.props.blockchain.updateProviderToInjectedAsync();
+ }
+ }
+}
diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index 11d3e7cc2..652da5435 100644
--- a/packages/website/ts/components/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -1,21 +1,32 @@
import * as _ from 'lodash';
import Drawer from 'material-ui/Drawer';
+import Menu from 'material-ui/Menu';
import MenuItem from 'material-ui/MenuItem';
import * as React from 'react';
import { Link } from 'react-router-dom';
import ReactTooltip = require('react-tooltip');
+import { Blockchain } from 'ts/blockchain';
import { PortalMenu } from 'ts/components/portal_menu';
-import { TopBarMenuItem } from 'ts/components/top_bar_menu_item';
-import { DropDownMenuItem } from 'ts/components/ui/drop_down_menu_item';
+import { ProviderDisplay } from 'ts/components/top_bar/provider_display';
+import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item';
+import { DropDown } from 'ts/components/ui/drop_down';
import { Identicon } from 'ts/components/ui/identicon';
import { DocsInfo } from 'ts/pages/documentation/docs_info';
import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu';
-import { DocsMenu, MenuSubsectionsBySection, Styles, WebsitePaths } from 'ts/types';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { DocsMenu, MenuSubsectionsBySection, ProviderType, Styles, TypeDocNode, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
+import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
interface TopBarProps {
userAddress?: string;
+ networkId?: number;
+ injectedProviderName?: string;
+ providerType?: ProviderType;
+ onToggleLedgerDialog?: () => void;
+ blockchain?: Blockchain;
+ dispatcher?: Dispatcher;
blockchainIsLoaded: boolean;
location: Location;
docsVersion?: string;
@@ -125,6 +136,15 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
cursor: 'pointer',
paddingTop: 16,
};
+ const hoverActiveNode = (
+ <div className="flex relative" style={{ color: menuIconStyle.color }}>
+ <div style={{ paddingRight: 10 }}>Developers</div>
+ <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
+ <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} />
+ </div>
+ </div>
+ );
+ const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
return (
<div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1">
<div className={parentClassNames}>
@@ -138,11 +158,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
{!this._isViewingPortal() && (
<div className={menuClasses}>
<div className="flex justify-between">
- <DropDownMenuItem
- title="Developers"
- subMenuItems={developerSectionMenuItems}
+ <DropDown
+ hoverActiveNode={hoverActiveNode}
+ popoverContent={popoverContent}
+ anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
+ targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
style={styles.menuItem}
- isNightVersion={isNightVersion}
/>
<TopBarMenuItem
title="Wiki"
@@ -167,10 +188,19 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</div>
</div>
)}
- {this.props.blockchainIsLoaded &&
- !_.isEmpty(this.props.userAddress) && (
- <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div>
- )}
+ {this.props.blockchainIsLoaded && (
+ <div className="col col-5">
+ <ProviderDisplay
+ dispatcher={this.props.dispatcher}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ blockchain={this.props.blockchain}
+ />
+ </div>
+ )}
<div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
<div style={menuIconStyle}>
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx
index 96ee86142..96ee86142 100644
--- a/packages/website/ts/components/top_bar_menu_item.tsx
+++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx
diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down.tsx
index a578fb4f9..31a67f0d7 100644
--- a/packages/website/ts/components/ui/drop_down_menu_item.tsx
+++ b/packages/website/ts/components/ui/drop_down.tsx
@@ -1,7 +1,8 @@
import * as _ from 'lodash';
import Menu from 'material-ui/Menu';
-import Popover from 'material-ui/Popover';
+import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
import * as React from 'react';
+import { MaterialUIPosition, Styles, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300;
@@ -9,28 +10,28 @@ const DEFAULT_STYLE = {
fontSize: 14,
};
-interface DropDownMenuItemProps {
- title: string;
- subMenuItems: React.ReactNode[];
+interface DropDownProps {
+ hoverActiveNode: React.ReactNode;
+ popoverContent: React.ReactNode;
+ anchorOrigin: MaterialUIPosition;
+ targetOrigin: MaterialUIPosition;
style?: React.CSSProperties;
- menuItemStyle?: React.CSSProperties;
- isNightVersion?: boolean;
+ zDepth?: number;
}
-interface DropDownMenuItemState {
+interface DropDownState {
isDropDownOpen: boolean;
anchorEl?: HTMLInputElement;
}
-export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> {
- public static defaultProps: Partial<DropDownMenuItemProps> = {
+export class DropDown extends React.Component<DropDownProps, DropDownState> {
+ public static defaultProps: Partial<DropDownProps> = {
style: DEFAULT_STYLE,
- menuItemStyle: DEFAULT_STYLE,
- isNightVersion: false,
+ zDepth: 1,
};
private _isHovering: boolean;
private _popoverCloseCheckIntervalId: number;
- constructor(props: DropDownMenuItemProps) {
+ constructor(props: DropDownProps) {
super(props);
this.state = {
isDropDownOpen: false,
@@ -44,30 +45,35 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro
public componentWillUnmount() {
window.clearInterval(this._popoverCloseCheckIntervalId);
}
+ public componentWillReceiveProps(nextProps: DropDownProps) {
+ // HACK: If the popoverContent is updated to a different dimension and the users
+ // mouse is no longer above it, the dropdown can enter an inconsistent state where
+ // it believes the user is still hovering over it. In order to remedy this, we
+ // call hoverOff whenever the dropdown receives updated props. This is a hack
+ // because it will effectively close the dropdown on any prop update, barring
+ // dropdowns from having dynamic content.
+ this._onHoverOff();
+ }
public render() {
- const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color;
return (
<div
- style={{ ...this.props.style, color: colorStyle }}
+ style={{ ...this.props.style, width: 'fit-content', height: '100%' }}
onMouseEnter={this._onHover.bind(this)}
onMouseLeave={this._onHoverOff.bind(this)}
>
- <div className="flex relative">
- <div style={{ paddingRight: 10 }}>{this.props.title}</div>
- <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
- <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} />
- </div>
- </div>
+ {this.props.hoverActiveNode}
<Popover
open={this.state.isDropDownOpen}
anchorEl={this.state.anchorEl}
- anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
- targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
+ anchorOrigin={this.props.anchorOrigin}
+ targetOrigin={this.props.targetOrigin}
onRequestClose={this._closePopover.bind(this)}
useLayerForClickAway={false}
+ animation={PopoverAnimationVertical}
+ zDepth={this.props.zDepth}
>
<div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}>
- <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu>
+ {this.props.popoverContent}
</div>
</Popover>
</div>
@@ -87,7 +93,7 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro
anchorEl: event.currentTarget,
});
}
- private _onHoverOff(event: React.FormEvent<HTMLInputElement>) {
+ private _onHoverOff() {
this._isHovering = false;
}
private _checkIfShouldClosePopover() {