diff options
Diffstat (limited to 'gui/src/renderer/components')
| -rw-r--r-- | gui/src/renderer/components/ChevronButton.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/CustomDnsSettings.tsx | 18 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountAddTime.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/NavigationBar.tsx | 27 | ||||
| -rw-r--r-- | gui/src/renderer/components/SplitTunnelingSettings.tsx | 20 | ||||
| -rw-r--r-- | gui/src/renderer/components/VpnSettings.tsx | 155 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardSettings.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/cell/Section.tsx | 64 | ||||
| -rw-r--r-- | gui/src/renderer/components/cell/Selector.tsx | 56 |
9 files changed, 205 insertions, 147 deletions
diff --git a/gui/src/renderer/components/ChevronButton.tsx b/gui/src/renderer/components/ChevronButton.tsx index 0485c22caf..6e16fdf6db 100644 --- a/gui/src/renderer/components/ChevronButton.tsx +++ b/gui/src/renderer/components/ChevronButton.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import styled from 'styled-components'; import { colors } from '../../config.json'; -import * as Cell from './cell'; +import { Icon } from './cell/Label'; interface IProps extends React.HTMLAttributes<HTMLButtonElement> { up: boolean; @@ -13,7 +13,7 @@ const Button = styled.button({ background: 'none', }); -const Icon = styled(Cell.Icon)({ +const StyledIcon = styled(Icon)({ flex: 0, alignSelf: 'stretch', justifyContent: 'center', @@ -24,7 +24,7 @@ export default function ChevronButton(props: IProps) { return ( <Button {...otherProps}> - <Icon + <StyledIcon tintColor={colors.white80} tintHoverColor={colors.white} source={up ? 'icon-chevron-up' : 'icon-chevron-down'} diff --git a/gui/src/renderer/components/CustomDnsSettings.tsx b/gui/src/renderer/components/CustomDnsSettings.tsx index bda3fbb864..fbf7d3dce0 100644 --- a/gui/src/renderer/components/CustomDnsSettings.tsx +++ b/gui/src/renderer/components/CustomDnsSettings.tsx @@ -4,6 +4,7 @@ import { sprintf } from 'sprintf-js'; import { colors, strings } from '../../config.json'; import { messages } from '../../shared/gettext'; import { useAppContext } from '../context'; +import { formatHtml } from '../lib/html-formatter'; import { IpAddress } from '../lib/ip'; import { useBoolean, useMounted } from '../lib/utilityHooks'; import { useSelector } from '../redux/store'; @@ -286,14 +287,15 @@ export default function CustomDnsSettings() { <Cell.CellFooterText> {featureAvailable ? messages.pgettext('vpn-settings-view', 'Enable to add at least one DNS server.') - : // This line makes sure that the next one isn't prefixed by the color. - // TRANSLATORS: This is displayed when either or both of the block ads/trackers settings are - // TRANSLATORS: turned on which makes the custom DNS setting disabled. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(preferencesPageName)s - The page title showed on top in the preferences page. - messages.pgettext( - 'vpn-settings-view', - 'Disable all content blockers to activate this setting.', + : formatHtml( + // TRANSLATORS: This is displayed when either or both of the block ads/trackers settings are + // TRANSLATORS: turned on which makes the custom DNS setting disabled. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(preferencesPageName)s - The page title showed on top in the preferences page. + messages.pgettext( + 'vpn-settings-view', + 'Disable all <b>DNS content blockers</b> above to activate this setting.', + ), )} </Cell.CellFooterText> </StyledCustomDnsFooter> diff --git a/gui/src/renderer/components/ExpiredAccountAddTime.tsx b/gui/src/renderer/components/ExpiredAccountAddTime.tsx index 14a2de74c4..54d27f75d8 100644 --- a/gui/src/renderer/components/ExpiredAccountAddTime.tsx +++ b/gui/src/renderer/components/ExpiredAccountAddTime.tsx @@ -280,7 +280,7 @@ function useFinishedCallback() { accountSetupFinished(); } - history.reset(RoutePath.main, undefined, transitions.push); + history.reset(RoutePath.main, transitions.push); }, [isNewAccount, accountSetupFinished, history]); return callback; diff --git a/gui/src/renderer/components/NavigationBar.tsx b/gui/src/renderer/components/NavigationBar.tsx index 4c27665b65..317a9bd26e 100644 --- a/gui/src/renderer/components/NavigationBar.tsx +++ b/gui/src/renderer/components/NavigationBar.tsx @@ -3,11 +3,8 @@ import React, { useCallback, useContext, useEffect, useLayoutEffect, useRef } fr import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; import { useAppContext } from '../context'; -import useActions from '../lib/actionsHook'; import { useHistory } from '../lib/history'; import { useCombinedRefs } from '../lib/utilityHooks'; -import { useSelector } from '../redux/store'; -import userInterface from '../redux/userinterface/actions'; import CustomScrollbars, { CustomScrollbarsRef, IScrollEvent } from './CustomScrollbars'; import { BackActionContext } from './KeyboardNavigation'; import { @@ -92,41 +89,35 @@ export const NavigationScrollbars = React.forwardRef(function NavigationScrollba forwardedRef?: React.Ref<CustomScrollbarsRef>, ) { const history = useHistory(); + const { setNavigationHistory } = useAppContext(); const { onScroll } = useContext(NavigationScrollContext); - const { setScrollPositions } = useAppContext(); const ref = useRef<CustomScrollbarsRef>(); const combinedRefs = useCombinedRefs(forwardedRef, ref); - const { addScrollPosition, removeScrollPosition } = useActions(userInterface); - const scrollPositions = useSelector((state) => state.userInterface.scrollPosition); - useEffect(() => { - const path = history.location.pathname; const beforeunload = () => { if (ref.current) { - const scrollPosition = ref.current.getScrollPosition(); - setScrollPositions({ ...scrollPositions, [path]: scrollPosition }); + history.location.state.scrollPosition = ref.current.getScrollPosition(); + setNavigationHistory(history.asObject); } }; window.addEventListener('beforeunload', beforeunload); return () => window.removeEventListener('beforeunload', beforeunload); - }, [scrollPositions]); + }, []); useLayoutEffect(() => { - const path = history.location.pathname; - - const scrollPosition = scrollPositions[history.location.pathname]; - if (history.action === 'POP' && scrollPosition) { - ref.current?.scrollTo(...scrollPosition); - removeScrollPosition(path); + const location = history.location; + if (history.action === 'POP') { + ref.current?.scrollTo(...location.state.scrollPosition); } return () => { if (history.action === 'PUSH' && ref.current) { - addScrollPosition(path, ref.current.getScrollPosition()); + location.state.scrollPosition = ref.current.getScrollPosition(); + setNavigationHistory(history.asObject); } }; }, []); diff --git a/gui/src/renderer/components/SplitTunnelingSettings.tsx b/gui/src/renderer/components/SplitTunnelingSettings.tsx index 4cad24f445..f68be19589 100644 --- a/gui/src/renderer/components/SplitTunnelingSettings.tsx +++ b/gui/src/renderer/components/SplitTunnelingSettings.tsx @@ -410,6 +410,16 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett splitTunnelingEnabled && (!filteredNonSplitApplications || filteredNonSplitApplications.length > 0); + const excludedTitle = ( + <Cell.SectionTitle> + {messages.pgettext('split-tunneling-view', 'Excluded apps')} + </Cell.SectionTitle> + ); + + const allTitle = ( + <Cell.SectionTitle>{messages.pgettext('split-tunneling-view', 'All apps')}</Cell.SectionTitle> + ); + return ( <> <SettingsHeader> @@ -428,10 +438,7 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett {splitTunnelingEnabled && <SearchBar searchTerm={searchTerm} onSearch={setSearchTerm} />} <Accordion expanded={showSplitSection}> - <Cell.Section> - <Cell.SectionTitle> - {messages.pgettext('split-tunneling-view', 'Excluded apps')} - </Cell.SectionTitle> + <Cell.Section sectionTitle={excludedTitle}> <ApplicationList applications={filteredSplitApplications} rowRenderer={excludedRowRenderer} @@ -440,10 +447,7 @@ export function WindowsSplitTunnelingSettings(props: IPlatformSplitTunnelingSett </Accordion> <Accordion expanded={showNonSplitSection}> - <Cell.Section> - <Cell.SectionTitle> - {messages.pgettext('split-tunneling-view', 'All apps')} - </Cell.SectionTitle> + <Cell.Section sectionTitle={allTitle}> <ApplicationList applications={filteredNonSplitApplications} rowRenderer={includedRowRenderer} diff --git a/gui/src/renderer/components/VpnSettings.tsx b/gui/src/renderer/components/VpnSettings.tsx index ad5d3439b1..1213703f61 100644 --- a/gui/src/renderer/components/VpnSettings.tsx +++ b/gui/src/renderer/components/VpnSettings.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'; import { sprintf } from 'sprintf-js'; import styled from 'styled-components'; -import { strings } from '../../config.json'; +import { colors, strings } from '../../config.json'; import { IDnsOptions, TunnelProtocol } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; @@ -47,6 +47,14 @@ const StyledSelectorContainer = styled.div({ flex: 0, }); +const StyledTitleLabel = styled(Cell.SectionTitle)({ + flex: 1, +}); + +const StyledSectionItem = styled(Cell.Container)({ + backgroundColor: colors.blue40, +}); + export default function VpnSettings() { const { pop } = useHistory(); @@ -82,11 +90,7 @@ export default function VpnSettings() { </Cell.Group> <Cell.Group> - <BlockAds /> - <BlockTrackers /> - <BlockMalware /> - <BlockGambling /> - <BlockAdultContent /> + <DnsBlockers /> </Cell.Group> <Cell.Group> @@ -230,38 +234,63 @@ function useDns(setting: keyof IDnsOptions['defaultOptions']) { return [dns, updateBlockSetting] as const; } +function DnsBlockers() { + const dns = useSelector((state) => state.settings.dns); + + const title = ( + <> + <StyledTitleLabel as="label" disabled={dns.state === 'custom'}> + {messages.pgettext('vpn-settings-view', 'DNS content blockers')} + </StyledTitleLabel> + <InfoButton> + <ModalMessage> + {messages.pgettext( + 'vpn-settings-view', + 'When this feature is enabled it stops the device from contacting certain domains or websites known for distributing ads, malware, trackers and more.', + )} + </ModalMessage> + <ModalMessage> + {messages.pgettext( + 'vpn-settings-view', + 'This might cause issues on certain websites, services, and programs.', + )} + </ModalMessage> + </InfoButton> + </> + ); + + return ( + <Cell.ExpandableSection sectionTitle={title} expandableId="dns-blockers"> + <BlockAds /> + <BlockTrackers /> + <BlockMalware /> + <BlockGambling /> + <BlockAdultContent /> + </Cell.ExpandableSection> + ); +} + function BlockAds() { const [dns, setBlockAds] = useDns('blockAds'); return ( <AriaInputGroup> - <Cell.Container disabled={dns.state === 'custom'}> + <StyledSectionItem disabled={dns.state === 'custom'}> <AriaLabel> - <Cell.InputLabel>{messages.pgettext('vpn-settings-view', 'Block ads')}</Cell.InputLabel> + <Cell.InputLabel> + { + // TRANSLATORS: Label for settings that enables ad blocking. + messages.pgettext('vpn-settings-view', 'Ads') + } + </Cell.InputLabel> </AriaLabel> - <AriaDetails> - <InfoButton> - <ModalMessage> - {messages.pgettext( - 'vpn-settings-view', - 'When enabled, this feature stops the device from contacting certain known ad domains.', - )} - </ModalMessage> - <ModalMessage> - {messages.pgettext( - 'vpn-settings-view', - 'Warning: This might cause issues on certain websites, services, and programs.', - )} - </ModalMessage> - </InfoButton> - </AriaDetails> <AriaInput> <Cell.Switch isOn={dns.state === 'default' && dns.defaultOptions.blockAds} onChange={setBlockAds} /> </AriaInput> - </Cell.Container> + </StyledSectionItem> </AriaInputGroup> ); } @@ -271,35 +300,22 @@ function BlockTrackers() { return ( <AriaInputGroup> - <Cell.Container disabled={dns.state === 'custom'}> + <StyledSectionItem disabled={dns.state === 'custom'}> <AriaLabel> <Cell.InputLabel> - {messages.pgettext('vpn-settings-view', 'Block trackers')} + { + // TRANSLATORS: Label for settings that enables tracker blocking. + messages.pgettext('vpn-settings-view', 'Trackers') + } </Cell.InputLabel> </AriaLabel> - <AriaDetails> - <InfoButton> - <ModalMessage> - {messages.pgettext( - 'vpn-settings-view', - 'When enabled, this feature stops the device from contacting certain domains known to track users.', - )} - </ModalMessage> - <ModalMessage> - {messages.pgettext( - 'vpn-settings-view', - 'Warning: This might cause issues on certain websites, services, and programs.', - )} - </ModalMessage> - </InfoButton> - </AriaDetails> <AriaInput> <Cell.Switch isOn={dns.state === 'default' && dns.defaultOptions.blockTrackers} onChange={setBlockTrackers} /> </AriaInput> - </Cell.Container> + </StyledSectionItem> </AriaInputGroup> ); } @@ -309,10 +325,13 @@ function BlockMalware() { return ( <AriaInputGroup> - <Cell.Container disabled={dns.state === 'custom'}> + <StyledSectionItem disabled={dns.state === 'custom'}> <AriaLabel> <Cell.InputLabel> - {messages.pgettext('vpn-settings-view', 'Block malware')} + { + // TRANSLATORS: Label for settings that enables malware blocking. + messages.pgettext('vpn-settings-view', 'Malware') + } </Cell.InputLabel> </AriaLabel> <AriaDetails> @@ -320,13 +339,7 @@ function BlockMalware() { <ModalMessage> {messages.pgettext( 'vpn-settings-view', - 'When enabled, this feature stops the device from contacting certain domains known to host malware.', - )} - </ModalMessage> - <ModalMessage> - {messages.pgettext( - 'vpn-settings-view', - 'Warning: This is not an anti-virus and should not be treated as such, this is just an extra layer of protection.', + 'Warning: The malware blocker is not an anti-virus and should not be treated as such, this is just an extra layer of protection.', )} </ModalMessage> </InfoButton> @@ -337,7 +350,7 @@ function BlockMalware() { onChange={setBlockMalware} /> </AriaInput> - </Cell.Container> + </StyledSectionItem> </AriaInputGroup> ); } @@ -347,27 +360,22 @@ function BlockGambling() { return ( <AriaInputGroup> - <Cell.Container disabled={dns.state === 'custom'}> + <StyledSectionItem disabled={dns.state === 'custom'}> <AriaLabel> <Cell.InputLabel> - {messages.pgettext('vpn-settings-view', 'Block gambling')} + { + // TRANSLATORS: Label for settings that enables block of gamling related websites. + messages.pgettext('vpn-settings-view', 'Gambling') + } </Cell.InputLabel> </AriaLabel> - <AriaDetails> - <InfoButton - message={messages.pgettext( - 'vpn-settings-view', - 'When enabled, this feature stops the device from contacting certain websites and services known to host gambling content.', - )} - /> - </AriaDetails> <AriaInput> <Cell.Switch isOn={dns.state === 'default' && dns.defaultOptions.blockGambling} onChange={setBlockGambling} /> </AriaInput> - </Cell.Container> + </StyledSectionItem> </AriaInputGroup> ); } @@ -377,27 +385,22 @@ function BlockAdultContent() { return ( <AriaInputGroup> - <Cell.Container disabled={dns.state === 'custom'}> + <StyledSectionItem disabled={dns.state === 'custom'}> <AriaLabel> <Cell.InputLabel> - {messages.pgettext('vpn-settings-view', 'Block adult content')} + { + // TRANSLATORS: Label for settings that enables block of adult content. + messages.pgettext('vpn-settings-view', 'Adult content') + } </Cell.InputLabel> </AriaLabel> - <AriaDetails> - <InfoButton - message={messages.pgettext( - 'vpn-settings-view', - 'When enabled, this feature stops the device from contacting certain websites and services known to host adult content.', - )} - /> - </AriaDetails> <AriaInput> <Cell.Switch isOn={dns.state === 'default' && dns.defaultOptions.blockAdultContent} onChange={setBlockAdultContent} /> </AriaInput> - </Cell.Container> + </StyledSectionItem> {dns.state === 'custom' && <CustomDnsEnabledFooter />} </AriaInputGroup> ); diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index dea8c6dbc2..18c39a7396 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -276,6 +276,8 @@ function Udp2tcpPortSetting() { [], ); + const expandableProps = useMemo(() => ({ expandable: true, id: 'udp2tcp-port' }), []); + const selectPort = useCallback( async (port: LiftedConstraint<number>) => { await setObfuscationSettings({ @@ -307,7 +309,7 @@ function Udp2tcpPortSetting() { value={port} onSelect={selectPort} disabled={obfuscationSettings.selectedObfuscation === ObfuscationType.off} - expandable + expandable={expandableProps} thinTitle automaticValue={'any' as const} /> diff --git a/gui/src/renderer/components/cell/Section.tsx b/gui/src/renderer/components/cell/Section.tsx index 38df17ebdb..8ba5975476 100644 --- a/gui/src/renderer/components/cell/Section.tsx +++ b/gui/src/renderer/components/cell/Section.tsx @@ -1,8 +1,14 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import styled from 'styled-components'; import { colors } from '../../../config.json'; +import { useAppContext } from '../../context'; +import { useHistory } from '../../lib/history'; +import { useBoolean } from '../../lib/utilityHooks'; +import Accordion from '../Accordion'; +import ChevronButton from '../ChevronButton'; import { buttonText, openSans, sourceSansPro } from '../common-styles'; +import { Container } from './Container'; import { Row } from './Row'; const StyledSection = styled.div({ @@ -25,11 +31,61 @@ export const SectionTitle = styled(Row)(buttonText, (props: SectionTitleProps) = export const CellSectionContext = React.createContext<boolean>(false); -export function Section(props: React.HTMLAttributes<HTMLDivElement>) { - const { children, ...otherProps } = props; +interface SectionProps extends React.HTMLAttributes<HTMLDivElement> { + sectionTitle?: React.ReactElement; +} + +export function Section(props: SectionProps) { + const { children, sectionTitle, ...otherProps } = props; return ( <StyledSection {...otherProps}> - <CellSectionContext.Provider value={true}>{children}</CellSectionContext.Provider> + <CellSectionContext.Provider value={true}> + {sectionTitle && <StyledTitleContainer>{sectionTitle}</StyledTitleContainer>} + {children} + </CellSectionContext.Provider> </StyledSection> ); } + +const StyledChevronButton = styled(ChevronButton)({ + padding: 0, + marginRight: '16px', +}); + +const StyledTitleContainer = styled(Container)({ + display: 'flex', + padding: 0, +}); + +interface ExpandableSectionProps extends SectionProps { + expandableId: string; + expandedInitially?: boolean; +} + +export function ExpandableSection(props: ExpandableSectionProps) { + const { expandableId, expandedInitially, sectionTitle, ...otherProps } = props; + + const history = useHistory(); + const { setNavigationHistory } = useAppContext(); + const expandedValue = + history.location.state.expandedSections[props.expandableId] ?? !!expandedInitially; + const [expanded, , , toggleExpanded] = useBoolean(expandedValue); + + useEffect(() => { + history.location.state.expandedSections[props.expandableId] = expanded; + setNavigationHistory(history.asObject); + }, [expanded]); + + const title = ( + <> + {sectionTitle} + <StyledChevronButton up={expanded} onClick={toggleExpanded} /> + </> + ); + + return ( + <Section className={props.className} sectionTitle={title} {...otherProps}> + <Accordion expanded={expanded}>{props.children}</Accordion> + </Section> + ); +} diff --git a/gui/src/renderer/components/cell/Selector.tsx b/gui/src/renderer/components/cell/Selector.tsx index cb0d6344b6..ed49444da1 100644 --- a/gui/src/renderer/components/cell/Selector.tsx +++ b/gui/src/renderer/components/cell/Selector.tsx @@ -3,28 +3,15 @@ import styled from 'styled-components'; import { colors } from '../../../config.json'; import { messages } from '../../../shared/gettext'; -import { useBoolean } from '../../lib/utilityHooks'; -import Accordion from '../Accordion'; import { AriaDetails, AriaInput, AriaLabel } from '../AriaGroup'; -import ChevronButton from '../ChevronButton'; import { normalText } from '../common-styles'; import InfoButton from '../InfoButton'; import * as Cell from '.'; -const StyledTitle = styled(Cell.Container)({ - display: 'flex', - padding: 0, -}); - const StyledTitleLabel = styled(Cell.SectionTitle)({ flex: 1, }); -const StyledChevronButton = styled(ChevronButton)({ - padding: 0, - marginRight: '16px', -}); - export interface SelectorItem<T> { label: string; value: T; @@ -39,7 +26,7 @@ interface CommonSelectorProps<T, U> { selectedCellRef?: React.Ref<HTMLElement>; className?: string; details?: React.ReactElement; - expandable?: boolean; + expandable?: { expandable: boolean; id: string }; disabled?: boolean; thinTitle?: boolean; automaticLabel?: string; @@ -52,8 +39,6 @@ interface SelectorProps<T, U> extends CommonSelectorProps<T, U> { } export default function Selector<T, U>(props: SelectorProps<T, U>) { - const [expanded, , , toggleExpanded] = useBoolean(!props.expandable); - const items = props.items.map((item) => { const selected = props.value === item.value; const ref = selected ? (props.selectedCellRef as React.Ref<HTMLButtonElement>) : undefined; @@ -88,8 +73,8 @@ export default function Selector<T, U>(props: SelectorProps<T, U>) { ); } - const title = props.title && ( - <StyledTitle> + const title = props.title ? ( + <> <AriaLabel> <StyledTitleLabel as="label" disabled={props.disabled} thin={props.thinTitle}> {props.title} @@ -100,8 +85,9 @@ export default function Selector<T, U>(props: SelectorProps<T, U>) { <InfoButton>{props.details}</InfoButton> </AriaDetails> )} - {props.expandable && <StyledChevronButton up={expanded} onClick={toggleExpanded} />} - </StyledTitle> + </> + ) : ( + <></> ); // Add potential additional items to the list. Used for custom entry. @@ -112,14 +98,28 @@ export default function Selector<T, U>(props: SelectorProps<T, U>) { </Cell.Group> ); - return ( - <AriaInput> - <Cell.Section role="listbox" className={props.className}> - {title} - {props.expandable ? <Accordion expanded={expanded}>{children}</Accordion> : children} - </Cell.Section> - </AriaInput> - ); + if (props.expandable?.expandable) { + return ( + <AriaInput> + <Cell.ExpandableSection + role="listbox" + expandedInitially={false} + className={props.className} + sectionTitle={title} + expandableId={props.expandable.id}> + {children} + </Cell.ExpandableSection> + </AriaInput> + ); + } else { + return ( + <AriaInput> + <Cell.Section role="listbox" className={props.className} sectionTitle={title}> + {children} + </Cell.Section> + </AriaInput> + ); + } } const StyledCellIcon = styled(Cell.Icon)((props: { visible: boolean }) => ({ |
