summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/select-location/SelectLocation.tsx50
-rw-r--r--gui/src/renderer/components/select-location/SpecialLocationList.tsx122
-rw-r--r--gui/src/renderer/components/select-location/select-location-hooks.ts44
-rw-r--r--gui/src/renderer/components/select-location/select-location-types.ts11
4 files changed, 154 insertions, 73 deletions
diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx
index 6bf0fcff6f..5ba0203939 100644
--- a/gui/src/renderer/components/select-location/SelectLocation.tsx
+++ b/gui/src/renderer/components/select-location/SelectLocation.tsx
@@ -9,7 +9,7 @@ import { filterSpecialLocations } from '../../lib/filter-locations';
import { useHistory } from '../../lib/history';
import { formatHtml } from '../../lib/html-formatter';
import { RoutePath } from '../../lib/routes';
-import { useNormalBridgeSettings, useNormalRelaySettings } from '../../lib/utilityHooks';
+import { useNormalRelaySettings } from '../../lib/utilityHooks';
import { useSelector } from '../../redux/store';
import * as Cell from '../cell';
import { useFilteredProviders } from '../Filter';
@@ -34,12 +34,7 @@ import {
useOnSelectEntryLocation,
useOnSelectExitLocation,
} from './select-location-hooks';
-import {
- LocationType,
- SpecialBridgeLocationType,
- SpecialLocation,
- SpecialLocationIcon,
-} from './select-location-types';
+import { LocationType, SpecialBridgeLocationType, SpecialLocation } from './select-location-types';
import { useSelectLocationContext } from './SelectLocationContainer';
import {
StyledClearFilterButton,
@@ -54,6 +49,11 @@ import {
StyledSearchBar,
} from './SelectLocationStyles';
import { SpacePreAllocationView } from './SpacePreAllocationView';
+import {
+ AutomaticLocationRow,
+ CustomBridgeLocationRow,
+ CustomExitLocationRow,
+} from './SpecialLocationList';
export default function SelectLocation() {
const history = useHistory();
@@ -260,22 +260,21 @@ function SelectLocationContent() {
const [onSelectBridgeRelay, onSelectBridgeSpecial] = useOnSelectBridgeLocation();
const relaySettings = useNormalRelaySettings();
- const bridgeSettings = useNormalBridgeSettings();
+ const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
const allowAddToCustomList = useSelector((state) => state.settings.customLists.length > 0);
if (locationType === LocationType.exit) {
// Add "Custom" item if a custom relay is selected
- const specialList: Array<SpecialLocation<undefined>> =
- relaySettings === undefined
- ? [
- {
- label: messages.gettext('Custom'),
- value: undefined,
- selected: true,
- },
- ]
- : [];
+ const specialList: Array<SpecialLocation<undefined>> = [];
+ if (relaySettings === undefined) {
+ specialList.push({
+ label: messages.gettext('Custom'),
+ value: undefined,
+ selected: true,
+ component: CustomExitLocationRow,
+ });
+ }
const specialLocations = filterSpecialLocations(searchTerm, specialList);
return (
@@ -320,14 +319,17 @@ function SelectLocationContent() {
// Add the "Automatic" item
const specialList: Array<SpecialLocation<SpecialBridgeLocationType>> = [
{
+ label: messages.pgettext('select-location-view', 'Custom bridge'),
+ value: SpecialBridgeLocationType.custom,
+ selected: bridgeSettings?.type === 'custom',
+ disabled: bridgeSettings?.custom === undefined,
+ component: CustomBridgeLocationRow,
+ },
+ {
label: messages.gettext('Automatic'),
- icon: SpecialLocationIcon.geoLocation,
- info: messages.pgettext(
- 'select-location-view',
- 'The app selects a random bridge server, but servers have a higher probability the closer they are to you.',
- ),
value: SpecialBridgeLocationType.closestToExit,
- selected: bridgeSettings?.location === 'any',
+ selected: bridgeSettings?.type === 'normal' && bridgeSettings.normal?.location === 'any',
+ component: AutomaticLocationRow,
},
];
diff --git a/gui/src/renderer/components/select-location/SpecialLocationList.tsx b/gui/src/renderer/components/select-location/SpecialLocationList.tsx
index 030520fc73..2e658c6046 100644
--- a/gui/src/renderer/components/select-location/SpecialLocationList.tsx
+++ b/gui/src/renderer/components/select-location/SpecialLocationList.tsx
@@ -3,16 +3,22 @@ import styled from 'styled-components';
import { colors } from '../../../config.json';
import { messages } from '../../../shared/gettext';
+import { useHistory } from '../../lib/history';
+import { RoutePath } from '../../lib/routes';
+import { useSelector } from '../../redux/store';
import * as Cell from '../cell';
+import ImageView from '../ImageView';
import InfoButton from '../InfoButton';
+import RelayStatusIndicator from '../RelayStatusIndicator';
import {
getButtonColor,
+ StyledHoverInfoButton,
StyledLocationRowButton,
- StyledLocationRowContainer,
+ StyledLocationRowContainerWithMargin,
StyledLocationRowIcon,
StyledLocationRowLabel,
-} from './LocationRow';
-import { SpecialLocation } from './select-location-types';
+} from './LocationRowStyles';
+import { SpecialBridgeLocationType, SpecialLocation } from './select-location-types';
interface SpecialLocationsProps<T> {
source: Array<SpecialLocation<T>>;
@@ -30,21 +36,14 @@ export default function SpecialLocationList<T>({ source, ...props }: SpecialLoca
);
}
-const StyledLocationRowContainerWithMargin = styled(StyledLocationRowContainer)({
- marginBottom: 1,
-});
-
const StyledSpecialLocationIcon = styled(Cell.Icon)({
flex: 0,
marginLeft: '2px',
marginRight: '8px',
});
-const StyledSpecialLocationInfoButton = styled(InfoButton)({
- margin: 0,
- padding: '0 25px',
- backgroundColor: colors.blue,
-});
+const StyledSpecialLocationInfoButton = styled(InfoButton)({ padding: '0 25px', margin: 0 });
+const StyledSpecialLocationSideButton = styled(ImageView)({ padding: '0 3px' });
interface SpecialLocationRowProps<T> {
source: SpecialLocation<T>;
@@ -59,30 +58,97 @@ function SpecialLocationRow<T>(props: SpecialLocationRowProps<T>) {
}
}, [props.source.selected, props.onSelect, props.source.value]);
- const icon = props.source.selected ? 'icon-tick' : props.source.icon ?? undefined;
+ const innerProps: SpecialLocationRowInnerProps<T> = {
+ ...props,
+ onSelect,
+ };
+ return <props.source.component {...innerProps} />;
+}
+
+export interface SpecialLocationRowInnerProps<T>
+ extends Omit<SpecialLocationRowProps<T>, 'onSelect'> {
+ onSelect: () => void;
+}
+
+export function AutomaticLocationRow(
+ props: SpecialLocationRowInnerProps<SpecialBridgeLocationType>,
+) {
+ const icon = props.source.selected ? 'icon-tick' : 'icon-nearest';
const selectedRef = props.source.selected ? props.selectedElementRef : undefined;
const background = getButtonColor(props.source.selected, 0, props.source.disabled);
return (
<StyledLocationRowContainerWithMargin ref={selectedRef}>
- <StyledLocationRowButton onClick={onSelect} $level={0} {...background}>
- {icon && (
- <StyledSpecialLocationIcon
- source={icon}
- tintColor={colors.white}
- height={22}
- width={22}
- />
+ <StyledLocationRowButton onClick={props.onSelect} $level={0} {...background}>
+ <StyledSpecialLocationIcon source={icon} tintColor={colors.white} height={22} width={22} />
+ <StyledLocationRowLabel>{props.source.label}</StyledLocationRowLabel>
+ </StyledLocationRowButton>
+ <StyledLocationRowIcon
+ as={StyledSpecialLocationInfoButton}
+ title={messages.gettext('Automatic')}
+ message={messages.pgettext(
+ 'select-location-view',
+ 'The app selects a random bridge server, but servers have a higher probability the closer they are to you.',
)}
+ aria-label={messages.pgettext('accessibility', 'info')}
+ {...background}
+ />
+ </StyledLocationRowContainerWithMargin>
+ );
+}
+
+export function CustomExitLocationRow(props: SpecialLocationRowInnerProps<undefined>) {
+ const selectedRef = props.source.selected ? props.selectedElementRef : undefined;
+ const background = getButtonColor(props.source.selected, 0, props.source.disabled);
+ return (
+ <StyledLocationRowContainerWithMargin ref={selectedRef}>
+ <StyledLocationRowButton $level={0} {...background}>
<StyledLocationRowLabel>{props.source.label}</StyledLocationRowLabel>
</StyledLocationRowButton>
- {props.source.info && (
- <StyledLocationRowIcon
- as={StyledSpecialLocationInfoButton}
- message={props.source.info}
- aria-label={messages.pgettext('accessibility', 'info')}
- {...background}
+ </StyledLocationRowContainerWithMargin>
+ );
+}
+
+export function CustomBridgeLocationRow(
+ props: SpecialLocationRowInnerProps<SpecialBridgeLocationType>,
+) {
+ const history = useHistory();
+
+ const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
+ const icon = bridgeSettings.custom !== undefined ? 'icon-edit' : 'icon-add';
+
+ const selectedRef = props.source.selected ? props.selectedElementRef : undefined;
+ const background = getButtonColor(props.source.selected, 0, props.source.disabled);
+
+ const navigate = useCallback(() => history.push(RoutePath.editCustomBridge), [history.push]);
+
+ return (
+ <StyledLocationRowContainerWithMargin ref={selectedRef} disabled={props.source.disabled}>
+ <StyledLocationRowButton
+ as="button"
+ onClick={props.onSelect}
+ $level={0}
+ disabled={props.source.disabled}
+ {...background}>
+ <RelayStatusIndicator active selected={props.source.selected} />
+ <StyledLocationRowLabel>{props.source.label}</StyledLocationRowLabel>
+ </StyledLocationRowButton>
+ <StyledHoverInfoButton
+ {...background}
+ $isLast
+ title={messages.pgettext('select-location-view', 'Custom bridge')}
+ message={messages.pgettext(
+ 'select-location-view',
+ 'A custom bridge server can be used to circumvent censorship when regular Mullvad bridge servers don’t work.',
+ )}
+ />
+ <StyledLocationRowIcon {...background} onClick={navigate}>
+ <StyledSpecialLocationSideButton
+ source={icon}
+ width={18}
+ tintColor={colors.white}
+ tintHoverColor={colors.white80}
/>
- )}
+ </StyledLocationRowIcon>
</StyledLocationRowContainerWithMargin>
);
}
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 b795f4a74f..08ed2dccf3 100644
--- a/gui/src/renderer/components/select-location/select-location-hooks.ts
+++ b/gui/src/renderer/components/select-location/select-location-hooks.ts
@@ -1,6 +1,5 @@
import { useCallback } from 'react';
-import BridgeSettingsBuilder from '../../../shared/bridge-settings-builder';
import {
BridgeSettings,
RelayLocation,
@@ -10,8 +9,8 @@ import {
import log from '../../../shared/logging';
import { useAppContext } from '../../context';
import { useRelaySettingsModifier } from '../../lib/constraint-updater';
+import { useBridgeSettingsModifier } from '../../lib/constraint-updater';
import { useHistory } from '../../lib/history';
-import { useSelector } from '../../redux/store';
import { LocationType, SpecialBridgeLocationType } from './select-location-types';
import { useSelectLocationContext } from './SelectLocationContainer';
@@ -89,7 +88,7 @@ function useOnSelectLocation() {
export function useOnSelectBridgeLocation() {
const { updateBridgeSettings } = useAppContext();
const { setLocationType } = useSelectLocationContext();
- const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
+ const bridgeSettingsModifier = useBridgeSettingsModifier();
const setLocation = useCallback(async (bridgeUpdate: BridgeSettings) => {
if (bridgeUpdate) {
@@ -105,21 +104,38 @@ export function useOnSelectBridgeLocation() {
const onSelectRelay = useCallback(
(location: RelayLocation) => {
- const bridgeUpdate = new BridgeSettingsBuilder().location.fromRaw(location).build();
- bridgeUpdate.custom = bridgeSettings.custom;
- return setLocation(bridgeUpdate);
+ return setLocation(
+ bridgeSettingsModifier((bridgeSettings) => {
+ bridgeSettings.type = 'normal';
+ bridgeSettings.normal.location = wrapConstraint(location);
+ return bridgeSettings;
+ }),
+ );
},
- [bridgeSettings],
+ [bridgeSettingsModifier],
);
- const onSelectSpecial = useCallback((location: SpecialBridgeLocationType) => {
- switch (location) {
- case SpecialBridgeLocationType.closestToExit: {
- const bridgeUpdate = new BridgeSettingsBuilder().location.any().build();
- return setLocation(bridgeUpdate);
+ const onSelectSpecial = useCallback(
+ (location: SpecialBridgeLocationType) => {
+ switch (location) {
+ case SpecialBridgeLocationType.closestToExit:
+ return setLocation(
+ bridgeSettingsModifier((bridgeSettings) => {
+ bridgeSettings.normal.location = 'any';
+ return bridgeSettings;
+ }),
+ );
+ case SpecialBridgeLocationType.custom:
+ return setLocation(
+ bridgeSettingsModifier((bridgeSettings) => {
+ bridgeSettings.type = 'custom';
+ return bridgeSettings;
+ }),
+ );
}
- }
- }, []);
+ },
+ [bridgeSettingsModifier],
+ );
return [onSelectRelay, onSelectSpecial] as const;
}
diff --git a/gui/src/renderer/components/select-location/select-location-types.ts b/gui/src/renderer/components/select-location/select-location-types.ts
index c42cc45f6b..ca6afecf74 100644
--- a/gui/src/renderer/components/select-location/select-location-types.ts
+++ b/gui/src/renderer/components/select-location/select-location-types.ts
@@ -11,6 +11,7 @@ import {
IRelayLocationCountryRedux,
IRelayLocationRelayRedux,
} from '../../redux/settings/reducers';
+import { SpecialLocationRowInnerProps } from './SpecialLocationList';
export enum LocationType {
entry = 0,
@@ -21,11 +22,8 @@ export type RelayList = GeographicalRelayList | Array<CustomListSpecification>;
export type GeographicalRelayList = Array<CountrySpecification>;
export enum SpecialBridgeLocationType {
- closestToExit = 0,
-}
-
-export enum SpecialLocationIcon {
- geoLocation = 'icon-nearest',
+ closestToExit,
+ custom,
}
export interface LocationVisibility {
@@ -40,9 +38,8 @@ interface CommonLocationSpecification {
}
export interface SpecialLocation<T> extends CommonLocationSpecification {
- icon?: SpecialLocationIcon;
- info?: string;
value: T;
+ component: React.ComponentType<SpecialLocationRowInnerProps<T>>;
}
type GeographicalLocationSpecification =