import * as React from 'react'; import ReactDOM from 'react-dom'; import { Component, View } from 'reactxp'; import { colors } from '../../config.json'; import { LiftedConstraint, RelayLocation } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import { IRelayLocationRedux } from '../redux/settings/reducers'; import { LocationScope } from '../redux/userinterface/reducers'; import * as Cell from './Cell'; import CustomScrollbars from './CustomScrollbars'; import { Container, Layout } from './Layout'; import LocationList from './LocationList'; import { CloseBarItem, NavigationBar, NavigationContainer, NavigationItems, NavigationScrollbars, ScopeBar, ScopeBarItem, TitleBarItem, } from './NavigationBar'; import styles from './SelectLocationStyles'; import { HeaderSubTitle } from './SettingsHeader'; interface IProps { locationScope: LocationScope; selectedExitLocation?: RelayLocation; selectedBridgeLocation?: LiftedConstraint; relayLocations: IRelayLocationRedux[]; bridgeLocations: IRelayLocationRedux[]; allowBridgeSelection: boolean; onClose: () => void; onChangeLocationScope: (location: LocationScope) => void; onSelectExitLocation: (location: RelayLocation) => void; onSelectBridgeLocation: (location: RelayLocation) => void; onSelectClosestToExit: () => void; } export default class SelectLocation extends Component { private scrollView = React.createRef(); private exitLocationList = React.createRef(); private bridgeLocationList = React.createRef(); private scrollPositionByScope: { [index: number]: [number, number] } = {}; public componentDidMount() { this.scrollToSelectedCell(); } public componentDidUpdate(prevProps: IProps, _prevState: {}, snapshot?: [number, number]) { if (this.props.locationScope !== prevProps.locationScope) { this.restoreScrollPosition(this.props.locationScope); if (snapshot) { this.saveScrollPosition(prevProps.locationScope, snapshot); } } } public getSnapshotBeforeUpdate(_prevProps: IProps) { const scrollView = this.scrollView.current; if (scrollView) { return scrollView.getScrollPosition(); } else { return undefined; } } public render() { return ( {// TRANSLATORS: Title label in navigation bar messages.pgettext('select-location-nav', 'Select location')} {this.props.allowBridgeSelection ? messages.pgettext( 'select-location-view', 'While connected, your traffic will be routed through two secure locations, the entry point (a bridge server) and the exit point (a VPN server).', ) : messages.pgettext( 'select-location-view', 'While connected, your real location is masked with a private and secure location in the selected region.', )} {this.props.allowBridgeSelection && ( {messages.pgettext('select-location-nav', 'Entry')} {messages.pgettext('select-location-nav', 'Exit')} )} {this.props.locationScope === LocationScope.relay ? ( ) : ( )} ); } public saveScrollPosition(scope: LocationScope, position: [number, number]) { this.scrollPositionByScope[scope] = position; } public restoreScrollPosition(scope: LocationScope) { const prevScrollPos = this.scrollPositionByScope[scope]; if (prevScrollPos) { this.scrollToPosition(...prevScrollPos); } else { this.scrollToSelectedCell(); } } private scrollToPosition(x: number, y: number) { const scrollView = this.scrollView.current; if (scrollView) { scrollView.scrollTo(x, y); } } private scrollToSelectedCell() { const ref = this.props.locationScope === LocationScope.relay ? this.exitLocationList : this.bridgeLocationList; const locationList = ref.current; if (locationList) { const cell = locationList.selectedCell.current; const scrollView = this.scrollView.current; if (scrollView) { if (cell) { const cellDOMNode = ReactDOM.findDOMNode(cell as Element); if (cellDOMNode instanceof HTMLElement) { scrollView.scrollToElement(cellDOMNode, 'middle'); } } else { scrollView.scrollToTop(); } } } } } interface IClosestToExitCellProps { isSelected: boolean; onSelect: () => void; } function ClosestToExitCell(props: IClosestToExitCellProps) { return ( {messages.pgettext('select-location-view', 'Closest to exit server')} ); }