mirror of
https://github.com/trezor/trezor-wallet
synced 2025-02-27 15:52:02 +00:00
Merge pull request #70 from trezor/fix-left-navigation-bootloader
fix: LeftNavigation+device in bootloader mode
This commit is contained in:
commit
49f0d089ac
@ -1,12 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'components/Icon';
|
||||
import icons from 'config/icons';
|
||||
import {
|
||||
getStatusColor,
|
||||
getStatusName,
|
||||
isDisabled,
|
||||
getStatus,
|
||||
getVersion,
|
||||
} from 'utils/device';
|
||||
@ -19,15 +16,17 @@ const Wrapper = styled.div`
|
||||
width: 320px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: ${colors.WHITE};
|
||||
border-radius: 4px 0 0 0;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
|
||||
background: ${props => (props.disabled ? colors.GRAY_LIGHT : 'transparent')};
|
||||
background: ${props => (props.isSelected ? colors.WHITE : 'transparent')};
|
||||
|
||||
${props => props.isOpen && css`
|
||||
border-radius: 4px 0 0 0;
|
||||
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
|
||||
|
||||
${props => (props.isOpen || !props.isSelected) && css`
|
||||
box-shadow: none;
|
||||
`}
|
||||
|
||||
${props => props.isHoverable && css`
|
||||
${props => props.isHoverable && !props.disabled && css`
|
||||
&:hover {
|
||||
background: ${colors.GRAY_LIGHT};
|
||||
}
|
||||
@ -43,7 +42,7 @@ const ClickWrapper = styled.div`
|
||||
cursor: pointer;
|
||||
|
||||
${props => props.disabled && css`
|
||||
cursor: initial;
|
||||
cursor: default;
|
||||
`}
|
||||
`;
|
||||
|
||||
@ -71,18 +70,6 @@ const Status = styled.div`
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
`;
|
||||
|
||||
const Counter = styled.div`
|
||||
border: 1px solid ${colors.DIVIDER};
|
||||
border-radius: 50%;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
padding-right: 25px;
|
||||
display: flex;
|
||||
@ -104,72 +91,53 @@ const Dot = styled.div`
|
||||
height: 10px;
|
||||
`;
|
||||
|
||||
class DeviceHeader extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
clicked: false,
|
||||
};
|
||||
}
|
||||
|
||||
isDisabled(device, devices, transport) {
|
||||
return isDisabled(device, devices, transport);
|
||||
}
|
||||
|
||||
handleClickWrapper() {
|
||||
this.setState({ clicked: true });
|
||||
if (!this.props.disabled) {
|
||||
this.props.onClickWrapper();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen, icon, device, devices, transport, isHoverable,
|
||||
} = this.props;
|
||||
const status = getStatus(device);
|
||||
const disabled = isDisabled(device, devices, transport);
|
||||
const deviceCount = devices.length;
|
||||
|
||||
return (
|
||||
<Wrapper isOpen={isOpen} isHoverable={isHoverable}>
|
||||
<ClickWrapper disabled={disabled} onClick={() => this.handleClickWrapper()}>
|
||||
<ImageWrapper>
|
||||
<Dot color={getStatusColor(status)} />
|
||||
<TrezorImage model={getVersion(device)} />
|
||||
</ImageWrapper>
|
||||
<LabelWrapper>
|
||||
<Name>{device.instanceLabel}</Name>
|
||||
<Status>{getStatusName(status)}</Status>
|
||||
</LabelWrapper>
|
||||
<IconWrapper>
|
||||
{icon && icon}
|
||||
{!icon && deviceCount > 1 && <Counter>{deviceCount}</Counter>}
|
||||
{!icon && !disabled && (
|
||||
<Icon
|
||||
canAnimate={this.state.clicked === true}
|
||||
isActive={isOpen}
|
||||
size={25}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
icon={icons.ARROW_DOWN}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</IconWrapper>
|
||||
</ClickWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
const DeviceHeader = ({
|
||||
isOpen,
|
||||
icon,
|
||||
device,
|
||||
isHoverable = true,
|
||||
onClickWrapper,
|
||||
isBootloader = false,
|
||||
disabled = false,
|
||||
isSelected = false,
|
||||
}) => {
|
||||
const status = getStatus(device);
|
||||
return (
|
||||
<Wrapper
|
||||
isSelected={isSelected}
|
||||
isOpen={isOpen}
|
||||
isHoverable={isHoverable}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ClickWrapper
|
||||
disabled={disabled}
|
||||
onClick={onClickWrapper}
|
||||
>
|
||||
<ImageWrapper>
|
||||
<Dot color={getStatusColor(status)} />
|
||||
<TrezorImage model={getVersion(device)} />
|
||||
</ImageWrapper>
|
||||
<LabelWrapper>
|
||||
<Name>{device.instanceLabel}</Name>
|
||||
<Status>{getStatusName(status)}</Status>
|
||||
</LabelWrapper>
|
||||
<IconWrapper>
|
||||
{icon && !disabled && !isBootloader && icon}
|
||||
</IconWrapper>
|
||||
</ClickWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceHeader.propTypes = {
|
||||
isBootloader: PropTypes.bool,
|
||||
device: PropTypes.object,
|
||||
devices: PropTypes.array,
|
||||
transport: PropTypes.object,
|
||||
icon: PropTypes.element,
|
||||
isHoverable: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
isOpen: PropTypes.bool,
|
||||
isSelected: PropTypes.bool,
|
||||
onClickWrapper: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,9 @@ import colors from 'config/colors';
|
||||
|
||||
const getStatus = (device) => {
|
||||
let status = 'connected';
|
||||
if (!device.connected) {
|
||||
if (device.features && device.features.bootloader_mode) {
|
||||
status = 'connected-bootloader';
|
||||
} else if (!device.connected) {
|
||||
status = 'disconnected';
|
||||
} else if (!device.available) {
|
||||
status = 'unavailable';
|
||||
@ -26,6 +28,9 @@ const getStatusName = (deviceStatus) => {
|
||||
case 'connected':
|
||||
statusName = 'Connected';
|
||||
break;
|
||||
case 'connected-bootloader':
|
||||
statusName = 'Connected (bootloader mode)';
|
||||
break;
|
||||
case 'disconnected':
|
||||
statusName = 'Disconnected';
|
||||
break;
|
||||
@ -43,7 +48,15 @@ const getStatusName = (deviceStatus) => {
|
||||
|
||||
const isWebUSB = transport => !!((transport && transport.version.indexOf('webusb') >= 0));
|
||||
|
||||
const isDisabled = (selectedDevice, devices, transport) => (devices.length < 1 && !isWebUSB(transport)) || (devices.length === 1 && !selectedDevice.features && !isWebUSB(transport));
|
||||
const isDisabled = (selectedDevice, devices, transport) => {
|
||||
if (isWebUSB(transport)) return false; // always enabled if webusb
|
||||
if (devices.length < 1) return true; // no devices
|
||||
if (devices.length === 1) {
|
||||
if (!selectedDevice.features) return true; // unacquired, unreadable
|
||||
if (selectedDevice.features.bootloader_mode || !selectedDevice.features.initialized) return true; // bootlader, not initialized
|
||||
}
|
||||
return false; // default
|
||||
};
|
||||
|
||||
const getVersion = (device) => {
|
||||
let version;
|
||||
@ -64,6 +77,9 @@ const getStatusColor = (deviceStatus) => {
|
||||
case 'connected':
|
||||
color = colors.GREEN_PRIMARY;
|
||||
break;
|
||||
case 'connected-bootloader':
|
||||
color = colors.WARNING_PRIMARY;
|
||||
break;
|
||||
case 'unacquired':
|
||||
color = colors.WARNING_PRIMARY;
|
||||
break;
|
||||
|
@ -21,7 +21,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
||||
connect: state.connect,
|
||||
accounts: state.accounts,
|
||||
router: state.router,
|
||||
deviceDropdownOpened: state.wallet.dropdownOpened,
|
||||
fiat: state.fiat,
|
||||
localStorage: state.localStorage,
|
||||
discovery: state.discovery,
|
||||
|
@ -4,6 +4,7 @@ import Icon from 'components/Icon';
|
||||
import DeviceHeader from 'components/DeviceHeader';
|
||||
import icons from 'config/icons';
|
||||
import colors from 'config/colors';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
const IconClick = styled.div``;
|
||||
@ -14,8 +15,14 @@ class DeviceList extends Component {
|
||||
return a.instance > b.instance ? 1 : -1;
|
||||
}
|
||||
|
||||
redirectToBootloader(selectedDevice) {
|
||||
this.props.history.push(`/device/${selectedDevice.features.device_id}/bootloader`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { devices, selectedDevice, onSelectDevice } = this.props;
|
||||
const {
|
||||
devices, selectedDevice, onSelectDevice, forgetDevice,
|
||||
} = this.props;
|
||||
return (
|
||||
<Wrapper>
|
||||
{devices
|
||||
@ -23,22 +30,32 @@ class DeviceList extends Component {
|
||||
.map(device => (
|
||||
device !== selectedDevice && (
|
||||
<DeviceHeader
|
||||
key={`${device.instanceLabel}`}
|
||||
onClickWrapper={() => onSelectDevice(device)}
|
||||
onClickIcon={() => this.onDeviceMenuClick({ type: 'forget', label: '' }, device)}
|
||||
key={device.state || device.path}
|
||||
isBootloader={device.features.bootloader_mode}
|
||||
onClickWrapper={() => {
|
||||
if (device.features) {
|
||||
if (device.features.bootloader_mode) {
|
||||
this.redirectToBootloader(selectedDevice);
|
||||
}
|
||||
onSelectDevice(device);
|
||||
}
|
||||
}}
|
||||
onClickIcon={() => forgetDevice(device)}
|
||||
icon={(
|
||||
<IconClick onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.onDeviceMenuClick({ type: 'forget', label: '' }, device);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon={icons.EJECT}
|
||||
size={25}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
/>
|
||||
</IconClick>
|
||||
<React.Fragment>
|
||||
<IconClick onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
forgetDevice(device);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon={icons.EJECT}
|
||||
size={25}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
/>
|
||||
</IconClick>
|
||||
</React.Fragment>
|
||||
)}
|
||||
device={device}
|
||||
devices={devices}
|
||||
@ -52,4 +69,4 @@ class DeviceList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default DeviceList;
|
||||
export default withRouter(DeviceList);
|
@ -41,6 +41,11 @@ class MenuItems extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
showDeviceMenu() {
|
||||
const device = this.props.device;
|
||||
return device && device.features && !device.features.bootloader_mode && device.features.initialized;
|
||||
}
|
||||
|
||||
showClone() {
|
||||
return this.props.device && this.props.device.features.passphrase_protection && this.props.device.connected && this.props.device.available;
|
||||
}
|
||||
@ -50,6 +55,7 @@ class MenuItems extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.showDeviceMenu()) return null;
|
||||
return (
|
||||
<Wrapper>
|
||||
<Item onClick={() => this.onDeviceMenuClick('settings', this.props.device)}>
|
||||
|
@ -98,7 +98,7 @@ class DeviceMenu extends Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { devices, onSelectDevice } = this.props;
|
||||
const { devices, onSelectDevice, forgetDevice } = this.props;
|
||||
const { transport } = this.props.connect;
|
||||
const { selectedDevice } = this.props.wallet;
|
||||
|
||||
@ -110,6 +110,7 @@ class DeviceMenu extends Component<Props> {
|
||||
devices={devices}
|
||||
selectedDevice={selectedDevice}
|
||||
onSelectDevice={onSelectDevice}
|
||||
forgetDevice={forgetDevice}
|
||||
/>
|
||||
<ButtonWrapper>
|
||||
{isWebUSB(transport) && (
|
||||
|
@ -8,7 +8,6 @@ export type StateProps = {
|
||||
connect: $ElementType<State, 'connect'>,
|
||||
accounts: $ElementType<State, 'accounts'>,
|
||||
router: $ElementType<State, 'router'>,
|
||||
deviceDropdownOpened: boolean,
|
||||
fiat: $ElementType<State, 'fiat'>,
|
||||
localStorage: $ElementType<State, 'localStorage'>,
|
||||
discovery: $ElementType<State, 'discovery'>,
|
||||
|
@ -1,5 +1,3 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import colors from 'config/colors';
|
||||
@ -16,6 +14,18 @@ import type { Props } from './components/common';
|
||||
|
||||
const Header = styled(DeviceHeader)``;
|
||||
|
||||
const Counter = styled.div`
|
||||
border: 1px solid ${colors.DIVIDER};
|
||||
border-radius: 50%;
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const TransitionGroupWrapper = styled(TransitionGroup)`
|
||||
width: 640px;
|
||||
`;
|
||||
@ -103,26 +113,26 @@ class LeftNavigation extends React.PureComponent<Props, State> {
|
||||
this.state = {
|
||||
animationType: hasNetwork ? 'slide-left' : null,
|
||||
shouldRenderDeviceSelection: false,
|
||||
clicked: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { deviceDropdownOpened } = nextProps;
|
||||
const { selectedDevice } = nextProps.wallet;
|
||||
const hasNetwork = nextProps.router.location.state && nextProps.router.location.state.network;
|
||||
const deviceReady = selectedDevice && selectedDevice.features && !selectedDevice.features.bootloader_mode && selectedDevice.features.initialized;
|
||||
|
||||
if (deviceDropdownOpened) {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { dropdownOpened, selectedDevice } = nextProps.wallet;
|
||||
const hasNetwork = nextProps.location.state && nextProps.location.state.network;
|
||||
const hasFeatures = selectedDevice && selectedDevice.features;
|
||||
const deviceReady = hasFeatures && !selectedDevice.features.bootloader_mode && selectedDevice.features.initialized;
|
||||
if (dropdownOpened) {
|
||||
this.setState({ shouldRenderDeviceSelection: true });
|
||||
} else if (hasNetwork) {
|
||||
this.setState({
|
||||
shouldRenderDeviceSelection: false,
|
||||
animationType: 'slide-left',
|
||||
});
|
||||
} else if (deviceReady) {
|
||||
} else {
|
||||
this.setState({
|
||||
shouldRenderDeviceSelection: false,
|
||||
animationType: 'slide-right',
|
||||
animationType: deviceReady ? 'slide-right' : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -139,7 +149,8 @@ class LeftNavigation extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.toggleDeviceDropdown(!this.props.deviceDropdownOpened);
|
||||
this.setState({ clicked: true });
|
||||
this.props.toggleDeviceDropdown(!this.props.wallet.dropdownOpened);
|
||||
}
|
||||
|
||||
shouldRenderCoins() {
|
||||
@ -163,22 +174,42 @@ class LeftNavigation extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
const isDeviceInBootloader = this.props.wallet.selectedDevice.features && this.props.wallet.selectedDevice.features.bootloader_mode;
|
||||
return (
|
||||
<StickyContainer
|
||||
location={this.props.router.location.pathname}
|
||||
deviceSelection={this.props.deviceDropdownOpened}
|
||||
location={this.props.location.pathname}
|
||||
deviceSelection={this.props.wallet.dropdownOpened}
|
||||
>
|
||||
<Header
|
||||
onClickWrapper={() => this.handleOpen()}
|
||||
isSelected
|
||||
isHoverable={false}
|
||||
onClickWrapper={() => {
|
||||
if (!isDeviceInBootloader || this.props.devices.length > 1) {
|
||||
this.handleOpen();
|
||||
}
|
||||
}}
|
||||
device={this.props.wallet.selectedDevice}
|
||||
transport={this.props.connect.transport}
|
||||
devices={this.props.devices}
|
||||
isOpen={this.props.deviceDropdownOpened}
|
||||
disabled={isDeviceInBootloader && this.props.devices.length === 1}
|
||||
isOpen={this.props.wallet.dropdownOpened}
|
||||
icon={(
|
||||
<React.Fragment>
|
||||
{this.props.devices.length > 1 && (
|
||||
<Counter>{this.props.devices.length}</Counter>
|
||||
)}
|
||||
<Icon
|
||||
canAnimate={this.state.clicked === true}
|
||||
isActive={this.props.wallet.dropdownOpened}
|
||||
size={25}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
icon={icons.ARROW_DOWN}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{...this.props}
|
||||
/>
|
||||
<Body>
|
||||
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
|
||||
{menu}
|
||||
{!isDeviceInBootloader && menu}
|
||||
</Body>
|
||||
<Footer key="sticky-footer">
|
||||
<Help>
|
||||
@ -200,7 +231,6 @@ LeftNavigation.propTypes = {
|
||||
connect: PropTypes.object,
|
||||
accounts: PropTypes.array,
|
||||
router: PropTypes.object,
|
||||
deviceDropdownOpened: PropTypes.bool,
|
||||
fiat: PropTypes.array,
|
||||
localStorage: PropTypes.object,
|
||||
discovery: PropTypes.array,
|
||||
@ -209,6 +239,7 @@ LeftNavigation.propTypes = {
|
||||
pending: PropTypes.array,
|
||||
|
||||
toggleDeviceDropdown: PropTypes.func,
|
||||
selectedDevice: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user