import assert from 'assert'; import moment from 'moment'; import React, { Component, PropTypes } from 'react'; import { If, Then, Else } from 'react-if'; import ReactMapboxGl, { Marker } from 'react-mapbox-gl'; import cheapRuler from 'cheap-ruler'; import { Layout, Container, Header } from './Layout'; import { mapbox as mapboxConfig } from '../config'; import Backend from '../lib/backend'; import { ConnectionState } from '../enums'; import ExternalLinkSVG from '../assets/images/icon-extLink.svg'; export default class Connect extends Component { static propTypes = { settings: PropTypes.object.isRequired, onSettings: PropTypes.func.isRequired, onConnect: PropTypes.func.isRequired, onCopyIP: PropTypes.func.isRequired, onDisconnect: PropTypes.func.isRequired, onExternalLink: PropTypes.func.isRequired, getServerInfo: PropTypes.func.isRequired }; constructor() { super(); // timer used along with `state.showCopyIPMessage` this._copyTimer = null; this.state = { isFirstPass: true, // this flag is used together with timer to display // a message that IP address has been copied to clipboard showCopyIPMessage: false }; } // Component Lifecycle componentDidMount() { this.setState({ isFirstPass: false }); } componentWillUnmount() { this.setState({ isFirstPass: true }); } render() { let error = null; // check if user out of time // this is by far the simplest implementation // later on backend will notify us and disconnect VPN etc.. if(moment(this.props.user.paidUntil).isSameOrBefore(moment())) { error = new Backend.Error(Backend.ErrorType.noCredit); } // Offline? if(this.props.connect.isOnline === false) { error = new Backend.Error(Backend.ErrorType.noInternetConnection); } return (
{ () => this.renderError(error) } { ::this.renderMap } ); } renderError(error) { return (
{ error.title }
{ error.message }
); } renderMap() { const preferredServer = this.props.settings.preferredServer; const serverInfo = this.props.getServerInfo(preferredServer); const isConnecting = this.props.connect.status === ConnectionState.connecting; const isConnected = this.props.connect.status === ConnectionState.connected; const isDisconnected = this.props.connect.status === ConnectionState.disconnected; const altitude = (isConnecting ? 300 : 100) * 1000; const displayLocation = this.displayLocation(); const bounds = this.getBounds(displayLocation.location, altitude); const userLocation = this.toLngLat(this.props.user.location); const serverLocation = this.toLngLat(serverInfo.location); const mapBounds = this.toLngLatBounds(bounds); const mapBoundsOptions = { offset: [0, -113], animate: !this.state.isFirstPass }; return (
{ /* show spinner when connecting */ }
{ this.networkSecurityMessage() }
{ /* ********************************** Begin: Location block ********************************** */ } { /* location when connecting */ }
{ 'Fastest' } { 'Nearest' } { /* silly but react-if does not have ElseIf */ } { displayLocation.country }
{ /* location when connected */ }
{ displayLocation.city }
{ displayLocation.country }
{ /* location when disconnected */ }
{ displayLocation.country }
{ /* ********************************** End: Location block ********************************** */ }
{ 'IP copied to clipboard!' } { this.props.connect.clientIp }
{ /* ********************************** Begin: Footer block ********************************** */ } { /* footer when disconnected */ }
Connect to
{ serverInfo.name }
{ /* footer when connecting */ }
{ /* footer when connected */ }
{ /* ********************************** End: Footer block ********************************** */ }
); } // Handlers onConnect() { const server = this.props.settings.preferredServer; const serverInfo = this.props.getServerInfo(server); this.props.onConnect(serverInfo.address); } onExternalLink(type) { 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() { const S = Header.Style; switch(this.props.connect.status) { case ConnectionState.connecting: case ConnectionState.disconnected: return S.error; case ConnectionState.connected: return S.success; } } networkSecurityClass() { let classes = ['connect__status-security']; if(this.props.connect.status === ConnectionState.connected) { classes.push('connect__status-security--secure'); } else if(this.props.connect.status === ConnectionState.disconnected) { classes.push('connect__status-security--unsecured'); } return classes.join(' '); } networkSecurityMessage() { switch(this.props.connect.status) { case ConnectionState.connected: return 'Secure connection'; case ConnectionState.connecting: return 'Creating secure connection'; default: return 'Unsecured connection'; } } spinnerClass() { var classes = ['connect__status-icon']; if(this.props.connect.status !== ConnectionState.connecting) { classes.push('connect__status-icon--hidden'); } return classes.join(' '); } ipAddressClass() { var classes = ['connect__status-ipaddress']; if(this.props.connect.status === ConnectionState.connecting) { classes.push('connect__status-ipaddress--invisible'); } return classes.join(' '); } displayLocation() { if(this.props.connect.status === ConnectionState.disconnected) { const { location, country, city } = this.props.user; return { location, country, city }; } const preferredServer = this.props.settings.preferredServer; return this.props.getServerInfo(preferredServer); } // Geo helpers getBounds(center, altitude) { const ruler = cheapRuler(center[0], 'meters'); return ruler.bufferPoint(center, altitude); } toLngLat(pos) { assert(pos.length === 2, 'wrong number of coordinates in position'); return [ pos[1], pos[0] ]; } toLngLatBounds(bounds) { assert(bounds.length % 2 === 0, 'wrong number of sides in bounds'); let result = []; for(let i = 0; i < bounds.length; i += 2) { result.push(bounds.slice(i, i + 2).reverse()); } return result; } }