mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-24 09:18:09 +00:00
Merge pull request #127 from trezor/feature/app-notifications
Feature/app notifications
This commit is contained in:
commit
ac724210dd
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { push, LOCATION_CHANGE } from 'react-router-redux';
|
import { push, LOCATION_CHANGE } from 'react-router-redux';
|
||||||
import { routes } from 'support/routes';
|
import { routes } from 'support/routes';
|
||||||
|
import * as deviceUtils from 'utils/device';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
RouterLocationState,
|
RouterLocationState,
|
||||||
@ -59,6 +60,11 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!device) return false;
|
if (!device) return false;
|
||||||
|
|
||||||
|
if (!deviceUtils.isDeviceAccessible(device)) {
|
||||||
|
// TODO: there should be no access to deep links if device has incorrect mode/firmware
|
||||||
|
// if (params.hasOwnProperty('network') || params.hasOwnProperty('account')) return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate requested network
|
// validate requested network
|
||||||
@ -177,10 +183,12 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
|
|||||||
let url: ?string;
|
let url: ?string;
|
||||||
if (!device.features) {
|
if (!device.features) {
|
||||||
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
|
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
|
||||||
} else if (device.features.bootloader_mode) {
|
} else if (device.mode === 'bootloader') { // device in bootloader doesn't have device_id
|
||||||
url = `/device/${device.path}/bootloader`;
|
url = `/device/${device.path}/bootloader`;
|
||||||
} else if (!device.features.initialized) {
|
} else if (device.mode === 'initialize') {
|
||||||
url = `/device/${device.features.device_id}/initialize`;
|
url = `/device/${device.features.device_id}/initialize`;
|
||||||
|
} else if (device.firmware === 'required') {
|
||||||
|
url = `/device/${device.features.device_id}/firmware-update`;
|
||||||
} else if (typeof device.instance === 'number') {
|
} else if (typeof device.instance === 'number') {
|
||||||
url = `/device/${device.features.device_id}:${device.instance}`;
|
url = `/device/${device.features.device_id}:${device.instance}`;
|
||||||
} else {
|
} else {
|
||||||
@ -303,6 +311,24 @@ export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispat
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Go to UpdateBridge page
|
||||||
|
*/
|
||||||
|
export const gotoBridgeUpdate = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
|
dispatch(goto('/bridge'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Go to UpdateFirmware page
|
||||||
|
* Called from App notification
|
||||||
|
*/
|
||||||
|
export const gotoFirmwareUpdate = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const { selectedDevice } = getState().wallet;
|
||||||
|
if (!selectedDevice || !selectedDevice.features) return;
|
||||||
|
const devUrl: string = `${selectedDevice.features.device_id}${selectedDevice.instance ? `:${selectedDevice.instance}` : ''}`;
|
||||||
|
dispatch(goto(`/device/${devUrl}/firmware-update`));
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to redirect to initial url
|
* Try to redirect to initial url
|
||||||
*/
|
*/
|
||||||
|
@ -154,50 +154,50 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch): void => {
|
|||||||
|
|
||||||
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const selected = getState().wallet.selectedDevice;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (selected
|
if (!selected) return;
|
||||||
&& selected.connected
|
const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
|
||||||
&& (selected.features && !selected.features.bootloader_mode && selected.features.initialized)
|
if (!isDeviceReady) return;
|
||||||
&& !selected.state) {
|
|
||||||
const response = await TrezorConnect.getDeviceState({
|
|
||||||
device: {
|
|
||||||
path: selected.path,
|
|
||||||
instance: selected.instance,
|
|
||||||
state: selected.state,
|
|
||||||
},
|
|
||||||
useEmptyPassphrase: !selected.instance,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response && response.success) {
|
const response = await TrezorConnect.getDeviceState({
|
||||||
dispatch({
|
device: {
|
||||||
type: CONNECT.AUTH_DEVICE,
|
path: selected.path,
|
||||||
device: selected,
|
instance: selected.instance,
|
||||||
state: response.payload.state,
|
state: selected.state,
|
||||||
});
|
},
|
||||||
} else {
|
useEmptyPassphrase: !selected.instance,
|
||||||
dispatch({
|
});
|
||||||
type: NOTIFICATION.ADD,
|
|
||||||
payload: {
|
if (response && response.success) {
|
||||||
devicePath: selected.path,
|
dispatch({
|
||||||
type: 'error',
|
type: CONNECT.AUTH_DEVICE,
|
||||||
title: 'Authentication error',
|
device: selected,
|
||||||
message: response.payload.error,
|
state: response.payload.state,
|
||||||
cancelable: false,
|
});
|
||||||
actions: [
|
} else {
|
||||||
{
|
dispatch({
|
||||||
label: 'Try again',
|
type: NOTIFICATION.ADD,
|
||||||
callback: () => {
|
payload: {
|
||||||
dispatch({
|
devicePath: selected.path,
|
||||||
type: NOTIFICATION.CLOSE,
|
type: 'error',
|
||||||
payload: { devicePath: selected.path },
|
title: 'Authentication error',
|
||||||
});
|
message: response.payload.error,
|
||||||
dispatch(getSelectedDeviceState());
|
cancelable: false,
|
||||||
},
|
actions: [
|
||||||
|
{
|
||||||
|
label: 'Try again',
|
||||||
|
callback: () => {
|
||||||
|
dispatch({
|
||||||
|
type: NOTIFICATION.CLOSE,
|
||||||
|
payload: { devicePath: selected.path },
|
||||||
|
});
|
||||||
|
dispatch(getSelectedDeviceState());
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
@ -5,6 +5,7 @@ import { DEVICE } from 'trezor-connect';
|
|||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
import * as reducerUtils from 'reducers/utils';
|
import * as reducerUtils from 'reducers/utils';
|
||||||
|
import * as deviceUtils from 'utils/device';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Device,
|
Device,
|
||||||
@ -104,7 +105,7 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
|
|||||||
|
|
||||||
// handle devices state change (from trezor-connect events or location change)
|
// handle devices state change (from trezor-connect events or location change)
|
||||||
if (locationChanged || selectedDeviceChanged) {
|
if (locationChanged || selectedDeviceChanged) {
|
||||||
if (device && reducerUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
|
if (device && deviceUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: WALLET.UPDATE_SELECTED_DEVICE,
|
type: WALLET.UPDATE_SELECTED_DEVICE,
|
||||||
device,
|
device,
|
||||||
|
@ -98,7 +98,7 @@ const DeviceHeader = ({
|
|||||||
device,
|
device,
|
||||||
isHoverable = true,
|
isHoverable = true,
|
||||||
onClickWrapper,
|
onClickWrapper,
|
||||||
isBootloader = false,
|
isAccessible = true,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
}) => {
|
}) => {
|
||||||
@ -123,7 +123,7 @@ const DeviceHeader = ({
|
|||||||
<Status>{getStatusName(status)}</Status>
|
<Status>{getStatusName(status)}</Status>
|
||||||
</LabelWrapper>
|
</LabelWrapper>
|
||||||
<IconWrapper>
|
<IconWrapper>
|
||||||
{icon && !disabled && !isBootloader && icon}
|
{icon && !disabled && isAccessible && icon}
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
</ClickWrapper>
|
</ClickWrapper>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
@ -131,7 +131,7 @@ const DeviceHeader = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
DeviceHeader.propTypes = {
|
DeviceHeader.propTypes = {
|
||||||
isBootloader: PropTypes.bool,
|
isAccessible: PropTypes.bool,
|
||||||
device: PropTypes.object,
|
device: PropTypes.object,
|
||||||
icon: PropTypes.element,
|
icon: PropTypes.element,
|
||||||
isHoverable: PropTypes.bool,
|
isHoverable: PropTypes.bool,
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
/* @flow */
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Notification } from 'components/Notification';
|
||||||
|
|
||||||
|
import type { Props } from '../../index';
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const { online } = props.wallet;
|
||||||
|
if (online) return null;
|
||||||
|
return (<Notification type="error" title="Wallet is offline" />);
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
/* @flow */
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Notification } from 'components/Notification';
|
||||||
|
|
||||||
|
import type { Props } from '../../index';
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
if (props.connect.transport && props.connect.transport.outdated) {
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
type="warning"
|
||||||
|
title="New Trezor Bridge is available"
|
||||||
|
actions={
|
||||||
|
[{
|
||||||
|
label: 'Update',
|
||||||
|
callback: props.routerActions.gotoBridgeUpdate,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
/* @flow */
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Notification } from 'components/Notification';
|
||||||
|
|
||||||
|
import type { Props } from '../../index';
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const { selectedDevice } = props.wallet;
|
||||||
|
const outdated = selectedDevice && selectedDevice.features && selectedDevice.firmware === 'outdated';
|
||||||
|
if (!outdated) return null;
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
type="warning"
|
||||||
|
title="Firmware update"
|
||||||
|
actions={
|
||||||
|
[{
|
||||||
|
label: 'Update',
|
||||||
|
callback: props.routerActions.gotoFirmwareUpdate,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,49 @@
|
|||||||
|
/* @flow */
|
||||||
|
import * as React from 'react';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
|
import type { State, Dispatch } from 'flowtype';
|
||||||
|
|
||||||
|
import * as NotificationActions from 'actions/NotificationActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
|
|
||||||
|
import OnlineStatus from './components/OnlineStatus';
|
||||||
|
import UpdateBridge from './components/UpdateBridge';
|
||||||
|
import UpdateFirmware from './components/UpdateFirmware';
|
||||||
|
|
||||||
|
export type StateProps = {
|
||||||
|
connect: $ElementType<State, 'connect'>;
|
||||||
|
wallet: $ElementType<State, 'wallet'>;
|
||||||
|
children?: React.Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DispatchProps = {
|
||||||
|
close: typeof NotificationActions.close;
|
||||||
|
routerActions: typeof RouterActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = StateProps & DispatchProps;
|
||||||
|
|
||||||
|
type OwnProps = {};
|
||||||
|
|
||||||
|
const Notifications = (props: Props) => (
|
||||||
|
<React.Fragment>
|
||||||
|
<OnlineStatus {...props} />
|
||||||
|
<UpdateBridge {...props} />
|
||||||
|
<UpdateFirmware {...props} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
|
||||||
|
connect: state.connect,
|
||||||
|
wallet: state.wallet,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||||
|
close: bindActionCreators(NotificationActions.close, dispatch),
|
||||||
|
routerActions: bindActionCreators(RouterActions, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Notifications);
|
@ -1,8 +0,0 @@
|
|||||||
export default [
|
|
||||||
{ id: 'Windows', value: 'trezor-bridge-2.0.11-win32-install.exe', label: 'Windows' },
|
|
||||||
{ id: 'macOS', value: 'trezor-bridge-2.0.11.pkg', label: 'macOS' },
|
|
||||||
{ id: 'Linux', value: 'trezor-bridge_2.0.11_amd64.deb', label: 'Linux 64-bit (deb)' },
|
|
||||||
{ id: 'Linux-rpm', value: 'trezor-bridge_2.0.11_amd64.rpm', label: 'Linux 64-bit (rpm)' },
|
|
||||||
{ id: '01', value: 'trezor-bridge_2.0.11_amd32.deb', label: 'Linux 32-bit (deb)' },
|
|
||||||
{ id: '02', value: 'trezor-bridge_2.0.11_amd32.rpm', label: 'Linux 32-bit (rpm)' },
|
|
||||||
];
|
|
@ -38,6 +38,7 @@ import type {
|
|||||||
Features,
|
Features,
|
||||||
DeviceStatus,
|
DeviceStatus,
|
||||||
DeviceFirmwareStatus,
|
DeviceFirmwareStatus,
|
||||||
|
DeviceMode,
|
||||||
DeviceMessageType,
|
DeviceMessageType,
|
||||||
TransportMessageType,
|
TransportMessageType,
|
||||||
BlockchainMessageType,
|
BlockchainMessageType,
|
||||||
@ -53,6 +54,7 @@ export type AcquiredDevice = $Exact<{
|
|||||||
+features: Features,
|
+features: Features,
|
||||||
+firmware: DeviceFirmwareStatus,
|
+firmware: DeviceFirmwareStatus,
|
||||||
status: DeviceStatus,
|
status: DeviceStatus,
|
||||||
|
+mode: DeviceMode,
|
||||||
state: ?string,
|
state: ?string,
|
||||||
|
|
||||||
remember: boolean; // device should be remembered
|
remember: boolean; // device should be remembered
|
||||||
|
@ -9,12 +9,24 @@ export type SelectedDevice = {
|
|||||||
instance: ?number;
|
instance: ?number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LatestBridge = {
|
||||||
|
version: Array<number>;
|
||||||
|
directory: string;
|
||||||
|
packages: Array<{ name: string; url: string; signature?: string; preferred: boolean; }>;
|
||||||
|
changelog: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
error: ?string;
|
error: ?string;
|
||||||
transport: ?{
|
transport: {
|
||||||
type: string;
|
type: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
outdated: boolean;
|
||||||
|
bridge: LatestBridge;
|
||||||
|
} | {
|
||||||
|
type: null,
|
||||||
|
bridge: LatestBridge;
|
||||||
};
|
};
|
||||||
// browserState: {
|
// browserState: {
|
||||||
// name: string;
|
// name: string;
|
||||||
@ -30,7 +42,15 @@ export type State = {
|
|||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
error: null,
|
error: null,
|
||||||
transport: null,
|
transport: {
|
||||||
|
type: null,
|
||||||
|
bridge: {
|
||||||
|
version: [],
|
||||||
|
directory: '',
|
||||||
|
packages: [],
|
||||||
|
changelog: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
browserState: {},
|
browserState: {},
|
||||||
acquiringDevice: false,
|
acquiringDevice: false,
|
||||||
};
|
};
|
||||||
@ -63,9 +83,12 @@ export default function connect(state: State = initialState, action: Action): St
|
|||||||
case TRANSPORT.ERROR:
|
case TRANSPORT.ERROR:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
// error: action.payload, // message is wrapped in "device" field. It's dispatched from TrezorConnect.on(DEVICE_EVENT...) in TrezorConnectService
|
// error: action.payload.error, // message is wrapped in "device" field. It's dispatched from TrezorConnect.on(DEVICE_EVENT...) in TrezorConnectService
|
||||||
error: 'Transport is missing',
|
error: 'Transport is missing',
|
||||||
transport: null,
|
transport: {
|
||||||
|
type: null,
|
||||||
|
bridge: action.payload.bridge,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
case CONNECT.START_ACQUIRING:
|
case CONNECT.START_ACQUIRING:
|
||||||
|
@ -21,7 +21,7 @@ export const getSelectedDevice = (state: State): ?TrezorDevice => {
|
|||||||
return state.devices.find((d) => {
|
return state.devices.find((d) => {
|
||||||
if (!d.features && d.path === locationState.device) {
|
if (!d.features && d.path === locationState.device) {
|
||||||
return true;
|
return true;
|
||||||
} if (d.features && d.features.bootloader_mode && d.path === locationState.device) {
|
} if (d.mode === 'bootloader' && d.path === locationState.device) {
|
||||||
return true;
|
return true;
|
||||||
} if (d.features && d.features.device_id === locationState.device && d.instance === instance) {
|
} if (d.features && d.features.device_id === locationState.device && d.instance === instance) {
|
||||||
return true;
|
return true;
|
||||||
@ -30,9 +30,6 @@ export const getSelectedDevice = (state: State): ?TrezorDevice => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
|
||||||
export const isSelectedDevice = (current: ?TrezorDevice, device: ?TrezorDevice): boolean => !!((current && device && (current.path === device.path && current.instance === device.instance)));
|
|
||||||
|
|
||||||
// find device by id and state
|
// find device by id and state
|
||||||
export const findDevice = (devices: Array<TrezorDevice>, deviceId: string, deviceState: string /*, instance: ?number*/): ?TrezorDevice => devices.find((d) => {
|
export const findDevice = (devices: Array<TrezorDevice>, deviceId: string, deviceState: string /*, instance: ?number*/): ?TrezorDevice => devices.find((d) => {
|
||||||
// TODO: && (instance && d.instance === instance)
|
// TODO: && (instance && d.instance === instance)
|
||||||
|
@ -1,64 +1,99 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
|
|
||||||
const getStatus = (device) => {
|
import type {
|
||||||
let status = 'connected';
|
TrezorDevice,
|
||||||
if (device.features && device.features.bootloader_mode) {
|
State,
|
||||||
status = 'connected-bootloader';
|
} from 'flowtype';
|
||||||
} else if (!device.connected) {
|
|
||||||
status = 'disconnected';
|
type Transport = $ElementType<$ElementType<State, 'connect'>, 'transport'>;
|
||||||
} else if (!device.available) {
|
|
||||||
status = 'unavailable';
|
export const getStatus = (device: TrezorDevice): string => {
|
||||||
} else if (device.type === 'acquired') {
|
if (!device.connected) {
|
||||||
if (device.status === 'occupied') {
|
return 'disconnected';
|
||||||
status = 'used-in-other-window';
|
}
|
||||||
|
if (device.type === 'acquired') {
|
||||||
|
if (device.mode === 'bootloader') {
|
||||||
|
return 'bootloader';
|
||||||
}
|
}
|
||||||
} else if (device.type === 'unacquired') {
|
if (device.mode === 'initialize') {
|
||||||
status = 'unacquired';
|
return 'initialize';
|
||||||
|
}
|
||||||
|
if (device.firmware === 'required') {
|
||||||
|
return 'firmware-required';
|
||||||
|
}
|
||||||
|
if (device.status === 'occupied') {
|
||||||
|
return 'used-in-other-window';
|
||||||
|
}
|
||||||
|
if (device.status === 'used') {
|
||||||
|
return 'used-in-other-window';
|
||||||
|
}
|
||||||
|
if (device.firmware === 'outdated') {
|
||||||
|
return 'firmware-recommended';
|
||||||
|
}
|
||||||
|
return 'connected';
|
||||||
}
|
}
|
||||||
|
if (!device.available) { // deprecated
|
||||||
return status;
|
return 'unavailable';
|
||||||
|
}
|
||||||
|
if (device.type === 'unacquired') {
|
||||||
|
return 'unacquired';
|
||||||
|
}
|
||||||
|
if (device.type === 'unreadable') {
|
||||||
|
return 'unreadable';
|
||||||
|
}
|
||||||
|
return 'unknown';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusName = (deviceStatus) => {
|
export const getStatusName = (deviceStatus: string): string => {
|
||||||
let statusName;
|
|
||||||
switch (deviceStatus) {
|
switch (deviceStatus) {
|
||||||
case 'used-in-other-window':
|
|
||||||
statusName = 'Used in other window';
|
|
||||||
break;
|
|
||||||
case 'connected':
|
case 'connected':
|
||||||
statusName = 'Connected';
|
return 'Connected';
|
||||||
break;
|
|
||||||
case 'connected-bootloader':
|
|
||||||
statusName = 'Connected (bootloader mode)';
|
|
||||||
break;
|
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
statusName = 'Disconnected';
|
return 'Disconnected';
|
||||||
break;
|
case 'bootloader':
|
||||||
|
return 'Connected (bootloader mode)';
|
||||||
|
case 'initialize':
|
||||||
|
return 'Connected (not initialized)';
|
||||||
|
case 'firmware-required':
|
||||||
|
return 'Connected (update required)';
|
||||||
|
case 'firmware-recommended':
|
||||||
|
return 'Connected (update recommended)';
|
||||||
|
case 'used-in-other-window':
|
||||||
|
return 'Used in other window';
|
||||||
case 'unacquired':
|
case 'unacquired':
|
||||||
statusName = 'Used in other window';
|
return 'Used in other window';
|
||||||
break;
|
|
||||||
case 'unavailable':
|
case 'unavailable':
|
||||||
statusName = 'Unavailable';
|
return 'Unavailable';
|
||||||
break;
|
case 'unreadable':
|
||||||
|
return 'Unreadable';
|
||||||
default:
|
default:
|
||||||
statusName = 'Status unknown';
|
return 'Status unknown';
|
||||||
}
|
}
|
||||||
return statusName;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isWebUSB = transport => !!((transport && transport.version.indexOf('webusb') >= 0));
|
export const isWebUSB = (transport: Transport) => !!((transport.type && transport.version.indexOf('webusb') >= 0));
|
||||||
|
|
||||||
const isDisabled = (selectedDevice, devices, transport) => {
|
export const isDisabled = (selectedDevice: TrezorDevice, devices: Array<TrezorDevice>, transport: Transport) => {
|
||||||
if (isWebUSB(transport)) return false; // always enabled if webusb
|
if (isWebUSB(transport)) return false; // always enabled if webusb
|
||||||
if (devices.length < 1) return true; // no devices
|
if (devices.length < 1) return true; // no devices
|
||||||
if (devices.length === 1) {
|
if (devices.length === 1) {
|
||||||
if (!selectedDevice.features) return true; // unacquired, unreadable
|
if (!selectedDevice.features) return true; // unacquired, unreadable
|
||||||
if (selectedDevice.features.bootloader_mode || !selectedDevice.features.initialized) return true; // bootlader, not initialized
|
if (selectedDevice.mode !== 'normal') return true; // bootloader, not initialized
|
||||||
|
if (selectedDevice.firmware === 'required') return true; // bootloader, not initialized
|
||||||
}
|
}
|
||||||
return false; // default
|
return false; // default
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVersion = (device) => {
|
export const isDeviceAccessible = (device: ?TrezorDevice): boolean => {
|
||||||
|
if (!device || !device.features) return false;
|
||||||
|
return device.mode === 'normal' && device.firmware !== 'required';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSelectedDevice = (current: ?TrezorDevice, device: ?TrezorDevice): boolean => !!((current && device && (current.path === device.path && current.instance === device.instance)));
|
||||||
|
|
||||||
|
export const getVersion = (device: TrezorDevice): string => {
|
||||||
let version;
|
let version;
|
||||||
if (device.features && device.features.major_version > 1) {
|
if (device.features && device.features.major_version > 1) {
|
||||||
version = 'T';
|
version = 'T';
|
||||||
@ -68,38 +103,23 @@ const getVersion = (device) => {
|
|||||||
return version;
|
return version;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusColor = (deviceStatus) => {
|
export const getStatusColor = (deviceStatus: string): string => {
|
||||||
let color;
|
|
||||||
switch (deviceStatus) {
|
switch (deviceStatus) {
|
||||||
case 'used-in-other-window':
|
|
||||||
color = colors.WARNING_PRIMARY;
|
|
||||||
break;
|
|
||||||
case 'connected':
|
case 'connected':
|
||||||
color = colors.GREEN_PRIMARY;
|
return colors.GREEN_PRIMARY;
|
||||||
break;
|
|
||||||
case 'connected-bootloader':
|
|
||||||
color = colors.WARNING_PRIMARY;
|
|
||||||
break;
|
|
||||||
case 'unacquired':
|
|
||||||
color = colors.WARNING_PRIMARY;
|
|
||||||
break;
|
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
color = colors.ERROR_PRIMARY;
|
return colors.ERROR_PRIMARY;
|
||||||
break;
|
case 'bootloader':
|
||||||
|
case 'initialize':
|
||||||
|
case 'firmware-recommended':
|
||||||
|
case 'used-in-other-window':
|
||||||
|
case 'unacquired':
|
||||||
|
return colors.WARNING_PRIMARY;
|
||||||
|
case 'firmware-required':
|
||||||
case 'unavailable':
|
case 'unavailable':
|
||||||
color = colors.ERROR_PRIMARY;
|
case 'unreadable':
|
||||||
break;
|
return colors.ERROR_PRIMARY;
|
||||||
default:
|
default:
|
||||||
color = colors.TEXT_PRIMARY;
|
return colors.TEXT_PRIMARY;
|
||||||
}
|
}
|
||||||
return color;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
isWebUSB,
|
|
||||||
getStatus,
|
|
||||||
isDisabled,
|
|
||||||
getStatusName,
|
|
||||||
getVersion,
|
|
||||||
getStatusColor,
|
|
||||||
};
|
};
|
@ -1,10 +1,13 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
import LandingPage from './index';
|
import LandingPage from './index';
|
||||||
|
|
||||||
|
|
||||||
export type StateProps = {
|
export type StateProps = {
|
||||||
localStorage: $ElementType<State, 'localStorage'>,
|
localStorage: $ElementType<State, 'localStorage'>,
|
||||||
modal: $ElementType<State, 'modal'>,
|
modal: $ElementType<State, 'modal'>,
|
||||||
@ -34,8 +37,8 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
devices: state.devices,
|
devices: state.devices,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (/* dispatch: Dispatch */): DispatchProps => ({
|
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||||
|
selectFirstAvailableDevice: bindActionCreators(RouterActions.selectFirstAvailableDevice, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(LandingPage);
|
export default connect(mapStateToProps, mapDispatchToProps)(LandingPage);
|
@ -4,7 +4,6 @@ import React, { Component } from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
||||||
import installers from 'constants/bridge';
|
|
||||||
import { Select } from 'components/Select';
|
import { Select } from 'components/Select';
|
||||||
import Link from 'components/Link';
|
import Link from 'components/Link';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
@ -13,24 +12,25 @@ import P from 'components/Paragraph';
|
|||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import ICONS from 'config/icons';
|
import ICONS from 'config/icons';
|
||||||
|
|
||||||
|
import type { State as TrezorConnectState } from 'reducers/TrezorConnectReducer';
|
||||||
|
|
||||||
type InstallTarget = {
|
type InstallTarget = {
|
||||||
id: string;
|
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
signature: ?string;
|
||||||
|
preferred: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
version: string;
|
currentVersion: string;
|
||||||
target: ?InstallTarget;
|
latestVersion: string;
|
||||||
url: string;
|
installers: Array<InstallTarget>;
|
||||||
|
target: InstallTarget;
|
||||||
|
uri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// import type { Props } from './index';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
browserState: {
|
transport: $ElementType<TrezorConnectState, 'transport'>;
|
||||||
osname: string,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const InstallBridgeWrapper = styled.div`
|
const InstallBridgeWrapper = styled.div`
|
||||||
@ -85,21 +85,21 @@ export default class InstallBridge extends Component<Props, State> {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const currentTarget: ?InstallTarget = installers.find(i => i.id === props.browserState.osname);
|
const installers = props.transport.bridge.packages.map(p => ({
|
||||||
this.state = {
|
label: p.name,
|
||||||
version: '2.0.12',
|
value: p.url,
|
||||||
url: 'https://wallet.trezor.io/data/bridge/2.0.12/',
|
signature: p.signature,
|
||||||
target: currentTarget,
|
preferred: p.preferred,
|
||||||
};
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUpdate() {
|
const currentTarget: ?InstallTarget = installers.find(i => i.preferred === true);
|
||||||
if (this.props.browserState.osname && !this.state.target) {
|
this.state = {
|
||||||
const currentTarget: ?InstallTarget = installers.find(i => i.id === this.props.browserState.osname);
|
currentVersion: props.transport.type ? `Your version ${props.transport.version}` : 'Not installed',
|
||||||
this.setState({
|
latestVersion: props.transport.bridge.version.join('.'),
|
||||||
target: currentTarget,
|
installers,
|
||||||
});
|
target: currentTarget || installers[0],
|
||||||
}
|
uri: 'https://wallet.trezor.io/data/',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(value: InstallTarget) {
|
onChange(value: InstallTarget) {
|
||||||
@ -109,23 +109,26 @@ export default class InstallBridge extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.state.target) {
|
const { target } = this.state;
|
||||||
|
if (!target) {
|
||||||
return <Loader text="Loading" size={100} />;
|
return <Loader text="Loading" size={100} />;
|
||||||
}
|
}
|
||||||
const { label } = this.state.target;
|
|
||||||
const url = `${this.state.url}${this.state.target.value}`;
|
|
||||||
|
|
||||||
|
const changelog = this.props.transport.bridge.changelog.map(entry => (
|
||||||
|
<li key={entry}>{entry}</li>
|
||||||
|
));
|
||||||
|
const url = `${this.state.uri}${target.value}`;
|
||||||
return (
|
return (
|
||||||
<InstallBridgeWrapper>
|
<InstallBridgeWrapper>
|
||||||
<TitleHeader>TREZOR Bridge.<BridgeVersion>Version {this.state.version}</BridgeVersion></TitleHeader>
|
<TitleHeader>TREZOR Bridge.<BridgeVersion>{this.state.currentVersion}</BridgeVersion></TitleHeader>
|
||||||
<P>New communication tool to facilitate the connection between your TREZOR and your internet browser.</P>
|
<P>New communication tool to facilitate the connection between your TREZOR and your internet browser.</P>
|
||||||
<DownloadBridgeWrapper>
|
<DownloadBridgeWrapper>
|
||||||
<SelectWrapper
|
<SelectWrapper
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
value={this.state.target}
|
value={target}
|
||||||
onChange={val => this.onChange(val)}
|
onChange={v => this.onChange(v)}
|
||||||
options={installers}
|
options={this.state.installers}
|
||||||
/>
|
/>
|
||||||
<Link href={url}>
|
<Link href={url}>
|
||||||
<DownloadBridgeButton>
|
<DownloadBridgeButton>
|
||||||
@ -134,12 +137,24 @@ export default class InstallBridge extends Component<Props, State> {
|
|||||||
color={colors.WHITE}
|
color={colors.WHITE}
|
||||||
size={30}
|
size={30}
|
||||||
/>
|
/>
|
||||||
Download for {label}
|
Download latest Bridge {this.state.latestVersion}
|
||||||
</DownloadBridgeButton>
|
</DownloadBridgeButton>
|
||||||
</Link>
|
</Link>
|
||||||
</DownloadBridgeWrapper>
|
</DownloadBridgeWrapper>
|
||||||
|
{target.signature && (
|
||||||
|
<P>
|
||||||
|
<Link
|
||||||
|
href={this.state.uri + target.signature}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
isGreen
|
||||||
|
>Check PGP signature
|
||||||
|
</Link>
|
||||||
|
</P>
|
||||||
|
)}
|
||||||
<P>
|
<P>
|
||||||
<LearnMoreText>Learn more about latest version in</LearnMoreText>
|
{ changelog }
|
||||||
|
<LearnMoreText>Learn more about latest versions in</LearnMoreText>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/trezor/trezord-go/blob/master/CHANGELOG.md"
|
href="https://github.com/trezor/trezord-go/blob/master/CHANGELOG.md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -148,6 +163,16 @@ export default class InstallBridge extends Component<Props, State> {
|
|||||||
>Changelog
|
>Changelog
|
||||||
</Link>
|
</Link>
|
||||||
</P>
|
</P>
|
||||||
|
{this.props.transport.type && (
|
||||||
|
<React.Fragment>
|
||||||
|
<P>
|
||||||
|
No, i dont want to upgrade Bridge now,
|
||||||
|
</P>
|
||||||
|
<P>
|
||||||
|
Take me <Link href="#/">back to the wallet</Link>
|
||||||
|
</P>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
</InstallBridgeWrapper>
|
</InstallBridgeWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ export default (props: Props) => {
|
|||||||
<Log />
|
<Log />
|
||||||
<LandingContent>
|
<LandingContent>
|
||||||
{shouldShowUnsupportedBrowser && <BrowserNotSupported />}
|
{shouldShowUnsupportedBrowser && <BrowserNotSupported />}
|
||||||
{shouldShowInstallBridge && <InstallBridge browserState={browserState} />}
|
{shouldShowInstallBridge && <InstallBridge transport={transport} />}
|
||||||
|
|
||||||
{(shouldShowConnectDevice || shouldShowDisconnectDevice) && (
|
{(shouldShowConnectDevice || shouldShowDisconnectDevice) && (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,24 +1,33 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import DeviceHeader from 'components/DeviceHeader';
|
import DeviceHeader from 'components/DeviceHeader';
|
||||||
|
import * as deviceUtils from 'utils/device';
|
||||||
import icons from 'config/icons';
|
import icons from 'config/icons';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
|
import type { TrezorDevice } from 'flowtype';
|
||||||
|
import type { Props as CommonProps } from '../../../common';
|
||||||
|
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
const Wrapper = styled.div``;
|
||||||
const IconClick = styled.div``;
|
const IconClick = styled.div``;
|
||||||
|
|
||||||
class DeviceList extends Component {
|
type Props = {
|
||||||
sortByInstance(a, b) {
|
devices: $ElementType<CommonProps, 'devices'>;
|
||||||
|
selectedDevice: $ElementType<$ElementType<CommonProps, 'wallet'>, 'selectedDevice'>;
|
||||||
|
onSelectDevice: $ElementType<CommonProps, 'onSelectDevice'>;
|
||||||
|
forgetDevice: $ElementType<CommonProps, 'forgetDevice'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DeviceList extends Component<Props> {
|
||||||
|
sortByInstance(a: TrezorDevice, b: TrezorDevice) {
|
||||||
if (!a.instance || !b.instance) return -1;
|
if (!a.instance || !b.instance) return -1;
|
||||||
return a.instance > b.instance ? 1 : -1;
|
return a.instance > b.instance ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectToBootloader(selectedDevice) {
|
|
||||||
this.props.history.push(`/device/${selectedDevice.features.device_id}/bootloader`);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
devices, selectedDevice, onSelectDevice, forgetDevice,
|
devices, selectedDevice, onSelectDevice, forgetDevice,
|
||||||
@ -28,16 +37,11 @@ class DeviceList extends Component {
|
|||||||
{devices
|
{devices
|
||||||
.sort(this.sortByInstance)
|
.sort(this.sortByInstance)
|
||||||
.map(device => (
|
.map(device => (
|
||||||
device !== selectedDevice && (
|
!deviceUtils.isSelectedDevice(selectedDevice, device) && (
|
||||||
<DeviceHeader
|
<DeviceHeader
|
||||||
key={device.state || device.path}
|
key={device.state || device.path}
|
||||||
isBootloader={device.features && device.features.bootloader_mode}
|
isAccessible={deviceUtils.isDeviceAccessible(device)}
|
||||||
onClickWrapper={() => {
|
onClickWrapper={() => {
|
||||||
if (device.features) {
|
|
||||||
if (device.features.bootloader_mode) {
|
|
||||||
this.redirectToBootloader(selectedDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onSelectDevice(device);
|
onSelectDevice(device);
|
||||||
}}
|
}}
|
||||||
onClickIcon={() => forgetDevice(device)}
|
onClickIcon={() => forgetDevice(device)}
|
||||||
@ -69,4 +73,4 @@ class DeviceList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(DeviceList);
|
export default DeviceList;
|
@ -42,8 +42,8 @@ class MenuItems extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showDeviceMenu() {
|
showDeviceMenu() {
|
||||||
const device = this.props.device;
|
const { device } = this.props;
|
||||||
return device && device.features && !device.features.bootloader_mode && device.features.initialized;
|
return device && device.mode === 'normal';
|
||||||
}
|
}
|
||||||
|
|
||||||
showClone() {
|
showClone() {
|
||||||
|
@ -4,7 +4,7 @@ import styled from 'styled-components';
|
|||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import type { TrezorDevice } from 'flowtype';
|
import type { TrezorDevice } from 'flowtype';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
import { isWebUSB } from 'utils/device';
|
import * as deviceUtils from 'utils/device';
|
||||||
import MenuItems from './components/MenuItems';
|
import MenuItems from './components/MenuItems';
|
||||||
import DeviceList from './components/DeviceList';
|
import DeviceList from './components/DeviceList';
|
||||||
|
|
||||||
@ -28,10 +28,6 @@ type DeviceMenuItem = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DeviceMenu extends Component<Props> {
|
class DeviceMenu extends Component<Props> {
|
||||||
mouseDownHandler: (event: MouseEvent) => void;
|
|
||||||
|
|
||||||
blurHandler: (event: FocusEvent) => void;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.mouseDownHandler = this.mouseDownHandler.bind(this);
|
this.mouseDownHandler = this.mouseDownHandler.bind(this);
|
||||||
@ -42,12 +38,33 @@ class DeviceMenu extends Component<Props> {
|
|||||||
window.addEventListener('mousedown', this.mouseDownHandler, false);
|
window.addEventListener('mousedown', this.mouseDownHandler, false);
|
||||||
// window.addEventListener('blur', this.blurHandler, false);
|
// window.addEventListener('blur', this.blurHandler, false);
|
||||||
const { transport } = this.props.connect;
|
const { transport } = this.props.connect;
|
||||||
if (transport && transport.version.indexOf('webusb') >= 0) TrezorConnect.renderWebUSBButton();
|
if (transport.type && transport.version.indexOf('webusb') >= 0) TrezorConnect.renderWebUSBButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { transport } = this.props.connect;
|
const { transport } = this.props.connect;
|
||||||
if (isWebUSB(transport)) TrezorConnect.renderWebUSBButton();
|
if (deviceUtils.isWebUSB(transport)) TrezorConnect.renderWebUSBButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
window.removeEventListener('mousedown', this.mouseDownHandler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeviceMenuClick(item: DeviceMenuItem, device: TrezorDevice): void {
|
||||||
|
if (item.type === 'reload') {
|
||||||
|
this.props.acquireDevice();
|
||||||
|
} else if (item.type === 'forget') {
|
||||||
|
this.props.forgetDevice(device);
|
||||||
|
} else if (item.type === 'clone') {
|
||||||
|
this.props.duplicateDevice(device);
|
||||||
|
} else if (item.type === 'settings') {
|
||||||
|
this.props.toggleDeviceDropdown(false);
|
||||||
|
this.props.gotoDeviceSettings(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blurHandler(): void {
|
||||||
|
this.props.toggleDeviceDropdown(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseDownHandler(event: MouseEvent): void {
|
mouseDownHandler(event: MouseEvent): void {
|
||||||
@ -67,34 +84,16 @@ class DeviceMenu extends Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blurHandler(): void {
|
mouseDownHandler: (event: MouseEvent) => void;
|
||||||
this.props.toggleDeviceDropdown(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeviceMenuClick(item: DeviceMenuItem, device: TrezorDevice): void {
|
blurHandler: (event: FocusEvent) => void;
|
||||||
if (item.type === 'reload') {
|
|
||||||
this.props.acquireDevice();
|
|
||||||
} else if (item.type === 'forget') {
|
|
||||||
this.props.forgetDevice(device);
|
|
||||||
} else if (item.type === 'clone') {
|
|
||||||
this.props.duplicateDevice(device);
|
|
||||||
} else if (item.type === 'settings') {
|
|
||||||
this.props.toggleDeviceDropdown(false);
|
|
||||||
this.props.gotoDeviceSettings(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
|
||||||
window.removeEventListener('mousedown', this.mouseDownHandler, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
showDivider() {
|
showDivider() {
|
||||||
return this.props.devices.length > 1;
|
return this.props.devices.length > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
showMenuItems() {
|
showMenuItems() {
|
||||||
const { selectedDevice } = this.props.wallet;
|
return deviceUtils.isDeviceAccessible(this.props.wallet.selectedDevice);
|
||||||
return selectedDevice && selectedDevice.features;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -113,7 +112,7 @@ class DeviceMenu extends Component<Props> {
|
|||||||
forgetDevice={forgetDevice}
|
forgetDevice={forgetDevice}
|
||||||
/>
|
/>
|
||||||
<ButtonWrapper>
|
<ButtonWrapper>
|
||||||
{isWebUSB(transport) && (
|
{deviceUtils.isWebUSB(transport) && (
|
||||||
<StyledButton isWebUsb>Check for devices</StyledButton>
|
<StyledButton isWebUsb>Check for devices</StyledButton>
|
||||||
)}
|
)}
|
||||||
</ButtonWrapper>
|
</ButtonWrapper>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
@ -6,10 +8,13 @@ import icons from 'config/icons';
|
|||||||
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import DeviceHeader from 'components/DeviceHeader';
|
import DeviceHeader from 'components/DeviceHeader';
|
||||||
|
import * as deviceUtils from 'utils/device';
|
||||||
|
|
||||||
import AccountMenu from './components/AccountMenu';
|
import AccountMenu from './components/AccountMenu';
|
||||||
import CoinMenu from './components/CoinMenu';
|
import CoinMenu from './components/CoinMenu';
|
||||||
import DeviceMenu from './components/DeviceMenu';
|
import DeviceMenu from './components/DeviceMenu';
|
||||||
import StickyContainer from './components/StickyContainer';
|
import StickyContainer from './components/StickyContainer';
|
||||||
|
|
||||||
import type { Props } from './components/common';
|
import type { Props } from './components/common';
|
||||||
|
|
||||||
const Header = styled(DeviceHeader)`
|
const Header = styled(DeviceHeader)`
|
||||||
@ -105,6 +110,7 @@ const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGro
|
|||||||
type State = {
|
type State = {
|
||||||
animationType: ?string;
|
animationType: ?string;
|
||||||
shouldRenderDeviceSelection: boolean;
|
shouldRenderDeviceSelection: boolean;
|
||||||
|
clicked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LeftNavigation extends React.PureComponent<Props, State> {
|
class LeftNavigation extends React.PureComponent<Props, State> {
|
||||||
@ -119,11 +125,11 @@ class LeftNavigation extends React.PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps: Props) {
|
||||||
const { dropdownOpened, selectedDevice } = nextProps.wallet;
|
const { dropdownOpened, selectedDevice } = nextProps.wallet;
|
||||||
const hasNetwork = nextProps.location.state && nextProps.location.state.network;
|
const { location } = nextProps.router;
|
||||||
const hasFeatures = selectedDevice && selectedDevice.features;
|
const hasNetwork = location && location.state.network;
|
||||||
const deviceReady = hasFeatures && !selectedDevice.features.bootloader_mode && selectedDevice.features.initialized;
|
const deviceReady = selectedDevice && selectedDevice.features && selectedDevice.mode === 'normal';
|
||||||
if (dropdownOpened) {
|
if (dropdownOpened) {
|
||||||
this.setState({ shouldRenderDeviceSelection: true });
|
this.setState({ shouldRenderDeviceSelection: true });
|
||||||
} else if (hasNetwork) {
|
} else if (hasNetwork) {
|
||||||
@ -176,22 +182,23 @@ class LeftNavigation extends React.PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDeviceInBootloader = this.props.wallet.selectedDevice.features && this.props.wallet.selectedDevice.features.bootloader_mode;
|
const { selectedDevice } = props.wallet;
|
||||||
|
const isDeviceAccessible = deviceUtils.isDeviceAccessible(selectedDevice);
|
||||||
return (
|
return (
|
||||||
<StickyContainer
|
<StickyContainer
|
||||||
location={this.props.location.pathname}
|
location={props.router.location.pathname}
|
||||||
deviceSelection={this.props.wallet.dropdownOpened}
|
deviceSelection={this.props.wallet.dropdownOpened}
|
||||||
>
|
>
|
||||||
<Header
|
<Header
|
||||||
isSelected
|
isSelected
|
||||||
isHoverable={false}
|
isHoverable={false}
|
||||||
onClickWrapper={() => {
|
onClickWrapper={() => {
|
||||||
if (!isDeviceInBootloader || this.props.devices.length > 1) {
|
if (isDeviceAccessible || this.props.devices.length > 1) {
|
||||||
this.handleOpen();
|
this.handleOpen();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
device={this.props.wallet.selectedDevice}
|
device={this.props.wallet.selectedDevice}
|
||||||
disabled={isDeviceInBootloader && this.props.devices.length === 1}
|
disabled={!isDeviceAccessible && this.props.devices.length === 1}
|
||||||
isOpen={this.props.wallet.dropdownOpened}
|
isOpen={this.props.wallet.dropdownOpened}
|
||||||
icon={(
|
icon={(
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -211,7 +218,7 @@ class LeftNavigation extends React.PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
<Body>
|
<Body>
|
||||||
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
|
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
|
||||||
{!isDeviceInBootloader && menu}
|
{isDeviceAccessible && menu}
|
||||||
</Body>
|
</Body>
|
||||||
<Footer key="sticky-footer">
|
<Footer key="sticky-footer">
|
||||||
<Help>
|
<Help>
|
||||||
@ -241,8 +248,12 @@ LeftNavigation.propTypes = {
|
|||||||
pending: PropTypes.array,
|
pending: PropTypes.array,
|
||||||
|
|
||||||
toggleDeviceDropdown: PropTypes.func,
|
toggleDeviceDropdown: PropTypes.func,
|
||||||
selectedDevice: PropTypes.object,
|
addAccount: PropTypes.func,
|
||||||
|
acquireDevice: PropTypes.func,
|
||||||
|
forgetDevice: PropTypes.func,
|
||||||
|
duplicateDevice: PropTypes.func,
|
||||||
|
gotoDeviceSettings: PropTypes.func,
|
||||||
|
onSelectDevice: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default LeftNavigation;
|
export default LeftNavigation;
|
||||||
|
@ -12,6 +12,7 @@ import type { State } from 'flowtype';
|
|||||||
import Header from 'components/Header';
|
import Header from 'components/Header';
|
||||||
import Footer from 'components/Footer';
|
import Footer from 'components/Footer';
|
||||||
import ModalContainer from 'components/modals';
|
import ModalContainer from 'components/modals';
|
||||||
|
import AppNotifications from 'components/notifications/App';
|
||||||
import ContextNotifications from 'components/notifications/Context';
|
import ContextNotifications from 'components/notifications/Context';
|
||||||
|
|
||||||
import Log from 'components/Log';
|
import Log from 'components/Log';
|
||||||
@ -84,6 +85,7 @@ const Body = styled.div`
|
|||||||
const Wallet = (props: WalletContainerProps) => (
|
const Wallet = (props: WalletContainerProps) => (
|
||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
<Header />
|
<Header />
|
||||||
|
<AppNotifications />
|
||||||
<WalletWrapper>
|
<WalletWrapper>
|
||||||
{props.wallet.selectedDevice && <LeftNavigation />}
|
{props.wallet.selectedDevice && <LeftNavigation />}
|
||||||
<MainContent>
|
<MainContent>
|
||||||
|
@ -1,13 +1,32 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { H1 } from 'components/Heading';
|
import { H1 } from 'components/Heading';
|
||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import Link from 'components/Link';
|
import Link from 'components/Link';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
import { FONT_SIZE } from 'config/variables';
|
import { FONT_SIZE } from 'config/variables';
|
||||||
|
import * as deviceUtils from 'utils/device';
|
||||||
|
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
TrezorDevice,
|
||||||
|
State,
|
||||||
|
Dispatch,
|
||||||
|
} from 'flowtype';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
device: ?TrezorDevice;
|
||||||
|
cancel: typeof RouterActions.selectFirstAvailableDevice,
|
||||||
|
}
|
||||||
|
|
||||||
const Wrapper = styled.section`
|
const Wrapper = styled.section`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -31,7 +50,7 @@ const StyledP = styled(P)`
|
|||||||
padding: 0 0 15px 0;
|
padding: 0 0 15px 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FirmwareUpdate = () => (
|
const FirmwareUpdate = (props: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Image>
|
<Image>
|
||||||
<svg width="181px" height="134px" viewBox="0 0 181 134" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
<svg width="181px" height="134px" viewBox="0 0 181 134" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -114,10 +133,19 @@ const FirmwareUpdate = () => (
|
|||||||
<Link href="https://wallet.trezor.io" target="_blank">
|
<Link href="https://wallet.trezor.io" target="_blank">
|
||||||
<Button>Take me to the old wallet</Button>
|
<Button>Take me to the old wallet</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<StyledNavLink to="/">
|
{deviceUtils.isDeviceAccessible(props.device) && (
|
||||||
I’ll do that later.
|
<StyledNavLink to="/">
|
||||||
</StyledNavLink>
|
I’ll do that later.
|
||||||
|
</StyledNavLink>
|
||||||
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default connect(null, null)(FirmwareUpdate);
|
export default connect(
|
||||||
|
(state: State) => ({
|
||||||
|
device: state.wallet.selectedDevice,
|
||||||
|
}),
|
||||||
|
(dispatch: Dispatch) => ({
|
||||||
|
cancel: bindActionCreators(RouterActions.selectFirstAvailableDevice, dispatch),
|
||||||
|
}),
|
||||||
|
)(FirmwareUpdate);
|
||||||
|
Loading…
Reference in New Issue
Block a user