summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-11-21 13:15:16 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-11-24 16:26:28 +0100
commitb3a27fdbdd56fec4255b785c73d702304f1e8180 (patch)
tree3d63c408aef04d35939ccf96b180c295cfeb6868
parent4f8f400dd010235a3e52fc9294bebad06166f27e (diff)
downloadmullvadvpn-b3a27fdbdd56fec4255b785c73d702304f1e8180.tar.xz
mullvadvpn-b3a27fdbdd56fec4255b785c73d702304f1e8180.zip
Move relay list logic to RelayListContext and scroll logic to ScrollPositionContext
-rw-r--r--gui/src/renderer/components/select-location/RelayListContext.tsx277
-rw-r--r--gui/src/renderer/components/select-location/ScrollPositionContext.tsx83
-rw-r--r--gui/src/renderer/components/select-location/SelectLocation.tsx73
-rw-r--r--gui/src/renderer/components/select-location/SelectLocationContainer.tsx64
-rw-r--r--gui/src/renderer/components/select-location/select-location-hooks.ts229
-rw-r--r--gui/src/renderer/lib/utilityHooks.ts25
6 files changed, 391 insertions, 360 deletions
diff --git a/gui/src/renderer/components/select-location/RelayListContext.tsx b/gui/src/renderer/components/select-location/RelayListContext.tsx
new file mode 100644
index 0000000000..fcef3c8a99
--- /dev/null
+++ b/gui/src/renderer/components/select-location/RelayListContext.tsx
@@ -0,0 +1,277 @@
+import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
+
+import { compareRelayLocation, RelayLocation } from '../../../shared/daemon-rpc-types';
+import {
+ EndpointType,
+ filterLocations,
+ filterLocationsByEndPointType,
+ getLocationsExpandedBySearch,
+ searchForLocations,
+} from '../../lib/filter-locations';
+import { useNormalBridgeSettings, useNormalRelaySettings } from '../../lib/utilityHooks';
+import { IRelayLocationRedux } from '../../redux/settings/reducers';
+import { useSelector } from '../../redux/store';
+import { useScrollPositionContext } from './ScrollPositionContext';
+import {
+ defaultExpandedLocations,
+ formatRowName,
+ isCityDisabled,
+ isCountryDisabled,
+ isExpanded,
+ isRelayDisabled,
+ isSelected,
+} from './select-location-helpers';
+import {
+ DisabledReason,
+ LocationList,
+ LocationSelectionType,
+ LocationType,
+} from './select-location-types';
+import { useSelectLocationContext } from './SelectLocationContainer';
+
+interface RelayListContext {
+ relayList: LocationList<never>;
+ expandLocation: (location: RelayLocation) => void;
+ collapseLocation: (location: RelayLocation) => void;
+ onBeforeExpand: (locationRect: DOMRect, expandedContentHeight: number) => void;
+}
+
+type ExpandedLocations = Partial<Record<LocationType, Array<RelayLocation>>>;
+
+const relayListContext = React.createContext<RelayListContext | undefined>(undefined);
+
+export function useRelayListContext() {
+ return useContext(relayListContext)!;
+}
+
+interface RelayListContextProviderProps {
+ children: React.ReactNode;
+}
+
+export function RelayListContextProvider(props: RelayListContextProviderProps) {
+ const { locationType, searchTerm } = useSelectLocationContext();
+ const fullRelayList = useSelector((state) => state.settings.relayLocations);
+ const relaySettings = useNormalRelaySettings();
+ const bridgeSettings = useNormalBridgeSettings();
+
+ const [expandedLocationsMap, setExpandedLocations] = useState<ExpandedLocations>(() =>
+ defaultExpandedLocations(relaySettings, bridgeSettings),
+ );
+ const {
+ expandedLocations,
+ expandLocation,
+ collapseLocation,
+ onBeforeExpand,
+ } = useExpandedLocations(expandedLocationsMap, setExpandedLocations);
+
+ const relayListForEndpointType = useMemo(() => {
+ const endpointType =
+ locationType === LocationType.entry ? EndpointType.entry : EndpointType.exit;
+ return filterLocationsByEndPointType(fullRelayList, endpointType, relaySettings);
+ }, [fullRelayList, locationType, relaySettings?.tunnelProtocol]);
+
+ const relayListForFilters = useMemo(() => {
+ return filterLocations(
+ relayListForEndpointType,
+ relaySettings?.ownership,
+ relaySettings?.providers,
+ );
+ }, [relaySettings?.ownership, relaySettings?.providers, relayListForEndpointType]);
+
+ const relayListForSearch = useMemo(() => {
+ return searchForLocations(relayListForFilters, searchTerm);
+ }, [relayListForFilters, searchTerm]);
+
+ const relayList = useRelayList(relayListForSearch, expandedLocations);
+
+ const value = useMemo(
+ () => ({
+ relayList,
+ expandLocation,
+ collapseLocation,
+ onBeforeExpand,
+ }),
+ [relayList, expandLocation, collapseLocation, onBeforeExpand],
+ );
+
+ useEffect(() => {
+ if (searchTerm === '') {
+ setExpandedLocations(defaultExpandedLocations(relaySettings, bridgeSettings));
+ } else {
+ setExpandedLocations((expandedLocations) => ({
+ ...expandedLocations,
+ [locationType]: getLocationsExpandedBySearch(relayListForFilters, searchTerm),
+ }));
+ }
+ }, [relayListForFilters, searchTerm, relaySettings?.ownership, relaySettings?.providers]);
+
+ return <relayListContext.Provider value={value}>{props.children}</relayListContext.Provider>;
+}
+
+// Return the final filtered and formatted relay list. This should be the only place in the app
+// where processing of the relay list is performed.
+function useRelayList(
+ relayList: Array<IRelayLocationRedux>,
+ expandedLocations?: Array<RelayLocation>,
+): LocationList<never> {
+ const locale = useSelector((state) => state.userInterface.locale);
+ const selectedLocation = useSelectedLocation();
+ const disabledLocation = useDisabledLocation();
+
+ return useMemo(() => {
+ return relayList
+ .map((country) => {
+ const countryLocation = { country: country.code };
+ const countryDisabled = isCountryDisabled(
+ country,
+ countryLocation.country,
+ disabledLocation,
+ );
+
+ return {
+ ...country,
+ type: LocationSelectionType.relay as const,
+ label: formatRowName(country.name, countryLocation, countryDisabled),
+ location: countryLocation,
+ active: countryDisabled !== DisabledReason.inactive,
+ disabled: countryDisabled !== undefined,
+ expanded: isExpanded(countryLocation, expandedLocations),
+ selected: isSelected(countryLocation, selectedLocation),
+ cities: country.cities
+ .map((city) => {
+ const cityLocation: RelayLocation = { city: [country.code, city.code] };
+ const cityDisabled =
+ countryDisabled ?? isCityDisabled(city, cityLocation.city, disabledLocation);
+
+ return {
+ ...city,
+ label: formatRowName(city.name, cityLocation, cityDisabled),
+ location: cityLocation,
+ active: cityDisabled !== DisabledReason.inactive,
+ disabled: cityDisabled !== undefined,
+ expanded: isExpanded(cityLocation, expandedLocations),
+ selected: isSelected(cityLocation, selectedLocation),
+ relays: city.relays
+ .map((relay) => {
+ const relayLocation: RelayLocation = {
+ hostname: [country.code, city.code, relay.hostname],
+ };
+ const relayDisabled =
+ countryDisabled ??
+ cityDisabled ??
+ isRelayDisabled(relay, relayLocation.hostname, disabledLocation);
+
+ return {
+ ...relay,
+ label: formatRowName(relay.hostname, relayLocation, relayDisabled),
+ location: relayLocation,
+ disabled: relayDisabled !== undefined,
+ selected: isSelected(relayLocation, selectedLocation),
+ };
+ })
+ .sort((a, b) => a.hostname.localeCompare(b.hostname, locale, { numeric: true })),
+ };
+ })
+ .sort((a, b) => a.label.localeCompare(b.label, locale)),
+ };
+ })
+ .sort((a, b) => a.label.localeCompare(b.label, locale));
+ }, [locale, expandedLocations, relayList, selectedLocation, disabledLocation]);
+}
+
+// Return all RelayLocations that should be expanded
+function useExpandedLocations(
+ expandedLocationsMap: ExpandedLocations,
+ setExpandedLocations: (
+ arg: ExpandedLocations | ((prev: ExpandedLocations) => ExpandedLocations),
+ ) => void,
+) {
+ const { locationType } = useSelectLocationContext();
+ const { spacePreAllocationViewRef, scrollViewRef } = useScrollPositionContext();
+
+ const expandLocation = useCallback(
+ (location: RelayLocation) => {
+ setExpandedLocations((expandedLocations) => ({
+ ...expandedLocations,
+ [locationType]: [...(expandedLocationsMap[locationType] ?? []), location],
+ }));
+ },
+ [locationType],
+ );
+
+ const collapseLocation = useCallback(
+ (location: RelayLocation) => {
+ setExpandedLocations((expandedLocations) => ({
+ ...expandedLocations,
+ [locationType]: expandedLocationsMap[locationType]!.filter(
+ (item) => !compareRelayLocation(location, item),
+ ),
+ }));
+ },
+ [locationType],
+ );
+
+ const onBeforeExpand = useCallback((locationRect: DOMRect, expandedContentHeight: number) => {
+ locationRect.height += expandedContentHeight;
+ spacePreAllocationViewRef.current?.allocate(expandedContentHeight);
+ scrollViewRef.current?.scrollIntoView(locationRect);
+ }, []);
+
+ return {
+ expandedLocations: expandedLocationsMap[locationType],
+ expandLocation,
+ collapseLocation,
+ onBeforeExpand,
+ };
+}
+
+function useDisabledLocation() {
+ const { locationType } = useSelectLocationContext();
+ const relaySettings = useNormalRelaySettings();
+
+ return useMemo(() => {
+ if (relaySettings?.tunnelProtocol !== 'openvpn' && relaySettings?.wireguard.useMultihop) {
+ if (locationType === LocationType.exit && relaySettings?.wireguard.entryLocation !== 'any') {
+ return {
+ location: relaySettings?.wireguard.entryLocation,
+ reason: DisabledReason.entry,
+ };
+ } else if (locationType === LocationType.entry && relaySettings?.location !== 'any') {
+ return { location: relaySettings?.location, reason: DisabledReason.exit };
+ }
+ }
+
+ return undefined;
+ }, [
+ locationType,
+ relaySettings?.tunnelProtocol,
+ relaySettings?.wireguard.useMultihop,
+ relaySettings?.wireguard.entryLocation,
+ relaySettings?.location,
+ ]);
+}
+
+// Returns the selected location for the current tunnel protocol and location type
+function useSelectedLocation() {
+ const { locationType } = useSelectLocationContext();
+ const relaySettings = useNormalRelaySettings();
+ const bridgeSettings = useNormalBridgeSettings();
+
+ return useMemo(() => {
+ if (locationType === LocationType.exit) {
+ return relaySettings?.location === 'any' ? undefined : relaySettings?.location;
+ } else if (relaySettings?.tunnelProtocol !== 'openvpn') {
+ return relaySettings?.wireguard.entryLocation === 'any'
+ ? undefined
+ : relaySettings?.wireguard.entryLocation;
+ } else {
+ return bridgeSettings?.location;
+ }
+ }, [
+ locationType,
+ relaySettings?.location,
+ relaySettings?.tunnelProtocol,
+ relaySettings?.wireguard.entryLocation,
+ bridgeSettings?.location,
+ ]);
+}
diff --git a/gui/src/renderer/components/select-location/ScrollPositionContext.tsx b/gui/src/renderer/components/select-location/ScrollPositionContext.tsx
new file mode 100644
index 0000000000..63efe06b47
--- /dev/null
+++ b/gui/src/renderer/components/select-location/ScrollPositionContext.tsx
@@ -0,0 +1,83 @@
+import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
+
+import { useNormalRelaySettings } from '../../lib/utilityHooks';
+import { CustomScrollbarsRef } from '../CustomScrollbars';
+import { LocationType } from './select-location-types';
+import { useSelectLocationContext } from './SelectLocationContainer';
+import { SpacePreAllocationView } from './SpacePreAllocationView';
+
+interface ScrollPositionContext {
+ scrollPositions: React.RefObject<Partial<Record<LocationType, ScrollPosition>>>;
+ selectedLocationRef: React.RefObject<HTMLDivElement>;
+ scrollViewRef: React.RefObject<CustomScrollbarsRef>;
+ spacePreAllocationViewRef: React.RefObject<SpacePreAllocationView>;
+ saveScrollPosition: () => void;
+ resetScrollPositions: () => void;
+}
+
+type ScrollPosition = [number, number];
+
+const scrollPositionContext = React.createContext<ScrollPositionContext | undefined>(undefined);
+
+export function useScrollPositionContext() {
+ return useContext(scrollPositionContext)!;
+}
+
+interface ScrollPositionContextProps {
+ children: React.ReactNode;
+}
+
+export function ScrollPositionContextProvider(props: ScrollPositionContextProps) {
+ const { locationType, searchTerm } = useSelectLocationContext();
+ const relaySettings = useNormalRelaySettings();
+
+ const scrollViewRef = useRef<CustomScrollbarsRef>(null);
+ const spacePreAllocationViewRef = useRef() as React.RefObject<SpacePreAllocationView>;
+ const scrollPositions = useRef<Partial<Record<LocationType, ScrollPosition>>>({});
+ const selectedLocationRef = useRef<HTMLDivElement>(null);
+
+ const saveScrollPosition = useCallback(() => {
+ const scrollPosition = scrollViewRef.current?.getScrollPosition();
+ if (scrollPositions.current && scrollPosition) {
+ scrollPositions.current[locationType] = scrollPosition;
+ }
+ }, [locationType]);
+
+ const resetScrollPositions = useCallback(() => {
+ for (const locationTypeVariant of [LocationType.entry, LocationType.exit]) {
+ if (
+ scrollPositions.current &&
+ (scrollPositions.current[locationTypeVariant] || locationTypeVariant === locationType)
+ ) {
+ scrollPositions.current[locationTypeVariant] = [0, 0];
+ }
+ }
+ }, [locationType]);
+
+ const value = useMemo(
+ () => ({
+ scrollPositions,
+ selectedLocationRef,
+ scrollViewRef,
+ spacePreAllocationViewRef,
+ saveScrollPosition,
+ resetScrollPositions,
+ }),
+ [],
+ );
+
+ useEffect(() => {
+ const scrollPosition = scrollPositions.current?.[locationType];
+ if (scrollPosition) {
+ scrollViewRef.current?.scrollTo(...scrollPosition);
+ } else if (selectedLocationRef.current) {
+ scrollViewRef.current?.scrollToElement(selectedLocationRef.current, 'middle');
+ } else {
+ scrollViewRef.current?.scrollToTop();
+ }
+ }, [locationType, searchTerm, relaySettings?.ownership, relaySettings?.providers]);
+
+ return (
+ <scrollPositionContext.Provider value={value}>{props.children}</scrollPositionContext.Provider>
+ );
+}
diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx
index 660bdacafa..3a1538d070 100644
--- a/gui/src/renderer/components/select-location/SelectLocation.tsx
+++ b/gui/src/renderer/components/select-location/SelectLocation.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect } from 'react';
+import { useCallback } from 'react';
import { sprintf } from 'sprintf-js';
import { colors } from '../../../config.json';
@@ -20,13 +20,10 @@ import {
TitleBarItem,
} from '../NavigationBar';
import LocationList from './LocationList';
+import { useRelayListContext } from './RelayListContext';
import { ScopeBar, ScopeBarItem } from './ScopeBar';
-import {
- useExpandedLocations,
- useOnSelectBridgeLocation,
- useOnSelectLocation,
- useRelayList,
-} from './select-location-hooks';
+import { useScrollPositionContext } from './ScrollPositionContext';
+import { useOnSelectBridgeLocation, useOnSelectLocation } from './select-location-hooks';
import {
LocationSelectionType,
LocationType,
@@ -49,8 +46,13 @@ import { SpacePreAllocationView } from './SpacePreAllocationView';
export default function SelectLocation() {
const history = useHistory();
const { updateRelaySettings } = useAppContext();
- const { saveScrollPosition, resetScrollPositions, applyScrollPosition } = useScrollPosition();
- const { scrollViewRef, spacePreAllocationViewRef, locationType, setLocationType, searchTerm, setSearchTerm } = useSelectLocationContext();
+ const {
+ saveScrollPosition,
+ resetScrollPositions,
+ scrollViewRef,
+ spacePreAllocationViewRef,
+ } = useScrollPositionContext();
+ const { locationType, setLocationType, searchTerm, setSearchTerm } = useSelectLocationContext();
const relaySettings = useNormalRelaySettings();
const ownership = relaySettings?.ownership ?? Ownership.any;
@@ -91,8 +93,6 @@ export default function SelectLocation() {
[resetScrollPositions],
);
- useEffect(applyScrollPosition, [applyScrollPosition]);
-
const showOwnershipFilter = ownership !== Ownership.any;
const showProvidersFilter = providers.length > 0;
const showFilters = showOwnershipFilter || showProvidersFilter;
@@ -207,10 +207,9 @@ function ownershipFilterLabel(ownership: Ownership): string {
}
function SelectLocationContent() {
- const { locationType, selectedLocationRef, spacePreAllocationViewRef } = useSelectLocationContext();
- const relayList = useRelayList();
- const { expandLocation, collapseLocation, updateExpandedLocations } = useExpandedLocations();
- const { onBeforeExpand } = useScrollPosition();
+ const { locationType } = useSelectLocationContext();
+ const { selectedLocationRef, spacePreAllocationViewRef } = useScrollPositionContext();
+ const { relayList, expandLocation, collapseLocation, onBeforeExpand } = useRelayListContext();
const onSelectLocation = useOnSelectLocation();
const onSelectBridgeLocation = useOnSelectBridgeLocation();
@@ -219,8 +218,6 @@ function SelectLocationContent() {
const resetHeight = useCallback(() => spacePreAllocationViewRef.current?.reset(), []);
- useEffect(updateExpandedLocations, [updateExpandedLocations]);
-
if (locationType === LocationType.exit) {
return (
<LocationList
@@ -276,45 +273,3 @@ function SelectLocationContent() {
);
}
}
-
-function useScrollPosition() {
- const { locationType, scrollPositions, scrollViewRef, spacePreAllocationViewRef, selectedLocationRef, searchTerm } = useSelectLocationContext();
- const relaySettings = useNormalRelaySettings();
-
- const saveScrollPosition = useCallback(() => {
- const scrollPosition = scrollViewRef.current?.getScrollPosition();
- if (scrollPositions.current && scrollPosition) {
- scrollPositions.current[locationType] = scrollPosition;
- }
- }, [locationType]);
-
- const resetScrollPositions = useCallback(() => {
- for (const locationTypeVariant of [LocationType.entry, LocationType.exit]) {
- if (
- scrollPositions.current &&
- (scrollPositions.current[locationTypeVariant] || locationTypeVariant === locationType)
- ) {
- scrollPositions.current[locationTypeVariant] = [0, 0];
- }
- }
- }, [locationType]);
-
- const applyScrollPosition = useCallback(() => {
- const scrollPosition = scrollPositions.current?.[locationType];
- if (scrollPosition) {
- scrollViewRef.current?.scrollTo(...scrollPosition);
- } else if (selectedLocationRef.current) {
- scrollViewRef.current?.scrollToElement(selectedLocationRef.current, 'middle');
- } else {
- scrollViewRef.current?.scrollToTop();
- }
- }, [locationType, searchTerm, relaySettings?.ownership, relaySettings?.providers]);
-
- const onBeforeExpand = useCallback((locationRect: DOMRect, expandedContentHeight: number) => {
- locationRect.height += expandedContentHeight;
- spacePreAllocationViewRef.current?.allocate(expandedContentHeight);
- scrollViewRef.current?.scrollIntoView(locationRect);
- }, []);
-
- return { spacePreAllocationViewRef, saveScrollPosition, resetScrollPositions, applyScrollPosition, onBeforeExpand };
-}
diff --git a/gui/src/renderer/components/select-location/SelectLocationContainer.tsx b/gui/src/renderer/components/select-location/SelectLocationContainer.tsx
index 4f64aa79f3..99203fa1ad 100644
--- a/gui/src/renderer/components/select-location/SelectLocationContainer.tsx
+++ b/gui/src/renderer/components/select-location/SelectLocationContainer.tsx
@@ -1,29 +1,16 @@
-import React, { useContext, useMemo, useRef, useState } from 'react';
+import React, { useContext, useMemo, useState } from 'react';
-import { RelayLocation } from '../../../shared/daemon-rpc-types';
-import { useNormalBridgeSettings, useNormalRelaySettings } from '../../lib/utilityHooks';
-import {CustomScrollbarsRef} from '../CustomScrollbars';
-import { defaultExpandedLocations } from './select-location-helpers';
+import { useNormalRelaySettings } from '../../lib/utilityHooks';
+import { RelayListContextProvider } from './RelayListContext';
+import { ScrollPositionContextProvider } from './ScrollPositionContext';
import { LocationType } from './select-location-types';
import SelectLocation from './SelectLocation';
-import {SpacePreAllocationView} from './SpacePreAllocationView';
-
-type ExpandedLocations = Partial<Record<LocationType, Array<RelayLocation>>>;
-type ScrollPosition = [number, number];
interface SelectLocationContext {
locationType: LocationType;
setLocationType: (locationType: LocationType) => void;
searchTerm: string;
setSearchTerm: (value: string) => void;
- expandedLocations: ExpandedLocations;
- setExpandedLocations: (
- arg: ExpandedLocations | ((prev: ExpandedLocations) => ExpandedLocations),
- ) => void;
- scrollPositions: React.RefObject<Partial<Record<LocationType, ScrollPosition>>>;
- selectedLocationRef: React.RefObject<HTMLDivElement>;
- scrollViewRef: React.RefObject<CustomScrollbarsRef>;
- spacePreAllocationViewRef: React.RefObject<SpacePreAllocationView>;
}
const selectLocationContext = React.createContext<SelectLocationContext | undefined>(undefined);
@@ -34,45 +21,24 @@ export function useSelectLocationContext() {
export default function SelectLocationContainer() {
const relaySettings = useNormalRelaySettings();
- const bridgeSettings = useNormalBridgeSettings();
const [locationType, setLocationType] = useState(LocationType.exit);
- const selectedLocationRef = useRef<HTMLDivElement>(null);
- const scrollViewRef = useRef<CustomScrollbarsRef>(null);
- const spacePreAllocationViewRef = useRef() as React.RefObject<SpacePreAllocationView>;
-
- const [expandedLocations, setExpandedLocations] = useState<
- Partial<Record<LocationType, Array<RelayLocation>>>
- >(() => defaultExpandedLocations(relaySettings, bridgeSettings));
- const scrollPositions = useRef<Partial<Record<LocationType, ScrollPosition>>>({});
-
const [searchTerm, setSearchTerm] = useState('');
- const value = useMemo(
- () => ({
- locationType,
- setLocationType,
- searchTerm,
- setSearchTerm,
- expandedLocations,
- setExpandedLocations,
- scrollPositions,
- selectedLocationRef,
- scrollViewRef,
- spacePreAllocationViewRef,
- }),
- [
- locationType,
- relaySettings?.ownership,
- relaySettings?.providers,
- expandedLocations,
- searchTerm,
- ],
- );
+ const value = useMemo(() => ({ locationType, setLocationType, searchTerm, setSearchTerm }), [
+ locationType,
+ relaySettings?.ownership,
+ relaySettings?.providers,
+ searchTerm,
+ ]);
return (
<selectLocationContext.Provider value={value}>
- <SelectLocation />
+ <ScrollPositionContextProvider>
+ <RelayListContextProvider>
+ <SelectLocation />
+ </RelayListContextProvider>
+ </ScrollPositionContextProvider>
</selectLocationContext.Provider>
);
}
diff --git a/gui/src/renderer/components/select-location/select-location-hooks.ts b/gui/src/renderer/components/select-location/select-location-hooks.ts
index 5bab2a429d..12aa490c68 100644
--- a/gui/src/renderer/components/select-location/select-location-hooks.ts
+++ b/gui/src/renderer/components/select-location/select-location-hooks.ts
@@ -1,42 +1,14 @@
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import BridgeSettingsBuilder from '../../../shared/bridge-settings-builder';
-import {
- compareRelayLocation,
- RelayLocation,
- RelaySettingsUpdate,
-} from '../../../shared/daemon-rpc-types';
+import { RelaySettingsUpdate } from '../../../shared/daemon-rpc-types';
import log from '../../../shared/logging';
import RelaySettingsBuilder from '../../../shared/relay-settings-builder';
import { useAppContext } from '../../context';
import { createWireguardRelayUpdater } from '../../lib/constraint-updater';
-import {
- EndpointType,
- filterLocations,
- filterLocationsByEndPointType,
- getLocationsExpandedBySearch,
- searchForLocations,
-} from '../../lib/filter-locations';
import { useHistory } from '../../lib/history';
-import {
- useNormalBridgeSettings,
- useNormalRelaySettings,
- useSharedMemo,
-} from '../../lib/utilityHooks';
-import { IRelayLocationRedux } from '../../redux/settings/reducers';
import { useSelector } from '../../redux/store';
import {
- defaultExpandedLocations,
- formatRowName,
- isCityDisabled,
- isCountryDisabled,
- isExpanded,
- isRelayDisabled,
- isSelected,
-} from './select-location-helpers';
-import {
- DisabledReason,
- LocationList,
LocationSelection,
LocationSelectionType,
LocationType,
@@ -44,203 +16,6 @@ import {
} from './select-location-types';
import { useSelectLocationContext } from './SelectLocationContainer';
-// Return all locations that matches both the set filters and the search term.
-function useFilteredRelays(): Array<IRelayLocationRedux> {
- const { locationType, searchTerm } = useSelectLocationContext();
- const relayList = useSelector((state) => state.settings.relayLocations);
- const relaySettings = useNormalRelaySettings();
-
- const relayListForEndpointType = useSharedMemo(
- 'relay-list-endpoint-type',
- () => {
- const endpointType =
- locationType === LocationType.entry ? EndpointType.entry : EndpointType.exit;
- return filterLocationsByEndPointType(relayList, endpointType, relaySettings);
- },
- [relayList, locationType, relaySettings?.tunnelProtocol],
- );
-
- const relayListForFilters = useSharedMemo(
- 'relay-list-filters',
- () => {
- return filterLocations(
- relayListForEndpointType,
- relaySettings?.ownership,
- relaySettings?.providers,
- );
- },
- [relaySettings?.ownership, relaySettings?.providers, relayListForEndpointType],
- );
-
- const filteredRelayList = useSharedMemo(
- 'relay-list-search',
- () => {
- return searchForLocations(relayListForFilters, searchTerm);
- },
- [relayListForFilters, searchTerm],
- );
-
- return filteredRelayList;
-}
-
-// Return all RelayLocations that should be expanded
-export function useExpandedLocations() {
- const relaySettings = useNormalRelaySettings();
- const bridgeSettings = useNormalBridgeSettings();
- const {
- locationType,
- expandedLocations,
- setExpandedLocations,
- searchTerm,
- } = useSelectLocationContext();
- const relayList = useFilteredRelays();
-
- const expandLocation = useCallback(
- (location: RelayLocation) => {
- setExpandedLocations((expandedLocations) => ({
- ...expandedLocations,
- [locationType]: [...(expandedLocations[locationType] ?? []), location],
- }));
- },
- [locationType],
- );
-
- const collapseLocation = useCallback(
- (location: RelayLocation) => {
- setExpandedLocations((expandedLocations) => ({
- ...expandedLocations,
- [locationType]: expandedLocations[locationType]!.filter(
- (item) => !compareRelayLocation(location, item),
- ),
- }));
- },
- [locationType],
- );
-
- const updateExpandedLocations = useCallback(() => {
- if (searchTerm === '') {
- setExpandedLocations(defaultExpandedLocations(relaySettings, bridgeSettings));
- } else {
- setExpandedLocations((expandedLocations) => ({
- ...expandedLocations,
- [locationType]: getLocationsExpandedBySearch(relayList, searchTerm),
- }));
- }
- }, [relayList, searchTerm, relaySettings?.ownership, relaySettings?.providers]);
-
- return {
- expandedLocations: expandedLocations[locationType],
- expandLocation,
- collapseLocation,
- updateExpandedLocations,
- };
-}
-
-// Return the final filtered and formatted relay list. This should be the only place in the app
-// where processing of the relay list is performed.
-export function useRelayList(): LocationList<never> {
- const locale = useSelector((state) => state.userInterface.locale);
- const { expandedLocations } = useExpandedLocations();
- const relayList = useFilteredRelays();
- const selectedLocation = useSelectedLocation();
- const disabledLocation = useDisabledLocation();
-
- return useSharedMemo('relay-list-formatted', () => {
- return relayList
- .map((country) => {
- const countryLocation = { country: country.code };
- const countryDisabled = isCountryDisabled(country, countryLocation.country, disabledLocation);
-
- return {
- ...country,
- type: LocationSelectionType.relay as const,
- label: formatRowName(country.name, countryLocation, countryDisabled),
- location: countryLocation,
- active: countryDisabled !== DisabledReason.inactive,
- disabled: countryDisabled !== undefined,
- expanded: isExpanded(countryLocation, expandedLocations),
- selected: isSelected(countryLocation, selectedLocation),
- cities: country.cities
- .map((city) => {
- const cityLocation: RelayLocation = { city: [country.code, city.code] };
- const cityDisabled =
- countryDisabled ?? isCityDisabled(city, cityLocation.city, disabledLocation);
-
- return {
- ...city,
- label: formatRowName(city.name, cityLocation, cityDisabled),
- location: cityLocation,
- active: cityDisabled !== DisabledReason.inactive,
- disabled: cityDisabled !== undefined,
- expanded: isExpanded(cityLocation, expandedLocations),
- selected: isSelected(cityLocation, selectedLocation),
- relays: city.relays
- .map((relay) => {
- const relayLocation: RelayLocation = {
- hostname: [country.code, city.code, relay.hostname],
- };
- const relayDisabled =
- countryDisabled ??
- cityDisabled ??
- isRelayDisabled(relay, relayLocation.hostname, disabledLocation);
-
- return {
- ...relay,
- label: formatRowName(relay.hostname, relayLocation, relayDisabled),
- location: relayLocation,
- disabled: relayDisabled !== undefined,
- selected: isSelected(relayLocation, selectedLocation),
- };
- })
- .sort((a, b) => a.hostname.localeCompare(b.hostname, locale, { numeric: true })),
- };
- })
- .sort((a, b) => a.label.localeCompare(b.label, locale)),
- };
- })
- .sort((a, b) => a.label.localeCompare(b.label, locale));
- }, [locale, expandedLocations, relayList, selectedLocation, disabledLocation]);
-}
-
-function useDisabledLocation() {
- const { locationType } = useSelectLocationContext();
- const relaySettings = useNormalRelaySettings();
-
- return useMemo(() => {
- if (relaySettings?.tunnelProtocol !== 'openvpn' && relaySettings?.wireguard.useMultihop) {
- if (locationType === LocationType.exit && relaySettings?.wireguard.entryLocation !== 'any') {
- return {
- location: relaySettings?.wireguard.entryLocation,
- reason: DisabledReason.entry,
- };
- } else if (locationType === LocationType.entry && relaySettings?.location !== 'any') {
- return { location: relaySettings?.location, reason: DisabledReason.exit };
- }
- }
-
- return undefined;
- }, [locationType, relaySettings?.tunnelProtocol, relaySettings?.wireguard.useMultihop, relaySettings?.wireguard.entryLocation, relaySettings?.location]);
-}
-
-// Returns the selected location for the current tunnel protocol and location type
-function useSelectedLocation() {
- const { locationType } = useSelectLocationContext();
- const relaySettings = useNormalRelaySettings();
- const bridgeSettings = useNormalBridgeSettings();
-
- return useMemo(() => {
- if (locationType === LocationType.exit) {
- return relaySettings?.location === 'any' ? undefined : relaySettings?.location;
- } else if (relaySettings?.tunnelProtocol !== 'openvpn') {
- return relaySettings?.wireguard.entryLocation === 'any'
- ? undefined
- : relaySettings?.wireguard.entryLocation;
- } else {
- return bridgeSettings?.location;
- }
- }, [locationType, relaySettings?.location, relaySettings?.tunnelProtocol, relaySettings?.wireguard.entryLocation, bridgeSettings?.location]);
-}
-
export function useOnSelectLocation() {
const history = useHistory();
const { updateRelaySettings } = useAppContext();
diff --git a/gui/src/renderer/lib/utilityHooks.ts b/gui/src/renderer/lib/utilityHooks.ts
index 4c66937393..378a6d5ae5 100644
--- a/gui/src/renderer/lib/utilityHooks.ts
+++ b/gui/src/renderer/lib/utilityHooks.ts
@@ -65,28 +65,3 @@ export function useNormalBridgeSettings() {
const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
return 'normal' in bridgeSettings ? bridgeSettings.normal : undefined;
}
-
-const sharedMemoData: Record<
- string,
- { value: unknown; dependencies: Array<unknown> | undefined }
-> = {};
-export function useSharedMemo<T>(
- key: string,
- factory: () => T,
- dependencies: Array<unknown> | undefined,
-): T {
- const data = sharedMemoData[key];
- if (
- data === undefined ||
- data.dependencies === undefined ||
- dependencies === undefined ||
- data.dependencies.length !== dependencies.length ||
- data.dependencies.some((item, i) => item !== dependencies[i])
- ) {
- const value = factory();
- sharedMemoData[key] = { value, dependencies };
- return value;
- } else {
- return data.value as T;
- }
-}