aboutsummaryrefslogtreecommitdiffstats
path: root/packages
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
parent748d805a32ad7a1ee5f5a212d383b95460d3d828 (diff)
downloaddexon-0x-contracts-dd9f5adc2e771f3602461ae708d44536f146b902.tar.gz
dexon-0x-contracts-dd9f5adc2e771f3602461ae708d44536f146b902.tar.zst
dexon-0x-contracts-dd9f5adc2e771f3602461ae708d44536f146b902.zip
Initial Ledger support implementation
Diffstat (limited to 'packages')
-rw-r--r--packages/website/package.json9
-rw-r--r--packages/website/public/images/ledger_icon.pngbin0 -> 4885 bytes
-rw-r--r--packages/website/public/images/metamask_or_parity.pngbin0 -> 48180 bytes
-rw-r--r--packages/website/public/images/network_icons/kovan.pngbin0 -> 244 bytes
-rw-r--r--packages/website/public/images/network_icons/mainnet.pngbin0 -> 205 bytes
-rw-r--r--packages/website/public/images/network_icons/rinkby.pngbin0 -> 126 bytes
-rw-r--r--packages/website/public/images/network_icons/ropsten.pngbin0 -> 251 bytes
-rw-r--r--packages/website/ts/blockchain.ts215
-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
-rw-r--r--packages/website/ts/containers/portal.tsx17
-rw-r--r--packages/website/ts/pages/about/about.tsx2
-rw-r--r--packages/website/ts/pages/documentation/documentation.tsx2
-rw-r--r--packages/website/ts/pages/faq/faq.tsx2
-rw-r--r--packages/website/ts/pages/landing/landing.tsx2
-rw-r--r--packages/website/ts/pages/not_found.tsx2
-rw-r--r--packages/website/ts/pages/wiki/wiki.tsx2
-rw-r--r--packages/website/ts/types.ts5
24 files changed, 559 insertions, 163 deletions
diff --git a/packages/website/package.json b/packages/website/package.json
index e6d2c5f03..38afa8ec1 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -8,9 +8,12 @@
"clean": "shx rm -f public/bundle*",
"lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'",
"dev": "webpack-dev-server --content-base public --https",
- "update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;",
- "deploy_staging": "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers",
- "deploy_live": "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers"
+ "update_contracts":
+ "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;",
+ "deploy_staging":
+ "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers",
+ "deploy_live":
+ "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers"
},
"config": {
"artifacts": "Mintable"
diff --git a/packages/website/public/images/ledger_icon.png b/packages/website/public/images/ledger_icon.png
new file mode 100644
index 000000000..29b8df08f
--- /dev/null
+++ b/packages/website/public/images/ledger_icon.png
Binary files differ
diff --git a/packages/website/public/images/metamask_or_parity.png b/packages/website/public/images/metamask_or_parity.png
new file mode 100644
index 000000000..1085a0120
--- /dev/null
+++ b/packages/website/public/images/metamask_or_parity.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/kovan.png b/packages/website/public/images/network_icons/kovan.png
new file mode 100644
index 000000000..f47a12e74
--- /dev/null
+++ b/packages/website/public/images/network_icons/kovan.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/mainnet.png b/packages/website/public/images/network_icons/mainnet.png
new file mode 100644
index 000000000..6693635d6
--- /dev/null
+++ b/packages/website/public/images/network_icons/mainnet.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/rinkby.png b/packages/website/public/images/network_icons/rinkby.png
new file mode 100644
index 000000000..f9ba18778
--- /dev/null
+++ b/packages/website/public/images/network_icons/rinkby.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/ropsten.png b/packages/website/public/images/network_icons/ropsten.png
new file mode 100644
index 000000000..894910b34
--- /dev/null
+++ b/packages/website/public/images/network_icons/ropsten.png
Binary files differ
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index 5530701c0..6fc56aecd 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -64,6 +64,7 @@ export class Blockchain {
private _exchangeAddress: string;
private _userAddress: string;
private _cachedProvider: Web3.Provider;
+ private _cachedProviderNetworkId: number;
private _ledgerSubprovider: LedgerWalletSubprovider;
private _zrxPollIntervalId: NodeJS.Timer;
private static async _onPageLoadAsync(): Promise<void> {
@@ -133,14 +134,14 @@ export class Blockchain {
} else if (this.networkId !== newNetworkId) {
this.networkId = newNetworkId;
this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError);
- await this._fetchTokenInformationAsync();
+ await this.fetchTokenInformationAsync();
await this._rehydrateStoreWithContractEvents();
}
}
public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) {
if (this._userAddress !== newUserAddress) {
this._userAddress = newUserAddress;
- await this._fetchTokenInformationAsync();
+ await this.fetchTokenInformationAsync();
await this._rehydrateStoreWithContractEvents();
}
}
@@ -180,63 +181,62 @@ export class Blockchain {
}
this._ledgerSubprovider.setPathIndex(pathIndex);
}
- public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) {
+ public async updateProviderToLedgerAsync(networkId: number) {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
- // Should actually be Web3.Provider|ProviderEngine union type but it causes issues
- // later on in the logic.
- let provider;
- switch (providerType) {
- case ProviderType.Ledger: {
- const isU2FSupported = await utils.isU2FSupportedAsync();
- if (!isU2FSupported) {
- throw new Error('Cannot update providerType to LEDGER without U2F support');
- }
- // Cache injected provider so that we can switch the user back to it easily
- this._cachedProvider = this._web3Wrapper.getProviderObj();
-
- this._dispatcher.updateUserAddress(''); // Clear old userAddress
-
- provider = new ProviderEngine();
- const ledgerWalletConfigs = {
- networkId: this.networkId,
- ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
- };
- this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
- provider.addProvider(this._ledgerSubprovider);
- provider.addProvider(new FilterSubprovider());
- const networkId = configs.IS_MAINNET_ENABLED
- ? constants.NETWORK_ID_MAINNET
- : constants.NETWORK_ID_TESTNET;
- provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
- provider.start();
- this._web3Wrapper.destroy();
- const shouldPollUserAddress = false;
- this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
- this._zeroEx.setProvider(provider, networkId);
- await this._postInstantiationOrUpdatingProviderZeroExAsync();
- break;
- }
+ const isU2FSupported = await utils.isU2FSupportedAsync();
+ if (!isU2FSupported) {
+ throw new Error('Cannot update providerType to LEDGER without U2F support');
+ }
- case ProviderType.Injected: {
- if (_.isUndefined(this._cachedProvider)) {
- return; // Going from injected to injected, so we noop
- }
- provider = this._cachedProvider;
- const shouldPollUserAddress = true;
- this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
- this._zeroEx.setProvider(provider, this.networkId);
- await this._postInstantiationOrUpdatingProviderZeroExAsync();
- delete this._ledgerSubprovider;
- delete this._cachedProvider;
- break;
- }
+ // Cache injected provider so that we can switch the user back to it easily
+ this._cachedProvider = this._web3Wrapper.getProviderObj();
+ this._cachedProviderNetworkId = this.networkId;
+
+ this._userAddress = '';
+ this._dispatcher.updateUserAddress(''); // Clear old userAddress
+
+ const provider = new ProviderEngine();
+ const ledgerWalletConfigs = {
+ networkId,
+ ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
+ };
+ this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
+ provider.addProvider(this._ledgerSubprovider);
+ provider.addProvider(new FilterSubprovider());
+ provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
+ provider.start();
+ this._web3Wrapper.destroy();
+ this.networkId = networkId;
+ this._dispatcher.updateNetworkId(this.networkId);
+ const shouldPollUserAddress = false;
+ this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
+ this._zeroEx.setProvider(provider, this.networkId);
+ await this._postInstantiationOrUpdatingProviderZeroExAsync();
+ this._dispatcher.updateProviderType(ProviderType.Ledger);
+ }
+ public async updateProviderToInjectedAsync() {
+ utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
- default:
- throw utils.spawnSwitchErr('providerType', providerType);
+ if (_.isUndefined(this._cachedProvider)) {
+ return; // Going from injected to injected, so we noop
}
+ const provider = this._cachedProvider;
+ this.networkId = this._cachedProviderNetworkId;
+ this._dispatcher.updateNetworkId(this.networkId);
- await this._fetchTokenInformationAsync();
+ this._web3Wrapper.destroy();
+ const shouldPollUserAddress = true;
+ this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
+
+ this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
+ this._dispatcher.updateUserAddress(this._userAddress);
+ this._zeroEx.setProvider(provider, this.networkId);
+ await this._postInstantiationOrUpdatingProviderZeroExAsync();
+ await this.fetchTokenInformationAsync();
+ this._dispatcher.updateProviderType(ProviderType.Injected);
+ delete this._ledgerSubprovider;
+ delete this._cachedProvider;
}
public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> {
utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid);
@@ -452,6 +452,7 @@ export class Blockchain {
return [balance, allowance];
}
public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) {
+ const err = new Error('show stopper');
const tokenStateByAddress: TokenStateByAddress = {};
for (const token of tokens) {
let balance = new BigNumber(0);
@@ -483,6 +484,60 @@ export class Blockchain {
this._web3Wrapper.destroy();
this._stopWatchingExchangeLogFillEvents();
}
+ public async fetchTokenInformationAsync() {
+ utils.assert(
+ !_.isUndefined(this.networkId),
+ 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node',
+ );
+
+ this._dispatcher.updateBlockchainIsLoaded(false);
+ // HACK: Without this timeout, the second call to dispatcher somehow causes blockchainIsLoaded
+ // to flicker... Need to debug further :(((())))
+ await new Promise(resolve => setTimeout(resolve, 100));
+ this._dispatcher.clearTokenByAddress();
+
+ const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
+
+ // HACK: This is a hack so that the loading spinner doesn't show up twice...
+ // Once for loading the blockchain, another for loading the userAddress
+ this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
+ if (!_.isEmpty(this._userAddress)) {
+ this._dispatcher.updateUserAddress(this._userAddress);
+ }
+
+ let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId);
+ const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
+ if (_.isUndefined(trackedTokensIfExists)) {
+ trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
+ const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
+ token.isTracked = true;
+ return token;
+ });
+ _.each(trackedTokensIfExists, token => {
+ trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token);
+ });
+ } else {
+ // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
+ _.each(trackedTokensIfExists, trackedToken => {
+ if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) {
+ tokenRegistryTokensByAddress[trackedToken.address].isTracked = true;
+ }
+ });
+ }
+ const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]);
+ this._dispatcher.updateTokenByAddress(allTokens);
+
+ // Get balance/allowance for tracked tokens
+ await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists);
+
+ const mostPopularTradingPairTokens: Token[] = [
+ _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }),
+ _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }),
+ ];
+ this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address);
+ this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address);
+ this._dispatcher.updateBlockchainIsLoaded(true);
+ }
private async _showEtherScanLinkAndAwaitTransactionMinedAsync(
txHash: string,
): Promise<TransactionReceiptWithDecodedLogs> {
@@ -690,60 +745,6 @@ export class Blockchain {
: constants.PROVIDER_NAME_PUBLIC;
this._dispatcher.updateInjectedProviderName(providerName);
}
- private async _fetchTokenInformationAsync() {
- utils.assert(
- !_.isUndefined(this.networkId),
- 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node',
- );
-
- this._dispatcher.updateBlockchainIsLoaded(false);
- this._dispatcher.clearTokenByAddress();
-
- const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
-
- // HACK: We need to fetch the userAddress here because otherwise we cannot save the
- // tracked tokens in localStorage under the users address nor fetch the token
- // balances and allowances and we need to do this in order not to trigger the blockchain
- // loading dialog to show up twice. First to load the contracts, and second to load the
- // balances and allowances.
- this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
- if (!_.isEmpty(this._userAddress)) {
- this._dispatcher.updateUserAddress(this._userAddress);
- }
-
- let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId);
- const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
- if (_.isUndefined(trackedTokensIfExists)) {
- trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
- const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
- token.isTracked = true;
- return token;
- });
- _.each(trackedTokensIfExists, token => {
- trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token);
- });
- } else {
- // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
- _.each(trackedTokensIfExists, trackedToken => {
- if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) {
- tokenRegistryTokensByAddress[trackedToken.address].isTracked = true;
- }
- });
- }
- const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]);
- this._dispatcher.updateTokenByAddress(allTokens);
-
- // Get balance/allowance for tracked tokens
- await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists);
-
- const mostPopularTradingPairTokens: Token[] = [
- _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }),
- _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }),
- ];
- this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address);
- this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address);
- this._dispatcher.updateBlockchainIsLoaded(true);
- }
private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> {
const c = await contract(artifact);
const providerObj = this._web3Wrapper.getProviderObj();
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() {
diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx
index f0247935b..2c515aac4 100644
--- a/packages/website/ts/containers/portal.tsx
+++ b/packages/website/ts/containers/portal.tsx
@@ -6,16 +6,27 @@ import { Dispatch } from 'redux';
import { Portal as PortalComponent, PortalAllProps as PortalComponentAllProps } from 'ts/components/portal';
import { Dispatcher } from 'ts/redux/dispatcher';
import { State } from 'ts/redux/reducer';
-import { BlockchainErrs, HashData, Order, ScreenWidths, Side, TokenByAddress, TokenStateByAddress } from 'ts/types';
+import {
+ BlockchainErrs,
+ HashData,
+ Order,
+ ProviderType,
+ ScreenWidths,
+ Side,
+ TokenByAddress,
+ TokenStateByAddress,
+} from 'ts/types';
import { constants } from 'ts/utils/constants';
interface ConnectedState {
blockchainErr: BlockchainErrs;
blockchainIsLoaded: boolean;
hashData: HashData;
+ injectedProviderName: string;
networkId: number;
nodeVersion: string;
orderFillAmount: BigNumber;
+ providerType: ProviderType;
tokenByAddress: TokenByAddress;
tokenStateByAddress: TokenStateByAddress;
userEtherBalance: BigNumber;
@@ -57,10 +68,12 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne
return {
blockchainErr: state.blockchainErr,
blockchainIsLoaded: state.blockchainIsLoaded,
+ hashData,
+ injectedProviderName: state.injectedProviderName,
networkId: state.networkId,
nodeVersion: state.nodeVersion,
orderFillAmount: state.orderFillAmount,
- hashData,
+ providerType: state.providerType,
screenWidth: state.screenWidth,
shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen,
tokenByAddress: state.tokenByAddress,
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
index c929673f5..0889e79f3 100644
--- a/packages/website/ts/pages/about/about.tsx
+++ b/packages/website/ts/pages/about/about.tsx
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Profile } from 'ts/pages/about/profile';
import { ProfileInfo, Styles } from 'ts/types';
import { colors } from 'ts/utils/colors';
diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx
index 2315847ad..13a85c301 100644
--- a/packages/website/ts/pages/documentation/documentation.tsx
+++ b/packages/website/ts/pages/documentation/documentation.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import DocumentTitle = require('react-document-title');
import { scroller } from 'react-scroll';
import semverSort = require('semver-sort');
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Badge } from 'ts/components/ui/badge';
import { Comment } from 'ts/pages/documentation/comment';
import { DocsInfo } from 'ts/pages/documentation/docs_info';
diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx
index b4b5214a2..0a7eecc2d 100644
--- a/packages/website/ts/pages/faq/faq.tsx
+++ b/packages/website/ts/pages/faq/faq.tsx
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Question } from 'ts/pages/faq/question';
import { FAQQuestion, FAQSection, Styles, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx
index ca76497df..b0c622fb4 100644
--- a/packages/website/ts/pages/landing/landing.tsx
+++ b/packages/website/ts/pages/landing/landing.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import DocumentTitle = require('react-document-title');
import { Link } from 'react-router-dom';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { ScreenWidths, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx
index ff277c377..0a6ec071c 100644
--- a/packages/website/ts/pages/not_found.tsx
+++ b/packages/website/ts/pages/not_found.tsx
@@ -1,7 +1,7 @@
import * as _ from 'lodash';
import * as React from 'react';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Styles } from 'ts/types';
export interface NotFoundProps {
diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx
index d065614ba..a3cf72450 100644
--- a/packages/website/ts/pages/wiki/wiki.tsx
+++ b/packages/website/ts/pages/wiki/wiki.tsx
@@ -3,7 +3,7 @@ import CircularProgress from 'material-ui/CircularProgress';
import * as React from 'react';
import DocumentTitle = require('react-document-title');
import { scroller } from 'react-scroll';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { MarkdownSection } from 'ts/pages/shared/markdown_section';
import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu';
import { SectionHeader } from 'ts/pages/shared/section_header';
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index f873f95fa..aec8f7e1e 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -678,4 +678,9 @@ export enum SmartContractDocSections {
ZRXToken = 'ZRXToken',
}
+export interface MaterialUIPosition {
+ vertical: 'bottom' | 'top' | 'center';
+ horizontal: 'left' | 'middle' | 'right';
+}
+
// tslint:disable:max-file-line-count