diff options
Diffstat (limited to 'gui/src')
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 = |
