Fix Duplicate DPaths & Deterministic Modal Improvements (#1682)

* Fix dPath uniqueness

* Pass around dPath object, not just path, to maintain uniqueness

* Improve deterministic modal UI

* Fix custom dpaths
This commit is contained in:
William O'Beirne 2018-04-30 14:39:25 -04:00 committed by Daniel Ternyak
parent 82e0530bec
commit a57d17a3e0
6 changed files with 62 additions and 58 deletions

View File

@ -46,7 +46,7 @@
}
&-addresses {
overflow-y: scroll;
overflow-y: auto;
&-table {
width: 732px;
text-align: center;
@ -74,16 +74,22 @@
background-image: url('~assets/images/icon-external-link.svg');
}
&-na {
font-size: $font-size-xs;
opacity: 0.3;
}
// Specific selectors to override bootstrap
tbody {
tr {
cursor: pointer;
}
td {
vertical-align: middle;
}
}
}
}
&-nav {
&-btn {

View File

@ -24,7 +24,7 @@ const WALLETS_PER_PAGE = 5;
interface Props {
// Passed props
isOpen?: boolean;
dPath: string;
dPath: DPath;
dPaths: DPath[];
publicKey?: string;
chainCode?: string;
@ -42,11 +42,11 @@ interface Props {
onCancel(): void;
onConfirmAddress(address: string, addressIndex: number): void;
onPathChange(path: string): void;
onPathChange(dPath: DPath): void;
}
interface State {
currentLabel: string;
currentDPath: DPath;
selectedAddress: string;
selectedAddrIndex: number;
isCustomPath: boolean;
@ -65,7 +65,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
selectedAddrIndex: 0,
isCustomPath: false,
customPath: '',
currentLabel: '',
currentDPath: this.props.dPath,
page: 0
};
@ -86,7 +86,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
}
public render() {
const { wallets, desiredToken, network, tokens, dPath, dPaths, onCancel } = this.props;
const { wallets, desiredToken, network, tokens, dPaths, onCancel } = this.props;
const { selectedAddress, customPath, page } = this.state;
const buttons: IButton[] = [
@ -119,7 +119,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
<div className="DWModal-path-select">
<Select
name="fieldDPath"
value={this.state.currentLabel || this.findDPath('value', dPath).value}
value={this.state.currentDPath}
onChange={this.handleChangePath}
options={dPaths.concat([customDPath])}
optionRenderer={this.renderDPathOption}
@ -128,7 +128,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
searchable={false}
/>
</div>
{this.state.currentLabel === customDPath.label && (
{this.state.currentDPath.label === customDPath.label && (
<React.Fragment>
<div className="DWModal-path-custom">
<Input
@ -199,12 +199,12 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
const { dPath, publicKey, chainCode, seed } = props;
if (dPath && ((publicKey && chainCode) || seed)) {
if (isValidPath(dPath)) {
if (isValidPath(dPath.value)) {
this.props.getDeterministicWallets({
seed,
dPath,
publicKey,
chainCode,
dPath: dPath.value,
limit: WALLETS_PER_PAGE,
offset: WALLETS_PER_PAGE * this.state.page
});
@ -214,19 +214,12 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
}
}
private findDPath = (prop: keyof DPath, cmp: string) => {
return this.props.dPaths.find(d => d[prop] === cmp) || customDPath;
};
private handleChangePath = (newPath: DPath) => {
const { value: dPathLabel } = newPath;
const { value } = this.findDPath('value', dPathLabel);
if (value === customDPath.value) {
this.setState({ isCustomPath: true, currentLabel: dPathLabel });
if (newPath.value === customDPath.value) {
this.setState({ isCustomPath: true, currentDPath: newPath });
} else {
this.setState({ isCustomPath: false, currentLabel: dPathLabel });
this.props.onPathChange(value);
this.setState({ isCustomPath: false, currentDPath: newPath });
this.props.onPathChange(newPath);
}
};
@ -235,11 +228,14 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
};
private handleSubmitCustomPath = (ev: React.FormEvent<HTMLFormElement>) => {
const { customPath, currentLabel } = this.state;
const { customPath, currentDPath } = this.state;
ev.preventDefault();
if (currentLabel === customDPath.label && isValidPath(customPath)) {
this.props.onPathChange(customPath);
if (currentDPath.value === customDPath.value && isValidPath(customPath)) {
this.props.onPathChange({
label: customDPath.label,
value: customPath
});
}
};
@ -309,16 +305,16 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
/>
</td>
<td>
{token ? (
{desiredToken ? (
<UnitDisplay
decimal={token.decimal}
value={token.value}
decimal={token ? token.decimal : 0}
value={token ? token.value : null}
symbol={desiredToken}
displayShortBalance={true}
checkOffline={true}
/>
) : (
'???'
<span className="DWModal-addresses-table-na">N/A</span>
)}
</td>
<td>

View File

@ -26,7 +26,7 @@ interface StateProps {
interface State {
publicKey: string;
chainCode: string;
dPath: string;
dPath: DPath;
error: string | null;
isLoading: boolean;
showTip: boolean;
@ -38,7 +38,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
public state: State = {
publicKey: '',
chainCode: '',
dPath: this.props.dPath ? this.props.dPath.value : '',
dPath: this.props.dPath || this.props.dPaths[0],
error: null,
isLoading: false,
showTip: false
@ -51,8 +51,8 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
};
public componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath) {
this.setState({ dPath: nextProps.dPath ? nextProps.dPath.value : '' });
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
this.setState({ dPath: nextProps.dPath });
}
}
@ -123,11 +123,11 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
);
}
private handlePathChange = (dPath: string) => {
private handlePathChange = (dPath: DPath) => {
this.handleConnect(dPath);
};
private handleConnect = (dPath: string = this.state.dPath) => {
private handleConnect = (dPath: DPath) => {
this.setState({
isLoading: true,
error: null,
@ -136,7 +136,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
ledger.comm_u2f.create_async().then((comm: any) => {
new ledger.eth(comm)
.getAddress_async(dPath, false, true)
.getAddress_async(dPath.value, false, true)
.then(res => {
this.setState({
publicKey: res.publicKey,
@ -182,19 +182,19 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
};
private handleUnlock = (address: string, index: number) => {
this.props.onUnlock(new LedgerWallet(address, this.state.dPath, index));
this.props.onUnlock(new LedgerWallet(address, this.state.dPath.value, index));
this.reset();
};
private handleNullConnect = (): void => {
return this.handleConnect();
return this.handleConnect(this.state.dPath);
};
private reset() {
this.setState({
publicKey: '',
chainCode: '',
dPath: this.props.dPath ? this.props.dPath.value : ''
dPath: this.props.dPath || this.props.dPaths[0]
});
}
}

View File

@ -27,7 +27,7 @@ interface State {
formattedPhrase: string;
pass: string;
seed: string;
dPath: string;
dPath: DPath;
}
class MnemonicDecryptClass extends PureComponent<Props, State> {
@ -36,12 +36,12 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
formattedPhrase: '',
pass: '',
seed: '',
dPath: this.props.dPath.value
dPath: this.props.dPath
};
public componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath) {
this.setState({ dPath: nextProps.dPath.value });
this.setState({ dPath: nextProps.dPath });
}
}
@ -131,7 +131,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
this.setState({ seed: '' });
};
private handlePathChange = (dPath: string) => {
private handlePathChange = (dPath: DPath) => {
this.setState({ dPath });
};
@ -139,7 +139,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
const { formattedPhrase, pass, dPath } = this.state;
this.props.onUnlock({
path: `${dPath}/${index}`,
path: `${dPath.value}/${index}`,
pass,
phrase: formattedPhrase,
address

View File

@ -25,7 +25,7 @@ interface StateProps {
interface State {
publicKey: string;
chainCode: string;
dPath: string;
dPath: DPath;
error: string | null;
isLoading: boolean;
}
@ -36,14 +36,14 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
public state: State = {
publicKey: '',
chainCode: '',
dPath: this.props.dPath ? this.props.dPath.value : '',
dPath: this.props.dPath || this.props.dPaths[0],
error: null,
isLoading: false
};
public componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath) {
this.setState({ dPath: nextProps.dPath ? nextProps.dPath.value : '' });
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
this.setState({ dPath: nextProps.dPath });
}
}
@ -98,19 +98,19 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
);
}
private handlePathChange = (dPath: string) => {
private handlePathChange = (dPath: DPath) => {
this.setState({ dPath });
this.handleConnect(dPath);
};
private handleConnect = (dPath: string = this.state.dPath): void => {
private handleConnect = (dPath: DPath): void => {
this.setState({
isLoading: true,
error: null
});
(TrezorConnect as any).getXPubKey(
dPath,
dPath.value,
(res: any) => {
if (res.success) {
this.setState({
@ -135,17 +135,19 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
};
private handleUnlock = (address: string, index: number) => {
this.props.onUnlock(new TrezorWallet(address, this.state.dPath, index));
this.props.onUnlock(new TrezorWallet(address, this.state.dPath.value, index));
this.reset();
};
private handleNullConnect = (): void => this.handleConnect();
private handleNullConnect = (): void => {
this.handleConnect(this.state.dPath);
};
private reset() {
this.setState({
publicKey: '',
chainCode: '',
dPath: this.props.dPath ? this.props.dPath.value : ''
dPath: this.props.dPath || this.props.dPaths[0]
});
}
}

View File

@ -1,6 +1,6 @@
import { InsecureWalletName, SecureWalletName, WalletName, walletNames } from 'config';
import { EXTRA_PATHS } from 'config/dpaths';
import sortedUniq from 'lodash/sortedUniq';
import uniqBy from 'lodash/uniqBy';
import difference from 'lodash/difference';
import { StaticNetworkConfig, DPathFormats } from 'types/network';
import { AppState } from 'reducers';
@ -22,7 +22,7 @@ export function getPaths(state: AppState, pathType: PathType): DPath[] {
return networkPaths;
}, [])
.concat(EXTRA_PATHS);
return sortedUniq(paths);
return uniqBy(paths, p => `${p.label}${p.value}`);
}
export function getSingleDPath(state: AppState, format: DPathFormat): DPath | undefined {