import * as React from 'react'; import ReactDOM from 'react-dom'; import { Component, View } from 'reactxp'; import { countries, messages, relayLocations } from '../../shared/gettext'; import CustomScrollbars from './CustomScrollbars'; import { Container, Layout } from './Layout'; import { CloseBarItem, NavigationBar, NavigationContainer, NavigationScrollbars, TitleBarItem, } from './NavigationBar'; import styles from './SelectLocationStyles'; import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; import CityRow from './CityRow'; import CountryRow from './CountryRow'; import RelayRow from './RelayRow'; import { compareRelayLocation, compareRelayLocationLoose, RelayLocation, } from '../../shared/daemon-rpc-types'; import { IRelayLocationRedux, RelaySettingsRedux } from '../redux/settings/reducers'; interface IProps { relaySettings: RelaySettingsRedux; relayLocations: IRelayLocationRedux[]; onClose: () => void; onSelect: (location: RelayLocation) => void; } interface IState { selectedLocation?: RelayLocation; expandedItems: RelayLocation[]; } export default class SelectLocation extends Component { public state: IState = { expandedItems: [], }; private selectedCellRef = React.createRef(); private scrollViewRef = React.createRef(); constructor(props: IProps) { super(props); if ('normal' in this.props.relaySettings) { const location = this.props.relaySettings.normal.location; if (typeof location === 'object') { const expandedItems: RelayLocation[] = []; if ('city' in location) { expandedItems.push({ country: location.city[0] }); } else if ('hostname' in location) { expandedItems.push({ country: location.hostname[0] }); expandedItems.push({ city: [location.hostname[0], location.hostname[1]] }); } this.state = { selectedLocation: location, expandedItems, }; } } } public componentDidUpdate(oldProps: IProps) { const currentLocation = this.state.selectedLocation; let newLocation = 'normal' in this.props.relaySettings ? this.props.relaySettings.normal.location : undefined; let oldLocation = 'normal' in oldProps.relaySettings ? oldProps.relaySettings.normal.location : undefined; if (newLocation === 'any') { newLocation = undefined; } if (oldLocation === 'any') { oldLocation = undefined; } if ( !compareRelayLocationLoose(oldLocation, newLocation) && !compareRelayLocationLoose(currentLocation, newLocation) ) { this.setState({ selectedLocation: newLocation }); } } public componentDidMount() { // restore scroll to the selected cell const cell = this.selectedCellRef.current; const scrollView = this.scrollViewRef.current; if (scrollView && cell) { // TODO: Fix the browser specific code const cellDOMNode = ReactDOM.findDOMNode(cell as Element); if (cellDOMNode instanceof HTMLElement) { scrollView.scrollToElement(cellDOMNode, 'middle'); } } } public render() { return ( {// TRANSLATORS: Title label in navigation bar messages.pgettext('select-location-nav', 'Select location')} {messages.pgettext('select-location-view', 'Select location')} {messages.pgettext( 'select-location-view', 'While connected, your real location is masked with a private and secure location in the selected region', )} {this.props.relayLocations.map((relayCountry) => { const countryLocation: RelayLocation = { country: relayCountry.code }; return ( {relayCountry.cities.map((relayCity) => { const cityLocation: RelayLocation = { city: [relayCountry.code, relayCity.code], }; return ( {relayCity.relays.map((relay) => { const relayLocation: RelayLocation = { hostname: [relayCountry.code, relayCity.code, relay.hostname], }; return ( ); })} ); })} ); })} ); } private isExpanded(relayLocation: RelayLocation) { return this.state.expandedItems.some((location) => compareRelayLocation(location, relayLocation), ); } private isSelected(relayLocation: RelayLocation) { return compareRelayLocationLoose(this.state.selectedLocation, relayLocation); } private handleSelection = (location: RelayLocation) => { if (!compareRelayLocationLoose(this.state.selectedLocation, location)) { this.setState({ selectedLocation: location }, () => { this.props.onSelect(location); }); } }; private handleExpand = (location: RelayLocation, expand: boolean) => { this.setState((state) => { const expandedItems = state.expandedItems.filter( (item) => !compareRelayLocation(item, location), ); if (expand) { expandedItems.push(location); } return { ...state, expandedItems, }; }); }; private getCommonCellProps( location: RelayLocation, ): { location: RelayLocation; selected: boolean; ref?: React.RefObject } { const selected = this.isSelected(location); const ref = selected ? (this.selectedCellRef as React.RefObject) : undefined; return { ref, selected, location }; } } function getLocationKey(location: RelayLocation): string { const components: string[] = []; if ('city' in location) { components.push(...location.city); } else if ('country' in location) { components.push(location.country); } else if ('hostname' in location) { components.push(...location.hostname); } return ([] as string[]).concat(components).join('-'); }