Merge pull request #70 from trezor/fix-left-navigation-bootloader

fix: LeftNavigation+device in bootloader mode
pull/120/head
Szymon Lesisz 6 years ago committed by GitHub
commit 49f0d089ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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};
background: ${props => (props.disabled ? colors.GRAY_LIGHT : 'transparent')};
background: ${props => (props.isSelected ? colors.WHITE : 'transparent')};
border-radius: 4px 0 0 0;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
${props => props.isOpen && css`
${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…
Cancel
Save