// @flow import moment from 'moment'; import * as React from 'react'; import { Layout, Container, Header } from './Layout'; import { Component, Text, View, Types } from 'reactxp'; import Img from './Img'; import { TransparentButton, RedTransparentButton, GreenButton, Label } from './styled'; import Accordion from './Accordion'; import styles from './ConnectStyles'; import { BackendError } from '../lib/backend'; import Map from './Map'; import type { HeaderBarStyle } from './HeaderBar'; import type { ConnectionReduxState } from '../redux/connection/reducers'; export type ConnectProps = { connection: ConnectionReduxState, accountExpiry: string, selectedRelayName: string, onSettings: () => void, onSelectLocation: () => void, onConnect: () => void, onCopyIP: () => void, onDisconnect: () => void, onExternalLink: (type: string) => void, }; type ConnectState = { showCopyIPMessage: boolean, mapOffset: [number, number], }; export default class Connect extends Component { state = { showCopyIPMessage: false, mapOffset: [0, 0], }; _copyTimer: ?TimeoutID; shouldComponentUpdate(nextProps: ConnectProps, nextState: ConnectState) { const { connection: prevConnection, ...otherPrevProps } = this.props; const { connection: nextConnection, ...otherNextProps } = nextProps; const prevState = this.state; return ( // shallow compare the connection !shallowCompare(prevConnection, nextConnection) || !shallowCompare(otherPrevProps, otherNextProps) || prevState.mapOffset[0] !== nextState.mapOffset[0] || prevState.mapOffset[1] !== nextState.mapOffset[1] || prevState.showCopyIPMessage !== nextState.showCopyIPMessage ); } componentWillUnmount() { if (this._copyTimer) { clearTimeout(this._copyTimer); } } render() { const error = this.displayError(); const child = error ? this.renderError(error) : this.renderMap(); return (
{child} ); } renderError(error: BackendError) { return ( {error.title} {error.message} {error.type === 'NO_CREDIT' ? ( ) : null} ); } _getMapProps() { const { longitude, latitude, status } = this.props.connection; // when the user location is known if (typeof longitude === 'number' && typeof latitude === 'number') { return { center: [longitude, latitude], // do not show the marker when connecting showMarker: status !== 'connecting', markerStyle: status === 'connected' ? 'secure' : 'unsecure', // zoom in when connected zoomLevel: status === 'connected' ? 'low' : 'medium', // a magic offset to align marker with spinner offset: [0, 123], }; } else { return { center: [0, 0], showMarker: false, markerStyle: 'unsecure', // show the world when user location is not known zoomLevel: 'high', // remove the offset since the marker is hidden offset: [0, 0], }; } } _updateMapOffset = (spinnerNode: ?HTMLElement) => { if (spinnerNode) { // calculate the vertical offset from the center of the map // to shift the center of the map upwards to align the centers // of spinner and marker on the map const y = spinnerNode.offsetTop + spinnerNode.clientHeight * 0.5; this.setState({ mapOffset: [0, y], }); } }; renderMap() { let [isConnecting, isConnected, isDisconnected] = [false, false, false]; switch (this.props.connection.status) { case 'connecting': isConnecting = true; break; case 'connected': isConnected = true; break; case 'disconnected': isDisconnected = true; break; } return ( {this._renderIsBlockingInternetMessage()} {/* show spinner when connecting */} {isConnecting ? ( ) : null} {this.networkSecurityMessage()} {/* ********************************** Begin: Location block ********************************** */} {/* location when connecting or disconnected */} {isConnecting || isDisconnected ? ( {this.props.connection.country} ) : null} {/* location when connected */} {isConnected ? ( {this.props.connection.city} {this.props.connection.city &&
} {this.props.connection.country}
) : null} {/* ********************************** End: Location block ********************************** */} {isConnected || isDisconnected ? ( {this.state.showCopyIPMessage ? 'IP copied to clipboard!' : this.props.connection.ip} ) : null}
{/* ********************************** Begin: Footer block ********************************** */} {/* footer when disconnected */} {isDisconnected ? ( Secure my connection ) : null} {/* footer when connecting */} {isConnecting ? ( Switch location Cancel ) : null} {/* footer when connected */} {isConnected ? ( Switch location Disconnect ) : null} {/* ********************************** End: Footer block ********************************** */}
); } _renderIsBlockingInternetMessage() { return (   BLOCKING INTERNET ); } // Handlers onExternalLink(type: string) { this.props.onExternalLink(type); } onIPAddressClick() { this._copyTimer && clearTimeout(this._copyTimer); this._copyTimer = setTimeout(() => this.setState({ showCopyIPMessage: false }), 3000); this.setState({ showCopyIPMessage: true }); this.props.onCopyIP(); } // Private headerStyle(): HeaderBarStyle { switch (this.props.connection.status) { case 'disconnected': return 'error'; case 'connecting': case 'connected': return 'success'; } throw new Error('Invalid ConnectionState'); } networkSecurityStyle(): Types.Style { let classes = [styles.status_security]; if (this.props.connection.status === 'connected') { classes.push(styles.status_security__secure); } else if (this.props.connection.status === 'disconnected') { classes.push(styles.status_security__unsecured); } return classes; } networkSecurityMessage(): string { switch (this.props.connection.status) { case 'connected': return 'SECURE CONNECTION'; case 'connecting': return 'CREATING SECURE CONNECTION'; default: return 'UNSECURED CONNECTION'; } } ipAddressStyle(): Types.Style { var classes = [styles.status_ipaddress]; if (this.props.connection.status === 'connecting') { classes.push(styles.status_ipaddress__invisible); } return classes; } displayError(): ?BackendError { // Offline? if (!this.props.connection.isOnline) { return new BackendError('NO_INTERNET'); } // No credit? const expiry = this.props.accountExpiry; if (expiry && moment(expiry).isSameOrBefore(moment())) { return new BackendError('NO_CREDIT'); } return null; } } function shallowCompare(lhs: Object, rhs: Object) { const keys = Object.keys(lhs); return keys.length === Object.keys(rhs).length && keys.every((key) => lhs[key] === rhs[key]); }