summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-01-10 13:47:43 +0100
committerAndrej Mihajlov <and@mullvad.net>2019-01-11 14:19:00 +0100
commitaded6fd25cdf52b26e11655a9fa4cb17e385e894 (patch)
tree8e7edb4f53998198e8caa171cf83acffc2955114
parenta5d2c751f00a80e6ee5465fb7eaa7356d9d5f1ef (diff)
downloadmullvadvpn-aded6fd25cdf52b26e11655a9fa4cb17e385e894.tar.xz
mullvadvpn-aded6fd25cdf52b26e11655a9fa4cb17e385e894.zip
Optimize the SelectLocation view
-rw-r--r--gui/packages/desktop/src/renderer/components/ChevronButton.js35
-rw-r--r--gui/packages/desktop/src/renderer/components/CityRow.js107
-rw-r--r--gui/packages/desktop/src/renderer/components/CountryRow.js113
-rw-r--r--gui/packages/desktop/src/renderer/components/RelayRow.js56
-rw-r--r--gui/packages/desktop/src/renderer/components/RelayStatusIndicator.js53
-rw-r--r--gui/packages/desktop/src/renderer/components/SelectLocation.js358
-rw-r--r--gui/packages/desktop/src/renderer/components/SelectLocationStyles.js74
7 files changed, 501 insertions, 295 deletions
diff --git a/gui/packages/desktop/src/renderer/components/ChevronButton.js b/gui/packages/desktop/src/renderer/components/ChevronButton.js
new file mode 100644
index 0000000000..d594c381cb
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/components/ChevronButton.js
@@ -0,0 +1,35 @@
+// @flow
+
+import * as React from 'react';
+import { Component, Styles } from 'reactxp';
+import * as Cell from './Cell';
+import { colors } from '../../config';
+
+type Props = {
+ up: boolean,
+ onPress: () => void,
+};
+
+const style = Styles.createViewStyle({
+ flex: 0,
+ alignSelf: 'stretch',
+ justifyContent: 'center',
+ paddingRight: 16,
+ paddingLeft: 16,
+});
+
+export default class ChevronButton extends Component<Props> {
+ render() {
+ return (
+ <Cell.Icon
+ style={[style, this.props.style]}
+ tintColor={colors.white80}
+ tintHoverColor={colors.white}
+ onPress={this.props.onPress}
+ source={this.props.up ? 'icon-chevron-up' : 'icon-chevron-down'}
+ height={24}
+ width={24}
+ />
+ );
+ }
+}
diff --git a/gui/packages/desktop/src/renderer/components/CityRow.js b/gui/packages/desktop/src/renderer/components/CityRow.js
new file mode 100644
index 0000000000..f375969dcf
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/components/CityRow.js
@@ -0,0 +1,107 @@
+// @flow
+
+import * as React from 'react';
+import { Component, Styles, View } from 'reactxp';
+import { Accordion } from '@mullvad/components';
+import * as Cell from './Cell';
+import RelayRow from './RelayRow';
+import RelayStatusIndicator from './RelayStatusIndicator';
+import ChevronButton from './ChevronButton';
+import { colors } from '../../config';
+
+type Props = {
+ name: string,
+ hasActiveRelays: boolean,
+ selected: boolean,
+ expanded: boolean,
+ selected: boolean,
+ onSelect?: () => void,
+ onExpand?: () => void,
+ children?: React.Element<typeof RelayRow>,
+};
+
+const styles = {
+ base: Styles.createViewStyle({
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingRight: 0,
+ paddingLeft: 40,
+ backgroundColor: colors.blue40,
+ }),
+ selected: Styles.createViewStyle({
+ backgroundColor: colors.green,
+ }),
+};
+
+export default class CityRow extends Component<Props> {
+ shouldComponentUpdate(nextProps: Props) {
+ return !CityRow.compareProps(this.props, nextProps);
+ }
+
+ static compareProps(oldProps: Props, nextProps: Props) {
+ if (React.Children.count(oldProps.children) !== React.Children.count(nextProps.children)) {
+ return false;
+ }
+
+ if (
+ oldProps.name !== nextProps.name ||
+ oldProps.hasActiveRelays !== nextProps.hasActiveRelays ||
+ oldProps.selected !== nextProps.selected ||
+ oldProps.expanded !== nextProps.expanded
+ ) {
+ return false;
+ }
+
+ const currChildren = React.Children.toArray(oldProps.children);
+ const nextChildren = React.Children.toArray(nextProps.children);
+
+ for (let i = 0; i < currChildren.length; i++) {
+ const currChild = currChildren[i];
+ const nextChild = nextChildren[i];
+
+ if (!RelayRow.compareProps(currChild.props, nextChild.props)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ render() {
+ const hasChildren = React.Children.count(this.props.children) > 1;
+
+ return (
+ <View>
+ <Cell.CellButton
+ onPress={this._handlePress}
+ disabled={!this.props.hasActiveRelays}
+ cellHoverStyle={this.props.selected ? styles.selected : null}
+ style={[styles.base, this.props.selected ? styles.selected : null]}
+ testName="city">
+ <RelayStatusIndicator
+ isActive={this.props.hasActiveRelays}
+ isSelected={this.props.selected}
+ />
+ <Cell.Label>{this.props.name}</Cell.Label>
+
+ {hasChildren && <ChevronButton onPress={this._toggleCollapse} up={this.props.expanded} />}
+ </Cell.CellButton>
+
+ {hasChildren && <Accordion expanded={this.props.expanded}>{this.props.children}</Accordion>}
+ </View>
+ );
+ }
+
+ _toggleCollapse = (event: Event) => {
+ if (this.props.onExpand) {
+ this.props.onExpand(!this.props.expanded);
+ }
+ event.stopPropagation();
+ };
+
+ _handlePress = () => {
+ if (this.props.onSelect) {
+ this.props.onSelect();
+ }
+ };
+}
diff --git a/gui/packages/desktop/src/renderer/components/CountryRow.js b/gui/packages/desktop/src/renderer/components/CountryRow.js
new file mode 100644
index 0000000000..c504ad397c
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/components/CountryRow.js
@@ -0,0 +1,113 @@
+// @flow
+
+import * as React from 'react';
+import { Component, Styles, View } from 'reactxp';
+import { Accordion } from '@mullvad/components';
+import * as Cell from './Cell';
+import CityRow from './CityRow';
+import RelayStatusIndicator from './RelayStatusIndicator';
+import ChevronButton from './ChevronButton';
+import { colors } from '../../config';
+
+type Props = {
+ name: string,
+ hasActiveRelays: boolean,
+ selected: boolean,
+ expanded: boolean,
+ onSelect?: () => void,
+ onExpand?: (boolean) => void,
+ children?: React.Element<typeof CityRow>,
+};
+
+const styles = {
+ container: Styles.createViewStyle({
+ flexDirection: 'column',
+ flex: 0,
+ }),
+ base: Styles.createViewStyle({
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingLeft: 20,
+ paddingRight: 0,
+ }),
+ selected: Styles.createViewStyle({
+ backgroundColor: colors.green,
+ }),
+};
+
+export default class CountryRow extends Component<Props> {
+ shouldComponentUpdate(nextProps: Props) {
+ return !CountryRow.compareProps(this.props, nextProps);
+ }
+
+ static compareProps(oldProps: Props, nextProps: Props) {
+ if (React.Children.count(oldProps.children) !== React.Children.count(nextProps.children)) {
+ return false;
+ }
+
+ if (
+ oldProps.name !== nextProps.name ||
+ oldProps.hasActiveRelays !== nextProps.hasActiveRelays ||
+ oldProps.selected !== nextProps.selected ||
+ oldProps.expanded !== nextProps.expanded
+ ) {
+ return false;
+ }
+
+ const currChildren = React.Children.toArray(oldProps.children);
+ const nextChildren = React.Children.toArray(nextProps.children);
+
+ for (let i = 0; i < currChildren.length; i++) {
+ const currChild = currChildren[i];
+ const nextChild = nextChildren[i];
+
+ if (!CityRow.compareProps(currChild.props, nextChild.props)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ render() {
+ const numChildren = React.Children.count(this.props.children);
+ const onlyChild = numChildren === 1 ? this.props.children[0] : undefined;
+ const numOnlyChildChildren = onlyChild ? React.Children.count(onlyChild.props.children) : 0;
+ const hasChildren = numChildren > 1 || numOnlyChildChildren > 1;
+
+ return (
+ <View style={styles.container}>
+ <Cell.CellButton
+ cellHoverStyle={this.props.selected ? styles.selected : null}
+ style={[styles.base, this.props.selected ? styles.selected : null]}
+ onPress={this._handlePress}
+ disabled={!this.props.hasActiveRelays}
+ testName="country">
+ <RelayStatusIndicator
+ isActive={this.props.hasActiveRelays}
+ isSelected={this.props.selected}
+ />
+ <Cell.Label>{this.props.name}</Cell.Label>
+ {hasChildren ? (
+ <ChevronButton onPress={this._toggleCollapse} up={this.props.expanded} />
+ ) : null}
+ </Cell.CellButton>
+
+ {hasChildren && <Accordion expanded={this.props.expanded}>{this.props.children}</Accordion>}
+ </View>
+ );
+ }
+
+ _toggleCollapse = (event: Event) => {
+ if (this.props.onExpand) {
+ this.props.onExpand(!this.props.expanded);
+ }
+ event.stopPropagation();
+ };
+
+ _handlePress = () => {
+ if (this.props.onSelect) {
+ this.props.onSelect();
+ }
+ };
+}
diff --git a/gui/packages/desktop/src/renderer/components/RelayRow.js b/gui/packages/desktop/src/renderer/components/RelayRow.js
new file mode 100644
index 0000000000..04aadf3fd5
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/components/RelayRow.js
@@ -0,0 +1,56 @@
+// @flow
+
+import * as React from 'react';
+import { Component, Styles } from 'reactxp';
+import * as Cell from './Cell';
+import RelayStatusIndicator from './RelayStatusIndicator';
+import { colors } from '../../config';
+
+type Props = {
+ hostname: string,
+ selected: boolean,
+ onSelect?: () => void,
+};
+
+const styles = {
+ base: Styles.createViewStyle({
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingRight: 0,
+ paddingLeft: 60,
+ backgroundColor: colors.blue20,
+ }),
+ selected: Styles.createViewStyle({
+ backgroundColor: colors.green,
+ }),
+};
+
+export default class RelayRow extends Component<Props> {
+ shouldComponentUpdate(nextProps: Props) {
+ return !RelayRow.compareProps(this.props, nextProps);
+ }
+
+ static compareProps(oldProps: Props, nextProps: Props) {
+ return oldProps.hostname === nextProps.hostname && oldProps.selected === nextProps.selected;
+ }
+
+ render() {
+ return (
+ <Cell.CellButton
+ onPress={this._handlePress}
+ cellHoverStyle={this.props.selected ? styles.selected : null}
+ style={[styles.base, this.props.selected ? styles.selected : null]}
+ testName="relay">
+ <RelayStatusIndicator isActive={true} isSelected={this.props.selected} />
+
+ <Cell.Label>{this.props.hostname}</Cell.Label>
+ </Cell.CellButton>
+ );
+ }
+
+ _handlePress = () => {
+ if (this.props.onSelect) {
+ this.props.onSelect();
+ }
+ };
+}
diff --git a/gui/packages/desktop/src/renderer/components/RelayStatusIndicator.js b/gui/packages/desktop/src/renderer/components/RelayStatusIndicator.js
new file mode 100644
index 0000000000..d6b3fa1469
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/components/RelayStatusIndicator.js
@@ -0,0 +1,53 @@
+// @flow
+
+import * as React from 'react';
+import { Component, Styles, View } from 'reactxp';
+import * as Cell from './Cell';
+import { colors } from '../../config';
+
+const styles = {
+ relay_status: Styles.createViewStyle({
+ width: 16,
+ height: 16,
+ borderRadius: 8,
+ marginLeft: 4,
+ marginRight: 4,
+ }),
+ relay_status__inactive: Styles.createViewStyle({
+ backgroundColor: colors.red95,
+ }),
+ relay_status__active: Styles.createViewStyle({
+ backgroundColor: colors.green90,
+ }),
+ tick_icon: Styles.createViewStyle({
+ color: colors.white,
+ marginLeft: 0,
+ marginRight: 0,
+ }),
+};
+
+type Props = {
+ isActive: boolean,
+ isSelected: boolean,
+};
+
+export default class RelayStatusIndicator extends Component<Props> {
+ render() {
+ return this.props.isSelected ? (
+ <Cell.Icon
+ style={styles.tick_icon}
+ tintColor={colors.white}
+ source="icon-tick"
+ height={24}
+ width={24}
+ />
+ ) : (
+ <View
+ style={[
+ styles.relay_status,
+ this.props.isActive ? styles.relay_status__active : styles.relay_status__inactive,
+ ]}
+ />
+ );
+ }
+}
diff --git a/gui/packages/desktop/src/renderer/components/SelectLocation.js b/gui/packages/desktop/src/renderer/components/SelectLocation.js
index 153984529c..0f45133da0 100644
--- a/gui/packages/desktop/src/renderer/components/SelectLocation.js
+++ b/gui/packages/desktop/src/renderer/components/SelectLocation.js
@@ -3,7 +3,7 @@
import * as React from 'react';
import ReactDOM from 'react-dom';
import { View, Component } from 'reactxp';
-import { Accordion, SettingsHeader, HeaderTitle, HeaderSubTitle } from '@mullvad/components';
+import { SettingsHeader, HeaderTitle, HeaderSubTitle } from '@mullvad/components';
import { Layout, Container } from './Layout';
import {
NavigationContainer,
@@ -12,17 +12,14 @@ import {
CloseBarItem,
TitleBarItem,
} from './NavigationBar';
-import * as Cell from './Cell';
import styles from './SelectLocationStyles';
-import type {
- RelaySettingsRedux,
- RelayLocationRedux,
- RelayLocationCityRedux,
- RelayLocationRelayRedux,
-} from '../redux/settings/reducers';
+import CountryRow from './CountryRow';
+import CityRow from './CityRow';
+import RelayRow from './RelayRow';
+
+import type { RelaySettingsRedux, RelayLocationRedux } from '../redux/settings/reducers';
import type { RelayLocation } from '../lib/daemon-rpc-proxy';
-import { colors } from '../../config';
type Props = {
relaySettings: RelaySettingsRedux,
@@ -32,7 +29,8 @@ type Props = {
};
type State = {
- expanded: Array<string>,
+ selectedLocation?: RelayLocation,
+ expandedItems: Array<RelayLocation>,
};
export default class SelectLocation extends Component<Props, State> {
@@ -40,43 +38,62 @@ export default class SelectLocation extends Component<Props, State> {
_scrollViewRef = React.createRef();
state = {
- expanded: [],
+ selectedLocation: undefined,
+ expandedItems: [],
};
constructor(props: Props) {
super(props);
- // set initially expanded country based on relaySettings
- const relaySettings = this.props.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) {
- const countryCode = location.city[0];
+ if (this.props.relaySettings.normal) {
+ const expandedItems = [];
+ const location = this.props.relaySettings.normal.location;
+
+ if (location.city) {
+ expandedItems.push({ country: location.city[0] });
+ }
- this.state.expanded.push(countryCode);
- } else if (location.hostname) {
- const countryCode = location.hostname[0];
- const cityCode = location.hostname[1];
+ if (location.hostname) {
+ expandedItems.push({ country: location.hostname[0] });
+ expandedItems.push({ city: [location.hostname[0], location.hostname[1]] });
+ }
- this.state.expanded.push(countryCode);
- this.state.expanded.push(`${countryCode}_${cityCode}`);
+ if (location !== 'any') {
+ this.state.selectedLocation = location;
}
+
+ this.state.expandedItems = expandedItems;
+ }
+ }
+
+ componentDidUpdate(oldProps: Props) {
+ const currentLocation = this.state.selectedLocation;
+ let newLocation = (this.props.relaySettings.normal || {}).location;
+ let oldLocation = (oldProps.relaySettings.normal || {}).location;
+
+ if (newLocation === 'any') {
+ newLocation = undefined;
+ }
+
+ if (oldLocation === 'any') {
+ oldLocation = undefined;
+ }
+
+ if (
+ !compareLocationLoose(oldLocation, newLocation) &&
+ !compareLocationLoose(currentLocation, newLocation)
+ ) {
+ this.setState({ selectedLocation: newLocation });
}
}
componentDidMount() {
- // restore scroll to selected cell
+ // restore scroll to the selected cell
const cell = this._selectedCellRef.current;
const scrollView = this._scrollViewRef.current;
-
if (scrollView && cell) {
// eslint-disable-next-line react/no-find-dom-node
const cellDOMNode = ReactDOM.findDOMNode(cell);
-
if (cellDOMNode instanceof HTMLElement) {
scrollView.scrollToElement(cellDOMNode, 'middle');
}
@@ -105,7 +122,48 @@ export default class SelectLocation extends Component<Props, State> {
</SettingsHeader>
{this.props.relayLocations.map((relayCountry) => {
- return this._renderCountry(relayCountry);
+ const location = { country: relayCountry.code };
+
+ return (
+ <CountryRow
+ key={getLocationKey(location)}
+ name={relayCountry.name}
+ hasActiveRelays={relayCountry.hasActiveRelays}
+ expanded={this._isExpanded(location)}
+ onSelect={() => this._handleSelection(location)}
+ onExpand={(expand) => this._handleExpand(location, expand)}
+ {...this._getCommonCellProps(location)}>
+ {relayCountry.cities.map((relayCity) => {
+ const location = { city: [relayCountry.code, relayCity.code] };
+
+ return (
+ <CityRow
+ key={getLocationKey(location)}
+ name={relayCity.name}
+ hasActiveRelays={relayCity.hasActiveRelays}
+ expanded={this._isExpanded(location)}
+ onSelect={() => this._handleSelection(location)}
+ onExpand={(expand) => this._handleExpand(location, expand)}
+ {...this._getCommonCellProps(location)}>
+ {relayCity.relays.map((relay) => {
+ const location = {
+ hostname: [relayCountry.code, relayCity.code, relay.hostname],
+ };
+
+ return (
+ <RelayRow
+ key={getLocationKey(location)}
+ hostname={relay.hostname}
+ onSelect={() => this._handleSelection(location)}
+ {...this._getCommonCellProps(location)}
+ />
+ );
+ })}
+ </CityRow>
+ );
+ })}
+ </CountryRow>
+ );
})}
</View>
</NavigationScrollbars>
@@ -117,213 +175,71 @@ export default class SelectLocation extends Component<Props, State> {
);
}
- _isSelected(selectedLocation: RelayLocation) {
- const relaySettings = this.props.relaySettings;
- 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])
- );
- }
+ _isExpanded(relayLocation: RelayLocation) {
+ return this.state.expandedItems.some((location) => compareLocation(location, relayLocation));
+ }
- if (Array.isArray(selectedLocation.hostname) && Array.isArray(otherLocation.hostname)) {
- const selectedRelay = selectedLocation.hostname;
- const otherRelay = otherLocation.hostname;
+ _isSelected(relayLocation: RelayLocation) {
+ return compareLocationLoose(this.state.selectedLocation, relayLocation);
+ }
- return (
- selectedRelay.length === otherRelay.length &&
- selectedRelay.every((v, i) => v === otherRelay[i])
- );
- }
+ _handleSelection = (location: RelayLocation) => {
+ if (!compareLocationLoose(this.state.selectedLocation, location)) {
+ this.setState({ selectedLocation: location }, () => {
+ this.props.onSelect(location);
+ });
}
- return false;
- }
+ };
- _toggleCollapse = (countryCode: string) => {
+ _handleExpand = (location: RelayLocation, expand: boolean) => {
this.setState((state) => {
- const expanded = state.expanded.slice();
- const index = expanded.indexOf(countryCode);
- if (index === -1) {
- expanded.push(countryCode);
- } else {
- expanded.splice(index, 1);
+ const expandedItems = state.expandedItems.filter((item) => !compareLocation(item, location));
+
+ if (expand) {
+ expandedItems.push(location);
}
- return { expanded };
+
+ return {
+ ...state,
+ expandedItems,
+ };
});
};
- _relayStatusIndicator(active: boolean, isSelected: boolean) {
- const statusClass = active ? styles.relay_status__active : styles.relay_status__inactive;
+ _getCommonCellProps(location: RelayLocation) {
+ const selected = this._isSelected(location);
+ const ref = selected ? this._selectedCellRef : undefined;
- return isSelected ? (
- <Cell.Icon
- style={styles.tick_icon}
- tintColor={colors.white}
- source="icon-tick"
- height={24}
- width={24}
- />
- ) : (
- <View style={[styles.relay_status, statusClass]} />
- );
+ return { ref, selected };
}
+}
- _renderCountry(relayCountry: RelayLocationRedux) {
- const isSelected = this._isSelected({ country: relayCountry.code });
-
- const cellRef = isSelected ? this._selectedCellRef : undefined;
-
- // either expanded by user or when the city selected within the country
- const isExpanded = this.state.expanded.includes(relayCountry.code);
-
- const hasChildren =
- relayCountry.cities.length > 1 ||
- (relayCountry.cities.length == 1 && relayCountry.cities[0].relays.length > 1);
-
- const handleSelect =
- relayCountry.hasActiveRelays && !isSelected
- ? () => {
- this.props.onSelect({ country: relayCountry.code });
- }
- : undefined;
-
- const handleCollapse = (e) => {
- this._toggleCollapse(relayCountry.code);
- e.stopPropagation();
- };
-
- return (
- <View key={relayCountry.code} style={styles.country}>
- <Cell.CellButton
- cellHoverStyle={isSelected ? styles.cell_selected : null}
- style={isSelected ? styles.cell_selected : styles.cell}
- onPress={handleSelect}
- disabled={!relayCountry.hasActiveRelays}
- testName="country"
- ref={cellRef}>
- {this._relayStatusIndicator(relayCountry.hasActiveRelays, isSelected)}
-
- <Cell.Label>{relayCountry.name}</Cell.Label>
-
- {hasChildren ? (
- <Cell.Icon
- style={styles.collapse_button}
- tintColor={colors.white80}
- tintHoverColor={colors.white}
- onPress={handleCollapse}
- source={isExpanded ? 'icon-chevron-up' : 'icon-chevron-down'}
- height={24}
- width={24}
- />
- ) : null}
- </Cell.CellButton>
-
- {hasChildren && (
- <Accordion height={isExpanded ? 'auto' : 0}>
- {relayCountry.cities.map((relayCity) => this._renderCity(relayCountry.code, relayCity))}
- </Accordion>
- )}
- </View>
- );
- }
-
- _renderCity(countryCode: string, relayCity: RelayLocationCityRedux) {
- const expandedCode = `${countryCode}_${relayCity.code}`;
- const relayLocation: RelayLocation = { city: [countryCode, relayCity.code] };
-
- const isSelected = this._isSelected(relayLocation);
-
- const cellRef = isSelected ? this._selectedCellRef : undefined;
-
- // either expanded by user or when the city or a relay from the city is selected
- const isExpanded = this.state.expanded.includes(expandedCode);
-
- const handleSelect =
- relayCity.hasActiveRelays && !isSelected
- ? () => {
- this.props.onSelect(relayLocation);
- }
- : undefined;
+function getLocationKey(location: RelayLocation) {
+ const components = location.city || location.country || location.hostname || [];
- const handleCollapse = (e) => {
- this._toggleCollapse(expandedCode);
- e.stopPropagation();
- };
+ return [].concat(components).join('-');
+}
+function compareLocation(lhs: RelayLocation, rhs: RelayLocation) {
+ if (lhs.country && rhs.country) {
+ return lhs.country === rhs.country;
+ } else if (lhs.city && rhs.city) {
+ return lhs.city[0] === rhs.city[0] && lhs.city[1] === rhs.city[1];
+ } else if (lhs.hostname && rhs.hostname) {
return (
- <View key={expandedCode}>
- <Cell.CellButton
- onPress={handleSelect}
- disabled={!relayCity.hasActiveRelays}
- cellHoverStyle={isSelected ? styles.sub_cell__selected : null}
- style={isSelected ? styles.sub_cell__selected : styles.sub_cell}
- testName="city"
- ref={cellRef}>
- {this._relayStatusIndicator(relayCity.hasActiveRelays, isSelected)}
-
- <Cell.Label>{relayCity.name}</Cell.Label>
-
- {relayCity.relays.length > 1 ? (
- <Cell.Icon
- style={styles.collapse_button}
- tintColor={colors.white80}
- tintHoverColor={colors.white}
- onPress={handleCollapse}
- source={isExpanded ? 'icon-chevron-up' : 'icon-chevron-down'}
- height={24}
- width={24}
- />
- ) : null}
- </Cell.CellButton>
-
- {relayCity.relays.length > 1 && (
- <Accordion height={isExpanded ? 'auto' : 0}>
- {relayCity.relays.map((relay) => this._renderRelay(countryCode, relayCity.code, relay))}
- </Accordion>
- )}
- </View>
+ lhs.hostname[0] === rhs.hostname[0] &&
+ lhs.hostname[1] === rhs.hostname[1] &&
+ lhs.hostname[2] === rhs.hostname[2]
);
+ } else {
+ return false;
}
+}
- _renderRelay(countryCode: string, cityCode: string, relay: RelayLocationRelayRedux) {
- const relayLocation: RelayLocation = { hostname: [countryCode, cityCode, relay.hostname] };
-
- const isSelected = this._isSelected(relayLocation);
-
- const cellRef = isSelected ? this._selectedCellRef : undefined;
-
- const handleSelect = !isSelected
- ? () => {
- this.props.onSelect(relayLocation);
- }
- : undefined;
-
- return (
- <Cell.CellButton
- key={`${countryCode}_${cityCode}_${relay.hostname}`}
- onPress={handleSelect}
- cellHoverStyle={isSelected ? styles.sub_sub_cell__selected : null}
- style={isSelected ? styles.sub_sub_cell__selected : styles.sub_sub_cell}
- testName="relay"
- ref={cellRef}>
- {this._relayStatusIndicator(true, isSelected)}
-
- <Cell.Label>{relay.hostname}</Cell.Label>
- </Cell.CellButton>
- );
+function compareLocationLoose(lhs: ?RelayLocation, rhs: ?RelayLocation) {
+ if (lhs && rhs) {
+ return compareLocation(lhs, rhs);
+ } else {
+ return lhs === rhs;
}
}
diff --git a/gui/packages/desktop/src/renderer/components/SelectLocationStyles.js b/gui/packages/desktop/src/renderer/components/SelectLocationStyles.js
index 202d2c53fe..c2db5a7718 100644
--- a/gui/packages/desktop/src/renderer/components/SelectLocationStyles.js
+++ b/gui/packages/desktop/src/renderer/components/SelectLocationStyles.js
@@ -21,78 +21,4 @@ export default {
content: Styles.createViewStyle({
overflow: 'visible',
}),
- relay_status: Styles.createViewStyle({
- width: 16,
- height: 16,
- borderRadius: 8,
- marginLeft: 4,
- marginRight: 4,
- marginTop: 20,
- marginBottom: 20,
- }),
- relay_status__inactive: Styles.createViewStyle({
- backgroundColor: colors.red95,
- }),
- relay_status__active: Styles.createViewStyle({
- backgroundColor: colors.green90,
- }),
- tick_icon: Styles.createViewStyle({
- color: colors.white,
- marginLeft: 0,
- marginRight: 0,
- marginTop: 15.5,
- marginBottom: 15.5,
- }),
- country: Styles.createViewStyle({
- flexDirection: 'column',
- flex: 0,
- }),
- collapse_button: Styles.createViewStyle({
- flex: 0,
- alignSelf: 'stretch',
- justifyContent: 'center',
- paddingRight: 16,
- paddingLeft: 16,
- }),
- cell: Styles.createViewStyle({
- paddingTop: 0,
- paddingBottom: 0,
- paddingLeft: 20,
- paddingRight: 0,
- }),
- cell_selected: Styles.createViewStyle({
- paddingTop: 0,
- paddingBottom: 0,
- paddingLeft: 20,
- paddingRight: 0,
- backgroundColor: colors.green,
- }),
- sub_cell: Styles.createViewStyle({
- paddingTop: 0,
- paddingBottom: 0,
- paddingRight: 0,
- paddingLeft: 40,
- backgroundColor: colors.blue40,
- }),
- sub_cell__selected: Styles.createViewStyle({
- paddingTop: 0,
- paddingBottom: 0,
- paddingRight: 0,
- paddingLeft: 40,
- backgroundColor: colors.green,
- }),
- sub_sub_cell: Styles.createViewStyle({
- paddingTop: 0,
- paddingBottom: 0,
- paddingRight: 0,
- paddingLeft: 60,
- backgroundColor: colors.blue20,
- }),
- sub_sub_cell__selected: Styles.createViewStyle({
- paddingTop: 0,
- paddingBottom: 0,
- paddingRight: 0,
- paddingLeft: 60,
- backgroundColor: colors.green,
- }),
};