diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2017-12-12 18:37:16 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2017-12-19 10:32:24 +0100 |
| commit | 035fdd96ba2a1a10885e4d69f2ae890272702f32 (patch) | |
| tree | d8b465aa3e86490f83ea28f03c95600ed329b114 /app/components/SelectLocation.js | |
| parent | 4353ec7bd52585c67324c0fabcd76473d4150600 (diff) | |
| download | mullvadvpn-035fdd96ba2a1a10885e4d69f2ae890272702f32.tar.xz mullvadvpn-035fdd96ba2a1a10885e4d69f2ae890272702f32.zip | |
Render two level location list
Diffstat (limited to 'app/components/SelectLocation.js')
| -rw-r--r-- | app/components/SelectLocation.js | 215 |
1 files changed, 157 insertions, 58 deletions
diff --git a/app/components/SelectLocation.js b/app/components/SelectLocation.js index e0f50a12d4..952d3882ea 100644 --- a/app/components/SelectLocation.js +++ b/app/components/SelectLocation.js @@ -2,11 +2,12 @@ import React, { Component } from 'react'; import { Layout, Container, Header } from './Layout'; import CustomScrollbars from './CustomScrollbars'; -import { servers } from '../config'; -import type { ServerInfo } from '../lib/backend'; +import ChevronDownSVG from '../assets/images/icon-chevron-down.svg'; +import ChevronUpSVG from '../assets/images/icon-chevron-up.svg'; + import type { SettingsReduxState } from '../redux/settings/reducers'; -import type { RelayLocation } from '../lib/ipc-facade'; +import type { RelayLocation, RelayListCity, RelayListCountry } from '../lib/ipc-facade'; export type SelectLocationProps = { settings: SettingsReduxState, @@ -18,54 +19,25 @@ export default class SelectLocation extends Component { props: SelectLocationProps; _selectedCell: ?HTMLElement; - _onSelect(location: RelayLocation) { - if (!this._isSelected(location)) { - this.props.onSelect(location); - } - } + state = { + expanded: ([]: Array<string>), + }; - _isSelected(selectedLocation: RelayLocation) { - const { relaySettings } = this.props.settings; - if(relaySettings.normal) { - const otherLocation = relaySettings.normal.location; + constructor(props: SelectLocationProps, context?: any) { + super(props, context); - if(selectedLocation.country && otherLocation.country && - selectedLocation.country === otherLocation.country) { - return true; - } - - if(Array.isArray(selectedLocation.city) && Array.isArray(otherLocation.city)) { - const selectedCity = selectedLocation.city; - const otherCity = otherLocation.city; - - return selectedCity.length === otherCity.length && - selectedCity.every((v, i) => v === otherCity[i]); + // set initially expanded country based on relaySettings + const relaySettings = this.props.settings.relaySettings; + if(relaySettings.normal) { + const { location } = relaySettings.normal; + if(location === 'any') { + // no-op + } else if(location.country) { + this.state.expanded.push(location.country); + } else if(location.city) { + this.state.expanded.push(location.city[0]); } } - return false; - } - - drawCell(key: string, name: string, selected: bool, icon: ?string, onClick: (e: Event) => void): React.Element<*> { - const classes = ['select-location__cell']; - if(selected) { - classes.push('select-location__cell--selected'); - } - const cellClass = classes.join(' '); - const onRef = selected ? (element) => { - this._selectedCell = element; - } : undefined; - - return ( - <div key={ key } className={ cellClass } onClick={ onClick } ref={ onRef }> - - { icon && <img className="select-location__cell-icon" src={ icon } />} - - <div className="select-location__cell-label">{ name }</div> - - { selected && <img className="select-location__cell-accessory" src="./assets/images/icon-tick.svg" /> } - - </div> - ); } componentDidMount() { @@ -81,7 +53,7 @@ export default class SelectLocation extends Component { } } - render(): React.Element<*> { + render() { return ( <Layout> <Header hidden={ true } style={ 'defaultDark' } /> @@ -99,16 +71,8 @@ export default class SelectLocation extends Component { While connected, your real location is masked with a private and secure location in the selected region </div> - <div className="select-location__separator"></div> - - { (servers: Array<ServerInfo>).map((server) => { - const { address, name, country_code, city_code } = server; - const relayLocation = { - city: [ country_code, city_code ] - }; - const selected = this._isSelected(relayLocation); - const clickHandler = () => this._onSelect(relayLocation); - return this.drawCell(address, name, selected, null, clickHandler); + { this.props.settings.relayLocations.countries.map((relayCountry) => { + return this._renderCountry(relayCountry); }) } </div> @@ -119,4 +83,139 @@ export default class SelectLocation extends Component { </Layout> ); } + + _onSelect(location: RelayLocation) { + if (!this._isSelected(location)) { + this.props.onSelect(location); + } + } + + _isSelected(selectedLocation: RelayLocation) { + const { relaySettings } = this.props.settings; + if(relaySettings.normal) { + const otherLocation = relaySettings.normal.location; + + if(selectedLocation.country && otherLocation.country && + selectedLocation.country === otherLocation.country) { + return true; + } + + if(Array.isArray(selectedLocation.city) && Array.isArray(otherLocation.city)) { + const selectedCity = selectedLocation.city; + const otherCity = otherLocation.city; + + return selectedCity.length === otherCity.length && + selectedCity.every((v, i) => v === otherCity[i]); + } + } + return false; + } + + _toggleCollapse = (countryCode: string) => { + this.setState((state) => { + const expanded = state.expanded.slice(); + const index = expanded.indexOf(countryCode); + if(index === -1) { + expanded.push(countryCode); + } else { + expanded.splice(index, 1); + } + return { expanded }; + }); + } + + _relayStatusIndicator(active: boolean) { + const statusClass = active ? 'select-location-relay-status--active' : 'select-location-relay-status--inactive'; + + return (<div className={ 'select-location-relay-status ' + statusClass }></div>); + } + + _renderCountry(relayCountry: RelayListCountry) { + const countryHasActiveRelays = relayCountry.cities.some((relayCity) => { + return relayCity.has_active_relays; + }); + + const isSelected = this._isSelected({ country: relayCountry.code }); + + // either expanded by user or when the city selected within the country + const isExpanded = this.state.expanded.includes(relayCountry.code); + + const handleSelect = () => this._onSelect({ country: relayCountry.code }); + const handleCollapse = (e) => { + this._toggleCollapse(relayCountry.code); + e.stopPropagation(); + }; + + const countryClass = 'select-location__cell ' + + (isSelected ? 'select-location__cell--selected' : ''); + + const onRef = isSelected ? (element) => { + this._selectedCell = element; + } : undefined; + + return ( + <div key={ relayCountry.code } className="select-location__country"> + <div className={ countryClass } + onClick={ handleSelect } + ref={ onRef }> + <div className="select-location__cell-content"> + + <div className="select-location__cell-icon"> + { isSelected ? + <img src="./assets/images/icon-tick.svg" /> : + this._relayStatusIndicator(countryHasActiveRelays) } + </div> + + <div className="select-location__cell-label">{ relayCountry.name }</div> + </div> + + { countryHasActiveRelays && <button type="button" className="select-location__collapse-button" onClick={ handleCollapse }> + { isExpanded ? + <ChevronUpSVG className="select-location__collapse-icon" /> : + <ChevronDownSVG className="select-location__collapse-icon" /> } + </button> } + + </div> + + { isExpanded && countryHasActiveRelays && relayCountry.cities.length > 0 && + (<div className="select-location__cities"> + { relayCountry.cities.map((relayCity) => this._renderCity(relayCountry.code, relayCity)) } + </div>) + } + </div> + ); + } + + _renderCity(countryCode: string, relayCity: RelayListCity) { + const relayLocation: RelayLocation = { city: [countryCode, relayCity.code] }; + + const handleSelect = () => this._onSelect(relayLocation); + + const isSelected = this._isSelected(relayLocation); + const key = countryCode + '_' + relayCity.code; + + const cityClass = 'select-location__sub-cell ' + + (isSelected ? 'select-location__sub-cell--selected' : ''); + + const onRef = isSelected ? (element) => { + this._selectedCell = element; + } : undefined; + + return ( + <div key={ key } + className={ cityClass } + onClick={ handleSelect } + ref={ onRef }> + + <div className="select-location__cell-icon"> + { isSelected ? + <img src="./assets/images/icon-tick.svg" /> : + this._relayStatusIndicator(relayCity.has_active_relays) } + </div> + + <div className="select-location__cell-label">{ relayCity.name }</div> + </div> + ); + } + } |
