summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/components
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-05-19 10:06:32 +0200
committerOskar Nyberg <oskar@mullvad.net>2022-05-19 10:06:32 +0200
commit9cca46231f623247fac50694a66fb5eedada15d8 (patch)
treea62db50609fe2679d1f1105558a4712f8300693d /gui/src/renderer/components
parent0211a43e2f9e1b7beccaefd9a2a09672f1877fd8 (diff)
parenta39213f52d76ddf08ed0a637a0fe831b3581466e (diff)
downloadmullvadvpn-9cca46231f623247fac50694a66fb5eedada15d8.tar.xz
mullvadvpn-9cca46231f623247fac50694a66fb5eedada15d8.zip
Merge branch 'update-filters'
Diffstat (limited to 'gui/src/renderer/components')
-rw-r--r--gui/src/renderer/components/AppRouter.tsx4
-rw-r--r--gui/src/renderer/components/Filter.tsx274
-rw-r--r--gui/src/renderer/components/FilterByProvider.tsx209
-rw-r--r--gui/src/renderer/components/SelectLocation.tsx156
-rw-r--r--gui/src/renderer/components/SelectLocationStyles.tsx37
5 files changed, 364 insertions, 316 deletions
diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx
index 278ad0aafc..db69cbb80f 100644
--- a/gui/src/renderer/components/AppRouter.tsx
+++ b/gui/src/renderer/components/AppRouter.tsx
@@ -22,7 +22,7 @@ import {
VoucherInput,
VoucherVerificationSuccess,
} from './ExpiredAccountAddTime';
-import FilterByProvider from './FilterByProvider';
+import Filter from './Filter';
import Focus, { IFocusHandle } from './Focus';
import Launch from './Launch';
import MainView from './MainView';
@@ -96,7 +96,7 @@ class AppRouter extends React.Component<IHistoryProps & IAppContext, IAppRoutesS
<Route exact path={RoutePath.splitTunneling} component={SplitTunnelingSettings} />
<Route exact path={RoutePath.support} component={SupportPage} />
<Route exact path={RoutePath.selectLocation} component={SelectLocationPage} />
- <Route exact path={RoutePath.filterByProvider} component={FilterByProvider} />
+ <Route exact path={RoutePath.filter} component={Filter} />
</Switch>
</TransitionView>
</TransitionContainer>
diff --git a/gui/src/renderer/components/Filter.tsx b/gui/src/renderer/components/Filter.tsx
new file mode 100644
index 0000000000..a7b8272831
--- /dev/null
+++ b/gui/src/renderer/components/Filter.tsx
@@ -0,0 +1,274 @@
+import { useCallback, useMemo, useState } from 'react';
+import styled from 'styled-components';
+
+import { colors } from '../../config.json';
+import { Ownership } from '../../shared/daemon-rpc-types';
+import { messages } from '../../shared/gettext';
+import { useAppContext } from '../context';
+import { useHistory } from '../lib/history';
+import { useBoolean } from '../lib/utilityHooks';
+import { IReduxState, useSelector } from '../redux/store';
+import Accordion from './Accordion';
+import * as AppButton from './AppButton';
+import { AriaInputGroup, AriaLabel } from './AriaGroup';
+import * as Cell from './cell';
+import Selector from './cell/Selector';
+import { normalText } from './common-styles';
+import ImageView from './ImageView';
+import { BackAction } from './KeyboardNavigation';
+import { Container, Layout } from './Layout';
+import {
+ NavigationBar,
+ NavigationContainer,
+ NavigationItems,
+ NavigationScrollbars,
+ TitleBarItem,
+} from './NavigationBar';
+
+const StyledContainer = styled(Container)({
+ backgroundColor: colors.darkBlue,
+});
+
+const StyledNavigationScrollbars = styled(NavigationScrollbars)({
+ backgroundColor: colors.darkBlue,
+ flex: 1,
+});
+
+const StyledFooter = styled.div({
+ display: 'flex',
+ flexDirection: 'column',
+ padding: '18px 22px 22px',
+});
+
+export default function Filter() {
+ const history = useHistory();
+ const { updateRelaySettings } = useAppContext();
+
+ const initialProviders = useSelector(providersSelector);
+ const [providers, setProviders] = useState<Record<string, boolean>>(initialProviders);
+
+ const initialOwnership = useSelector((state) =>
+ 'normal' in state.settings.relaySettings
+ ? state.settings.relaySettings.normal.ownership
+ : Ownership.any,
+ );
+ const [ownership, setOwnership] = useState<Ownership>(initialOwnership);
+
+ const onApply = useCallback(async () => {
+ // If all providers are selected it's represented as an empty array.
+ const selectedProviders = Object.values(providers).every((provider) => provider)
+ ? []
+ : Object.entries(providers)
+ .filter(([, selected]) => selected)
+ .map(([name]) => name);
+
+ await updateRelaySettings({ normal: { providers: selectedProviders, ownership } });
+ history.pop();
+ }, [providers, ownership, history, updateRelaySettings]);
+
+ return (
+ <BackAction action={history.pop}>
+ <Layout>
+ <StyledContainer>
+ <NavigationContainer>
+ <NavigationBar alwaysDisplayBarTitle={true}>
+ <NavigationItems>
+ <TitleBarItem>
+ {
+ // TRANSLATORS: Title label in navigation bar
+ messages.pgettext('filter-nav', 'Filter')
+ }
+ </TitleBarItem>
+ </NavigationItems>
+ </NavigationBar>
+ <StyledNavigationScrollbars>
+ <FilterByOwnership ownership={ownership} setOwnership={setOwnership} />
+ <FilterByProvider providers={providers} setProviders={setProviders} />
+ </StyledNavigationScrollbars>
+ <StyledFooter>
+ <AppButton.GreenButton
+ disabled={Object.values(providers).every((provider) => !provider)}
+ onClick={onApply}>
+ {messages.gettext('Apply')}
+ </AppButton.GreenButton>
+ </StyledFooter>
+ </NavigationContainer>
+ </StyledContainer>
+ </Layout>
+ </BackAction>
+ );
+}
+
+function providersSelector(state: IReduxState): Record<string, boolean> {
+ const providerConstraint =
+ 'normal' in state.settings.relaySettings ? state.settings.relaySettings.normal.providers : [];
+
+ const relays = state.settings.relayLocations.concat(
+ state.settings.bridgeState === 'on' ? state.settings.bridgeLocations : [],
+ );
+ const providers = relays.flatMap((country) =>
+ country.cities.flatMap((city) => city.relays.map((relay) => relay.provider)),
+ );
+ const uniqueProviders = removeDuplicates(providers).sort((a, b) => a.localeCompare(b));
+
+ // Empty containt array means that all providers are selected. No selection isn't possible.
+ return Object.fromEntries(
+ uniqueProviders.map((provider) => [
+ provider,
+ providerConstraint.length === 0 || providerConstraint.includes(provider),
+ ]),
+ );
+}
+
+const StyledSelector = (styled(Selector)({
+ marginBottom: 0,
+}) as unknown) as new <T>() => Selector<T>;
+
+interface IFilterByOwnershipProps {
+ ownership: Ownership;
+ setOwnership: (ownership: Ownership) => void;
+}
+
+function FilterByOwnership(props: IFilterByOwnershipProps) {
+ const [expanded, , , toggleExpanded] = useBoolean(false);
+
+ const values = useMemo(
+ () => [
+ {
+ label: messages.gettext('Any'),
+ value: Ownership.any,
+ },
+ {
+ label: messages.pgettext('filter-view', 'Mullvad owned only'),
+ value: Ownership.mullvadOwned,
+ },
+ {
+ label: messages.pgettext('filter-view', 'Rented only'),
+ value: Ownership.rented,
+ },
+ ],
+ [],
+ );
+
+ return (
+ <AriaInputGroup>
+ <Cell.CellButton onClick={toggleExpanded}>
+ <AriaLabel>
+ <Cell.Label>{messages.pgettext('filter-view', 'Ownership')}</Cell.Label>
+ </AriaLabel>
+ <ImageView
+ tintColor={colors.white80}
+ source={expanded ? 'icon-chevron-up' : 'icon-chevron-down'}
+ height={24}
+ />
+ </Cell.CellButton>
+
+ <Accordion expanded={expanded}>
+ <StyledSelector values={values} value={props.ownership} onSelect={props.setOwnership} />
+ </Accordion>
+ </AriaInputGroup>
+ );
+}
+
+interface IFilterByProviderProps {
+ providers: Record<string, boolean>;
+ setProviders: (providers: (previous: Record<string, boolean>) => Record<string, boolean>) => void;
+}
+
+function FilterByProvider(props: IFilterByProviderProps) {
+ const [expanded, , , toggleExpanded] = useBoolean(false);
+
+ const onToggle = useCallback(
+ (provider: string) =>
+ props.setProviders((providers) => ({ ...providers, [provider]: !providers[provider] })),
+ [props.setProviders],
+ );
+
+ const toggleAll = useCallback(() => {
+ props.setProviders((providers) => {
+ const shouldSelect = !Object.values(providers).every((value) => value);
+ return Object.fromEntries(Object.keys(providers).map((provider) => [provider, shouldSelect]));
+ });
+ }, []);
+
+ return (
+ <>
+ <Cell.CellButton onClick={toggleExpanded}>
+ <Cell.Label>{messages.pgettext('filter-view', 'Providers')}</Cell.Label>
+ <ImageView
+ tintColor={colors.white80}
+ source={expanded ? 'icon-chevron-up' : 'icon-chevron-down'}
+ height={24}
+ />
+ </Cell.CellButton>
+ <Accordion expanded={expanded}>
+ <CheckboxRow
+ label={messages.pgettext('filter-view', 'All providers')}
+ bold
+ checked={Object.values(props.providers).every((value) => value)}
+ onChange={toggleAll}
+ />
+ {Object.entries(props.providers).map(([provider, checked]) => (
+ <CheckboxRow key={provider} label={provider} checked={checked} onChange={onToggle} />
+ ))}
+ </Accordion>
+ </>
+ );
+}
+
+interface IStyledRowTitleProps {
+ bold?: boolean;
+}
+
+const StyledRow = styled.div({
+ display: 'flex',
+ height: '44px',
+ alignItems: 'center',
+ padding: '0 22px',
+ marginBottom: '1px',
+ backgroundColor: colors.blue,
+});
+
+const StyledCheckbox = styled.div({
+ width: '24px',
+ height: '24px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: colors.white,
+ borderRadius: '4px',
+});
+
+const StyledRowTitle = styled.label(normalText, (props: IStyledRowTitleProps) => ({
+ fontWeight: props.bold ? 600 : 400,
+ color: colors.white,
+ marginLeft: '22px',
+}));
+
+interface ICheckboxRowProps extends IStyledRowTitleProps {
+ label: string;
+ checked: boolean;
+ onChange: (provider: string) => void;
+}
+
+function CheckboxRow(props: ICheckboxRowProps) {
+ const onToggle = useCallback(() => props.onChange(props.label), [props.onChange, props.label]);
+
+ return (
+ <StyledRow onClick={onToggle}>
+ <StyledCheckbox role="checkbox" aria-label={props.label} aria-checked={props.checked}>
+ {props.checked && <ImageView source="icon-tick" width={18} tintColor={colors.green} />}
+ </StyledCheckbox>
+ <StyledRowTitle aria-hidden bold={props.bold}>
+ {props.label}
+ </StyledRowTitle>
+ </StyledRow>
+ );
+}
+
+function removeDuplicates(list: string[]): string[] {
+ return list.reduce(
+ (result, current) => (result.includes(current) ? result : [...result, current]),
+ [] as string[],
+ );
+}
diff --git a/gui/src/renderer/components/FilterByProvider.tsx b/gui/src/renderer/components/FilterByProvider.tsx
deleted file mode 100644
index 335d8597d8..0000000000
--- a/gui/src/renderer/components/FilterByProvider.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import { useCallback, useMemo, useState } from 'react';
-import styled from 'styled-components';
-
-import { colors } from '../../config.json';
-import { messages } from '../../shared/gettext';
-import { useAppContext } from '../context';
-import { useHistory } from '../lib/history';
-import { useSelector } from '../redux/store';
-import * as AppButton from './AppButton';
-import { normalText } from './common-styles';
-import ImageView from './ImageView';
-import { BackAction } from './KeyboardNavigation';
-import { Container, Layout } from './Layout';
-import {
- NavigationBar,
- NavigationContainer,
- NavigationItems,
- NavigationScrollbars,
- TitleBarItem,
-} from './NavigationBar';
-
-const StyledContainer = styled(Container)({
- backgroundColor: colors.darkBlue,
-});
-
-const StyledNavigationScrollbars = styled(NavigationScrollbars)({
- backgroundColor: colors.darkBlue,
- flex: 1,
-});
-
-const StyledFooter = styled.div({
- display: 'flex',
- flexDirection: 'column',
- padding: '18px 22px 22px',
-});
-
-enum Selection {
- all,
- some,
- none,
-}
-
-export default function FilterByProvider() {
- const history = useHistory();
- const { updateRelaySettings } = useAppContext();
-
- const serverList = useSelector((state) =>
- state.settings.relayLocations.concat(
- state.settings.bridgeState === 'on' ? state.settings.bridgeLocations : [],
- ),
- );
- const providerConstraint = useSelector((state) => {
- if ('normal' in state.settings.relaySettings) {
- return state.settings.relaySettings.normal.providers;
- } else {
- return [];
- }
- });
-
- const [providers, setProviders] = useState(() => {
- const providers = serverList.flatMap((country) =>
- country.cities.flatMap((city) => city.relays.map((relay) => relay.provider)),
- );
- const uniqueProviders = removeDuplicates(providers).sort((a, b) => a.localeCompare(b));
-
- return Object.fromEntries(
- uniqueProviders.map((provider) => [
- provider,
- providerConstraint.length === 0 || providerConstraint.includes(provider),
- ]),
- );
- });
-
- const selectionStatus = useMemo(() => {
- if (Object.values(providers).every((value) => value)) {
- return Selection.all;
- } else if (Object.values(providers).every((value) => !value)) {
- return Selection.none;
- } else {
- return Selection.some;
- }
- }, [providers]);
-
- const onCheck = useCallback((provider: string) => {
- setProviders((providers) => ({ ...providers, [provider]: !providers[provider] }));
- }, []);
-
- const toggleAll = useCallback(() => {
- setProviders((providers) =>
- Object.fromEntries(
- Object.keys(providers).map((provider) => [provider, selectionStatus !== Selection.all]),
- ),
- );
- }, [selectionStatus]);
-
- const onApply = useCallback(async () => {
- const selectedProviders =
- selectionStatus === Selection.all
- ? []
- : Object.entries(providers)
- .filter(([, selected]) => selected)
- .map(([provider]) => provider);
-
- await updateRelaySettings({ normal: { providers: selectedProviders } });
-
- history.pop();
- }, [providers, history, updateRelaySettings, selectionStatus]);
-
- return (
- <BackAction action={history.pop}>
- <Layout>
- <StyledContainer>
- <NavigationContainer>
- <NavigationBar alwaysDisplayBarTitle={true}>
- <NavigationItems>
- <TitleBarItem>
- {
- // TRANSLATORS: Title label in navigation bar
- messages.pgettext('filter-by-provider-nav', 'Filter by provider')
- }
- </TitleBarItem>
- </NavigationItems>
- </NavigationBar>
- <StyledNavigationScrollbars>
- <ProviderRow
- provider={messages.pgettext('filter-by-provider-view', 'All providers')}
- bold
- checked={selectionStatus === Selection.all}
- onCheck={toggleAll}
- />
- {Object.entries(providers).map(([provider, checked]) => (
- <ProviderRow
- key={provider}
- provider={provider}
- checked={checked}
- onCheck={onCheck}
- />
- ))}
- </StyledNavigationScrollbars>
- <StyledFooter>
- <AppButton.GreenButton
- disabled={selectionStatus === Selection.none}
- onClick={onApply}>
- {messages.gettext('Apply')}
- </AppButton.GreenButton>
- </StyledFooter>
- </NavigationContainer>
- </StyledContainer>
- </Layout>
- </BackAction>
- );
-}
-
-interface IStyledRowTitleProps {
- bold?: boolean;
-}
-
-const StyledRow = styled.div({
- display: 'flex',
- height: '44px',
- alignItems: 'center',
- padding: '0 22px',
- marginBottom: '1px',
- backgroundColor: colors.blue,
-});
-
-const StyledCheckbox = styled.div({
- width: '24px',
- height: '24px',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: colors.white,
- borderRadius: '4px',
-});
-
-const StyledRowTitle = styled.label(normalText, (props: IStyledRowTitleProps) => ({
- fontWeight: props.bold ? 600 : 400,
- color: colors.white,
- marginLeft: '22px',
-}));
-
-interface IProviderRowProps extends IStyledRowTitleProps {
- provider: string;
- checked: boolean;
- onCheck: (provider: string) => void;
-}
-
-function ProviderRow(props: IProviderRowProps) {
- const onCheck = useCallback(() => props.onCheck(props.provider), [props.onCheck, props.provider]);
-
- return (
- <StyledRow onClick={onCheck}>
- <StyledCheckbox role="checkbox" aria-label={props.provider} aria-checked={props.checked}>
- {props.checked && <ImageView source="icon-tick" width={18} tintColor={colors.green} />}
- </StyledCheckbox>
- <StyledRowTitle aria-hidden bold={props.bold}>
- {props.provider}
- </StyledRowTitle>
- </StyledRow>
- );
-}
-
-function removeDuplicates(list: string[]): string[] {
- return list.reduce(
- (result, current) => (result.includes(current) ? result : [...result, current]),
- [] as string[],
- );
-}
diff --git a/gui/src/renderer/components/SelectLocation.tsx b/gui/src/renderer/components/SelectLocation.tsx
index 3d64906a78..34059f3e85 100644
--- a/gui/src/renderer/components/SelectLocation.tsx
+++ b/gui/src/renderer/components/SelectLocation.tsx
@@ -2,7 +2,12 @@ import React from 'react';
import { sprintf } from 'sprintf-js';
import { colors } from '../../config.json';
-import { LiftedConstraint, RelayLocation, TunnelProtocol } from '../../shared/daemon-rpc-types';
+import {
+ LiftedConstraint,
+ Ownership,
+ RelayLocation,
+ TunnelProtocol,
+} from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
import { IRelayLocationRedux } from '../redux/settings/reducers';
import BridgeLocations, { SpecialBridgeLocationType } from './BridgeLocations';
@@ -25,16 +30,13 @@ import {
} from './NavigationBar';
import { ScopeBarItem } from './ScopeBar';
import {
- StyledClearProvidersButton,
+ StyledClearFilterButton,
StyledContainer,
StyledContent,
- StyledFilterByProviderButton,
- StyledFilterContainer,
+ StyledFilter,
StyledFilterIconButton,
- StyledFilterMenu,
+ StyledFilterRow,
StyledNavigationBarAttachment,
- StyledProviderCountRow,
- StyledProvidersCount,
StyledScopeBar,
StyledSettingsHeader,
} from './SelectLocationStyles';
@@ -50,13 +52,15 @@ interface IProps {
allowEntrySelection: boolean;
tunnelProtocol: LiftedConstraint<TunnelProtocol>;
providers: string[];
+ ownership: Ownership;
onClose: () => void;
- onViewFilterByProvider: () => void;
+ onViewFilter: () => void;
onSelectExitLocation: (location: RelayLocation) => void;
onSelectEntryLocation: (location: RelayLocation) => void;
onSelectBridgeLocation: (location: RelayLocation) => void;
onSelectClosestToExit: () => void;
onClearProviders: () => void;
+ onClearOwnership: () => void;
}
enum LocationScope {
@@ -65,7 +69,6 @@ enum LocationScope {
}
interface IState {
- showFilterMenu: boolean;
headingHeight: number;
locationScope: LocationScope;
}
@@ -76,7 +79,7 @@ interface ISelectLocationSnapshot {
}
export default class SelectLocation extends React.Component<IProps, IState> {
- public state = { showFilterMenu: false, headingHeight: 0, locationScope: LocationScope.exit };
+ public state = { headingHeight: 0, locationScope: LocationScope.exit };
private scrollView = React.createRef<CustomScrollbarsRef>();
private spacePreAllocationViewRef = React.createRef<SpacePreAllocationView>();
@@ -90,7 +93,6 @@ export default class SelectLocation extends React.Component<IProps, IState> {
private snapshotByScope: Partial<Record<LocationScope, ISelectLocationSnapshot>> = {};
- private filterButtonRef = React.createRef<HTMLDivElement>();
private headerRef = React.createRef<HTMLHeadingElement>();
public componentDidMount() {
@@ -132,9 +134,12 @@ export default class SelectLocation extends React.Component<IProps, IState> {
}
public render() {
+ const showOwnershipFilter = this.props.ownership !== Ownership.any;
+ const showProvidersFilter = this.props.providers.length > 0;
+ const showFilters = showOwnershipFilter || showProvidersFilter;
return (
<BackAction icon="close" action={this.props.onClose}>
- <Layout onClick={this.onClickAnywhere}>
+ <Layout>
<StyledContainer>
<NavigationContainer>
<NavigationBar>
@@ -146,26 +151,17 @@ export default class SelectLocation extends React.Component<IProps, IState> {
}
</TitleBarItem>
- <StyledFilterContainer ref={this.filterButtonRef}>
- <StyledFilterIconButton
- onClick={this.toggleFilterMenu}
- aria-label={messages.gettext('Filter')}>
- <ImageView
- source="icon-filter-round"
- tintColor={colors.white40}
- tintHoverColor={colors.white60}
- height={24}
- width={24}
- />
- </StyledFilterIconButton>
- {this.state.showFilterMenu && (
- <StyledFilterMenu>
- <StyledFilterByProviderButton onClick={this.props.onViewFilterByProvider}>
- {messages.pgettext('select-location-view', 'Filter by provider')}
- </StyledFilterByProviderButton>
- </StyledFilterMenu>
- )}
- </StyledFilterContainer>
+ <StyledFilterIconButton
+ onClick={this.props.onViewFilter}
+ aria-label={messages.gettext('Filter')}>
+ <ImageView
+ source="icon-filter-round"
+ tintColor={colors.white40}
+ tintHoverColor={colors.white60}
+ height={24}
+ width={24}
+ />
+ </StyledFilterIconButton>
</NavigationItems>
</NavigationBar>
<NavigationScrollbars ref={this.scrollView}>
@@ -181,32 +177,52 @@ export default class SelectLocation extends React.Component<IProps, IState> {
{this.renderHeaderSubtitle()}
</StyledSettingsHeader>
- {this.props.providers.length > 0 && (
- <StyledProviderCountRow>
+ {showFilters && (
+ <StyledFilterRow>
{messages.pgettext('select-location-view', 'Filtered:')}
- <StyledProvidersCount>
- {sprintf(
- messages.pgettext(
- 'select-location-view',
- 'Providers: %(numberOfProviders)d',
- ),
- {
- numberOfProviders: this.props.providers.length,
- },
- )}
- <StyledClearProvidersButton
- aria-label={messages.gettext('Clear')}
- onClick={this.props.onClearProviders}>
- <ImageView
- height={16}
- width={16}
- source="icon-close"
- tintColor={colors.white60}
- tintHoverColor={colors.white80}
- />
- </StyledClearProvidersButton>
- </StyledProvidersCount>
- </StyledProviderCountRow>
+
+ {showOwnershipFilter && (
+ <StyledFilter>
+ {this.ownershipFilterLabel()}
+ <StyledClearFilterButton
+ aria-label={messages.gettext('Clear')}
+ onClick={this.props.onClearOwnership}>
+ <ImageView
+ height={16}
+ width={16}
+ source="icon-close"
+ tintColor={colors.white60}
+ tintHoverColor={colors.white80}
+ />
+ </StyledClearFilterButton>
+ </StyledFilter>
+ )}
+
+ {showProvidersFilter && (
+ <StyledFilter>
+ {sprintf(
+ messages.pgettext(
+ 'select-location-view',
+ 'Providers: %(numberOfProviders)d',
+ ),
+ {
+ numberOfProviders: this.props.providers.length,
+ },
+ )}
+ <StyledClearFilterButton
+ aria-label={messages.gettext('Clear')}
+ onClick={this.props.onClearProviders}>
+ <ImageView
+ height={16}
+ width={16}
+ source="icon-close"
+ tintColor={colors.white60}
+ tintHoverColor={colors.white80}
+ />
+ </StyledClearFilterButton>
+ </StyledFilter>
+ )}
+ </StyledFilterRow>
)}
{this.props.allowEntrySelection && (
<StyledScopeBar
@@ -242,6 +258,17 @@ export default class SelectLocation extends React.Component<IProps, IState> {
}
}
+ private ownershipFilterLabel(): string {
+ switch (this.props.ownership) {
+ case Ownership.mullvadOwned:
+ return messages.pgettext('filter-view', 'Owned');
+ case Ownership.rented:
+ return messages.pgettext('filter-view', 'Rented');
+ default:
+ throw new Error('Only owned and rented should make label visible');
+ }
+ }
+
private getLocationListRef(prevProps: IProps, prevState: IState) {
if (prevState.locationScope === LocationScope.exit) {
return this.exitLocationList.current;
@@ -413,21 +440,6 @@ export default class SelectLocation extends React.Component<IProps, IState> {
this.spacePreAllocationViewRef.current?.allocate(expandedContentHeight);
this.scrollView.current?.scrollIntoView(locationRect);
};
-
- private toggleFilterMenu = () => {
- this.setState((state) => ({
- showFilterMenu: !state.showFilterMenu,
- }));
- };
-
- private onClickAnywhere = (event: React.MouseEvent<HTMLDivElement>) => {
- if (
- this.state.showFilterMenu &&
- !this.filterButtonRef.current?.contains(event.target as HTMLElement)
- ) {
- this.setState({ showFilterMenu: false });
- }
- };
}
interface ISpacePreAllocationView {
diff --git a/gui/src/renderer/components/SelectLocationStyles.tsx b/gui/src/renderer/components/SelectLocationStyles.tsx
index 4ace3b15cc..15211389f6 100644
--- a/gui/src/renderer/components/SelectLocationStyles.tsx
+++ b/gui/src/renderer/components/SelectLocationStyles.tsx
@@ -29,13 +29,8 @@ export const StyledNavigationBarAttachment = styled.div({}, (props: { top: numbe
zIndex: 1,
}));
-export const StyledFilterContainer = styled.div({
- display: 'flex',
- position: 'relative',
- justifySelf: 'end',
-});
-
export const StyledFilterIconButton = styled.button({
+ justifySelf: 'end',
borderWidth: 0,
padding: 0,
margin: 0,
@@ -43,43 +38,19 @@ export const StyledFilterIconButton = styled.button({
backgroundColor: 'transparent',
});
-export const StyledFilterMenu = styled.div({
- position: 'absolute',
- top: 'calc(100% + 4px)',
- right: '0',
- borderRadius: '4px',
- backgroundColor: colors.darkBlue,
- overflow: 'hidden',
- zIndex: 2,
-});
-
-export const StyledFilterByProviderButton = styled.button(tinyText, {
- borderWidth: 0,
- margin: 0,
- cursor: 'default',
- color: colors.white,
- padding: '7px 15px',
- whiteSpace: 'nowrap',
- borderRadius: 0,
- backgroundColor: colors.blue,
- ':hover': {
- backgroundColor: colors.blue80,
- },
-});
-
export const StyledSettingsHeader = styled(SettingsHeader)({
paddingLeft: '6px',
paddingBottom: '11px',
});
-export const StyledProviderCountRow = styled.div({
+export const StyledFilterRow = styled.div({
...tinyText,
color: colors.white,
marginLeft: '6px',
marginBottom: '8px',
});
-export const StyledProvidersCount = styled.div({
+export const StyledFilter = styled.div({
...tinyText,
display: 'inline-flex',
alignItems: 'center',
@@ -90,7 +61,7 @@ export const StyledProvidersCount = styled.div({
color: colors.white,
});
-export const StyledClearProvidersButton = styled.div({
+export const StyledClearFilterButton = styled.div({
display: 'inline-block',
borderWidth: 0,
padding: 0,