diff options
| -rw-r--r-- | app/components/Connect.js | 122 | ||||
| -rw-r--r-- | app/containers/ConnectPage.js | 37 | ||||
| -rw-r--r-- | test/components/Connect.spec.js | 45 |
3 files changed, 92 insertions, 112 deletions
diff --git a/app/components/Connect.js b/app/components/Connect.js index 83caaa0299..d29ac57c44 100644 --- a/app/components/Connect.js +++ b/app/components/Connect.js @@ -11,13 +11,11 @@ import ChevronRightSVG from '../assets/images/icon-chevron.svg'; import type { HeaderBarStyle } from './HeaderBar'; import type { ConnectionReduxState } from '../redux/connection/reducers'; -import type { SettingsReduxState } from '../redux/settings/reducers'; -import type { RelayLocation } from '../lib/ipc-facade'; export type ConnectProps = { - accountExpiry: string, connection: ConnectionReduxState, - settings: SettingsReduxState, + accountExpiry: string, + selectedRelayName: string, onSettings: () => void, onSelectLocation: () => void, onConnect: () => void, @@ -26,15 +24,37 @@ export type ConnectProps = { onExternalLink: (type: string) => void, }; +type ConnectState = { + showCopyIPMessage: boolean, + mapOffset: [number, number], +}; export default class Connect extends Component { props: ConnectProps; - state = { - showCopyIPMessage: false + state: ConnectState = { + showCopyIPMessage: false, + mapOffset: [0, 0], }; _copyTimer: ?number; + 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); @@ -46,7 +66,7 @@ export default class Connect extends Component { }); } - render(): React.Element<*> { + render() { const error = this.displayError(); const child = error ? this.renderError(error) : this.renderMap(); @@ -60,7 +80,7 @@ export default class Connect extends Component { ); } - renderError(error: BackendError): React.Element<*> { + renderError(error: BackendError) { return ( <div className="connect"> <div className="connect__status"> @@ -87,86 +107,46 @@ export default class Connect extends Component { ); } - _findRelayName(relay: RelayLocation): ?string { - const countries = this.props.settings.relayLocations; - const countryPredicate = (countryCode) => (country) => country.code === countryCode; - - if(relay.country) { - const country = countries.find(countryPredicate(relay.country)); - if(country) { - return country.name; - } - } else if(relay.city) { - const [countryCode, cityCode] = relay.city; - const country = countries.find(countryPredicate(countryCode)); - if(country) { - const city = country.cities.find((city) => city.code === cityCode); - if(city) { - return city.name; - } - } - } - return null; - } - - _getLocationName(): string { - const { relaySettings } = this.props.settings; - if(relaySettings.normal) { - const location = relaySettings.normal.location; - if(location === 'any') { - return 'Automatic'; - } else { - return this._findRelayName(location) || 'Unknown'; - } - } else if(relaySettings.custom_tunnel_endpoint) { - return 'Custom'; - } else { - throw new Error('Unsupported relay settings.'); - } - } - _getMapProps() { const { longitude, latitude, status } = this.props.connection; - const defaultProps = { - width: 320, - height: 429, - markerImagePath: status === 'connected' ? - './assets/images/location-marker-secure.svg' : - './assets/images/location-marker-unsecure.svg' - }; // when the user location is known if(typeof(longitude) === 'number' && typeof(latitude) === 'number') { return { - ...defaultProps, - 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' ? 40 : 20, - + zoomLevel: status === 'connected' ? 'low' : 'medium', // a magic offset to align marker with spinner offset: [0, 123], }; } else { return { - ...defaultProps, - center: [0, 0], showMarker: false, - + markerStyle: 'unsecure', // show the world when user location is not known - zoomLevel: 1, - + 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) { @@ -178,7 +158,7 @@ export default class Connect extends Component { return ( <div className="connect"> <div className="connect__map"> - <Map { ...this._getMapProps() } /> + <Map style={{ width: '100%', height: '100%' }} { ...this._getMapProps() } /> </div> <div className="connect__container"> @@ -186,7 +166,7 @@ export default class Connect extends Component { <div className="connect__status"> { /* show spinner when connecting */ } <div className={ this.spinnerClass() }> - <img src="./assets/images/icon-spinner.svg" alt="" /> + <img src="./assets/images/icon-spinner.svg" alt="" ref={ this._updateMapOffset } /> </div> <div className={ this.networkSecurityClass() }>{ this.networkSecurityMessage() }</div> @@ -243,7 +223,7 @@ export default class Connect extends Component { <div className="connect__footer"> <div className="connect__row"> <button className="connect__server button button--neutral button--blur" onClick={ this.props.onSelectLocation }> - <div className="connect__server-label">{ this._getLocationName() }</div> + <div className="connect__server-label">{ this.props.selectedRelayName }</div> <div className="connect__server-chevron"><ChevronRightSVG /></div> </button> </div> @@ -384,3 +364,11 @@ export default class Connect extends Component { 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]) + ); +}
\ No newline at end of file diff --git a/app/containers/ConnectPage.js b/app/containers/ConnectPage.js index d9f3bd81e7..9ce245a927 100644 --- a/app/containers/ConnectPage.js +++ b/app/containers/ConnectPage.js @@ -11,11 +11,46 @@ import { openLink } from '../lib/platform'; import type { ReduxState, ReduxDispatch } from '../redux/store'; import type { SharedRouteProps } from '../routes'; +import type { RelaySettingsRedux, RelayLocationRedux } from '../redux/settings/reducers'; + +function getRelayName(relaySettings: RelaySettingsRedux, relayLocations: Array<RelayLocationRedux>): string { + if(relaySettings.normal) { + const location = relaySettings.normal.location; + + if(location === 'any') { + return 'Automatic'; + } else if(location.country) { + const country = relayLocations.find(({ code }) => code === location.country); + if(country) { + return country.name; + } + } else if(location.city) { + const [countryCode, cityCode] = location.city; + const country = relayLocations.find(({ code }) => code === countryCode); + if(country) { + const city = country.cities.find(({ code }) => code === cityCode); + if(city) { + return city.name; + } + } + } + + return 'Unknown'; + } else if(relaySettings.custom_tunnel_endpoint) { + return 'Custom'; + } else { + throw new Error('Unsupported relay settings.'); + } +} + const mapStateToProps = (state: ReduxState) => { return { accountExpiry: state.account.expiry, + selectedRelayName: getRelayName( + state.settings.relaySettings, + state.settings.relayLocations + ), connection: state.connection, - settings: state.settings, }; }; diff --git a/test/components/Connect.spec.js b/test/components/Connect.spec.js index c92baf51d4..9c6ed0d109 100644 --- a/test/components/Connect.spec.js +++ b/test/components/Connect.spec.js @@ -98,28 +98,6 @@ describe('components/Connect', () => { expect(ipAddr.text()).to.contain('4.3.2.1'); }); - it('shows the country name in the location switcher', () => { - const component = renderWithProps({ - connection: { - ...defaultProps.connection, - status: 'disconnected', - }, - settings: { - ...defaultProps.settings, - relaySettings: { - normal: { - location: { city: ['se', 'mma'] }, - protocol: 'any', - port: 'any', - } - }, - }, - }); - - const locationSwitcher = component.find('.connect__server'); - expect(locationSwitcher.text()).to.contain('Malmö'); - }); - it('invokes the onConnect prop', (done) => { const component = renderWithProps({ onConnect: () => done(), @@ -142,28 +120,7 @@ const defaultProps: ConnectProps = { onDisconnect: () => {}, onExternalLink: () => {}, accountExpiry: '', - settings: { - relaySettings: { - normal: { - location: 'any', - protocol: 'any', - port: 'any', - } - }, - relayLocations: [{ - name: 'Sweden', - code: 'se', - hasActiveRelays: true, - cities: [{ - name: 'Malmö', - code: 'mma', - latitude: 0, - longitude: 0, - hasActiveRelays: true, - }] - }], - allowLan: false, - }, + selectedRelayName: '', connection: { status: 'disconnected', isOnline: true, |
