diff --git a/src/components/DeviceHeader/index.js b/src/components/DeviceHeader/index.js index c47e3d3b..8315b326 100644 --- a/src/components/DeviceHeader/index.js +++ b/src/components/DeviceHeader/index.js @@ -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 ( - - this.handleClickWrapper()}> - - - - - - {device.instanceLabel} - {getStatusName(status)} - - - {icon && icon} - {!icon && deviceCount > 1 && {deviceCount}} - {!icon && !disabled && ( - - ) - } - - - - ); - } -} +const DeviceHeader = ({ + isOpen, + icon, + device, + isHoverable = true, + onClickWrapper, + isBootloader = false, + disabled = false, + isSelected = false, +}) => { + const status = getStatus(device); + return ( + + + + + + + + {device.instanceLabel} + {getStatusName(status)} + + + {icon && !disabled && !isBootloader && icon} + + + + ); +}; 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, }; diff --git a/src/utils/device.js b/src/utils/device.js index de04d66f..a8b03564 100644 --- a/src/utils/device.js +++ b/src/utils/device.js @@ -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; diff --git a/src/views/Wallet/components/LeftNavigation/Container.js b/src/views/Wallet/components/LeftNavigation/Container.js index e297c59c..f5dd762f 100644 --- a/src/views/Wallet/components/LeftNavigation/Container.js +++ b/src/views/Wallet/components/LeftNavigation/Container.js @@ -21,7 +21,6 @@ const mapStateToProps: MapStateToProps = (state: St connect: state.connect, accounts: state.accounts, router: state.router, - deviceDropdownOpened: state.wallet.dropdownOpened, fiat: state.fiat, localStorage: state.localStorage, discovery: state.discovery, diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js index faca2a75..d4b86412 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js @@ -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 ( {devices @@ -23,22 +30,32 @@ class DeviceList extends Component { .map(device => ( device !== selectedDevice && ( 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={( - { - event.stopPropagation(); - event.preventDefault(); - this.onDeviceMenuClick({ type: 'forget', label: '' }, device); - }} - > - - + + { + event.stopPropagation(); + event.preventDefault(); + forgetDevice(device); + }} + > + + + )} device={device} devices={devices} @@ -52,4 +69,4 @@ class DeviceList extends Component { } } -export default DeviceList; \ No newline at end of file +export default withRouter(DeviceList); \ No newline at end of file diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js index 5c0b9d15..7ca2c305 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js @@ -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 ( this.onDeviceMenuClick('settings', this.props.device)}> diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js index 4d50fba5..dc34884e 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js @@ -98,7 +98,7 @@ class DeviceMenu extends Component { } 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 { devices={devices} selectedDevice={selectedDevice} onSelectDevice={onSelectDevice} + forgetDevice={forgetDevice} /> {isWebUSB(transport) && ( diff --git a/src/views/Wallet/components/LeftNavigation/components/common.js b/src/views/Wallet/components/LeftNavigation/components/common.js index ee7f1d37..0e3a66c4 100644 --- a/src/views/Wallet/components/LeftNavigation/components/common.js +++ b/src/views/Wallet/components/LeftNavigation/components/common.js @@ -8,7 +8,6 @@ export type StateProps = { connect: $ElementType, accounts: $ElementType, router: $ElementType, - deviceDropdownOpened: boolean, fiat: $ElementType, localStorage: $ElementType, discovery: $ElementType, diff --git a/src/views/Wallet/components/LeftNavigation/index.js b/src/views/Wallet/components/LeftNavigation/index.js index 6f3bf158..e399e10d 100644 --- a/src/views/Wallet/components/LeftNavigation/index.js +++ b/src/views/Wallet/components/LeftNavigation/index.js @@ -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 { 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 { } 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 { ); } + const isDeviceInBootloader = this.props.wallet.selectedDevice.features && this.props.wallet.selectedDevice.features.bootloader_mode; return (
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={( + + {this.props.devices.length > 1 && ( + {this.props.devices.length} + )} + + + )} {...this.props} /> {this.state.shouldRenderDeviceSelection && } - {menu} + {!isDeviceInBootloader && menu}