diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2022-07-15 17:43:46 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-07-22 14:36:05 +0200 |
| commit | 566b5a118bebb5633593523badea643971c96b97 (patch) | |
| tree | 12d0849640b8888e95e25c9e0ad43f12cc9730cf | |
| parent | 33a452faefd260b31fc84ea4569735e465feed96 (diff) | |
| download | mullvadvpn-566b5a118bebb5633593523badea643971c96b97.tar.xz mullvadvpn-566b5a118bebb5633593523badea643971c96b97.zip | |
Refactor Settings.tsx
| -rw-r--r-- | gui/src/renderer/components/AppRouter.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 411 | ||||
| -rw-r--r-- | gui/src/renderer/components/SettingsStyles.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/containers/SettingsPage.tsx | 36 | ||||
| -rw-r--r-- | gui/src/renderer/lib/routes.ts | 2 |
5 files changed, 203 insertions, 256 deletions
diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx index e9355775b2..75892805e9 100644 --- a/gui/src/renderer/components/AppRouter.tsx +++ b/gui/src/renderer/components/AppRouter.tsx @@ -8,7 +8,6 @@ import OpenVPNSettingsPage from '../containers/OpenVPNSettingsPage'; import ProblemReportPage from '../containers/ProblemReportPage'; import SelectLanguagePage from '../containers/SelectLanguagePage'; import SelectLocationPage from '../containers/SelectLocationPage'; -import SettingsPage from '../containers/SettingsPage'; import WireguardSettingsPage from '../containers/WireguardSettingsPage'; import withAppContext, { IAppContext } from '../context'; import { IHistoryProps, ITransitionSpecification, transitions, withHistory } from '../lib/history'; @@ -25,6 +24,7 @@ import Focus, { IFocusHandle } from './Focus'; import InterfaceSettings from './InterfaceSettings'; import Launch from './Launch'; import MainView from './MainView'; +import Settings from './Settings'; import SplitTunnelingSettings from './SplitTunnelingSettings'; import Support from './Support'; import TooManyDevices from './TooManyDevices'; @@ -87,7 +87,7 @@ class AppRouter extends React.Component<IHistoryProps & IAppContext, IAppRoutesS <Route exact path={RoutePath.voucherSuccess} component={VoucherVerificationSuccess} /> <Route exact path={RoutePath.timeAdded} component={TimeAdded} /> <Route exact path={RoutePath.setupFinished} component={SetupFinished} /> - <Route exact path={RoutePath.settings} component={SettingsPage} /> + <Route exact path={RoutePath.settings} component={Settings} /> <Route exact path={RoutePath.selectLanguage} component={SelectLanguagePage} /> <Route exact path={RoutePath.accountSettings} component={AccountPage} /> <Route exact path={RoutePath.interfaceSettings} component={InterfaceSettings} /> diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx index 2deb1ee0e7..812deef142 100644 --- a/gui/src/renderer/components/Settings.tsx +++ b/gui/src/renderer/components/Settings.tsx @@ -1,10 +1,12 @@ -import * as React from 'react'; +import { useCallback, useEffect } from 'react'; import { colors, links } from '../../config.json'; import { formatRemainingTime, hasExpired } from '../../shared/account-expiry'; import { messages } from '../../shared/gettext'; -import History from '../lib/history'; -import { LoginState } from '../redux/account/reducers'; +import { useAppContext } from '../context'; +import { useHistory } from '../lib/history'; +import { RoutePath } from '../lib/routes'; +import { useSelector } from '../redux/store'; import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup'; import * as Cell from './cell'; import { BackAction } from './KeyboardNavigation'; @@ -13,7 +15,6 @@ import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } fro import SettingsHeader, { HeaderTitle } from './SettingsHeader'; import { StyledCellIcon, - StyledCellSpacer, StyledContainer, StyledContent, StyledNavigationScrollbars, @@ -22,238 +23,226 @@ import { StyledSettingsContent, } from './SettingsStyles'; -export interface IProps { - preferredLocaleDisplayName: string; - loginState: LoginState; - connectedToDaemon: boolean; - accountExpiry?: string; - appVersion: string; - consistentVersion: boolean; - upToDateVersion: boolean; - suggestedIsBeta: boolean; - isOffline: boolean; - onQuit: () => void; - onClose: () => void; - onViewSelectLanguage: () => void; - onViewAccount: () => void; - onViewSupport: () => void; - onViewPreferences: () => void; - onViewAdvancedSettings: () => void; - onExternalLink: (url: string) => void; - updateAccountData: () => void; - history: History; -} +export default function Support() { + const history = useHistory(); + const { updateAccountData } = useAppContext(); -export default class Settings extends React.Component<IProps> { - public componentDidMount() { - if (this.props.history.action === 'PUSH') { - this.props.updateAccountData(); + useEffect(() => { + if (history.action === 'PUSH') { + updateAccountData(); } - } + }, []); + + const loginState = useSelector((state) => state.account.status); + const connectedToDaemon = useSelector((state) => state.userInterface.connectedToDaemon); + + const showLargeTitle = loginState.type !== 'ok'; + const showSubSettings = loginState.type === 'ok' && connectedToDaemon; - public render() { - const showLargeTitle = this.props.loginState.type !== 'ok'; + return ( + <BackAction icon="close" action={history.dismiss}> + <Layout> + <StyledContainer> + <NavigationContainer> + <NavigationBar alwaysDisplayBarTitle={!showLargeTitle}> + <NavigationItems> + <TitleBarItem> + { + // TRANSLATORS: Title label in navigation bar + messages.pgettext('navigation-bar', 'Settings') + } + </TitleBarItem> + </NavigationItems> + </NavigationBar> - return ( - <BackAction icon="close" action={this.props.onClose}> - <Layout> - <StyledContainer> - <NavigationContainer> - <NavigationBar alwaysDisplayBarTitle={!showLargeTitle}> - <NavigationItems> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('navigation-bar', 'Settings') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <StyledNavigationScrollbars fillContainer> + <StyledContent> + {showLargeTitle && ( + <SettingsHeader> + <HeaderTitle>{messages.pgettext('navigation-bar', 'Settings')}</HeaderTitle> + </SettingsHeader> + )} - <StyledNavigationScrollbars fillContainer> - <StyledContent> - {showLargeTitle && ( - <SettingsHeader> - <HeaderTitle>{messages.pgettext('navigation-bar', 'Settings')}</HeaderTitle> - </SettingsHeader> + <StyledSettingsContent> + {showSubSettings && ( + <> + <Cell.Group> + <AccountButton /> + <InterfaceSettingsButton /> + <VpnSettingsButton /> + </Cell.Group> + + {(window.env.platform === 'linux' || window.env.platform === 'win32') && ( + <Cell.Group> + <SplitTunnelingButton /> + </Cell.Group> + )} + </> )} - <StyledSettingsContent> - {this.renderTopButtons()} - {this.renderMiddleButtons()} - {this.renderBottomButtons()} - </StyledSettingsContent> - </StyledContent> + <Cell.Group> + <SupportButton /> + <AppVersionButton /> + </Cell.Group> + </StyledSettingsContent> + </StyledContent> - {this.renderQuitButton()} - </StyledNavigationScrollbars> - </NavigationContainer> - </StyledContainer> - </Layout> - </BackAction> - ); - } + <QuitButton /> + </StyledNavigationScrollbars> + </NavigationContainer> + </StyledContainer> + </Layout> + </BackAction> + ); +} - private openDownloadLink = () => - this.props.onExternalLink(this.props.suggestedIsBeta ? links.betaDownload : links.download); - private openFaqLink = () => this.props.onExternalLink(links.faq); +function AccountButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.accountSettings), [history]); - private renderQuitButton() { - return ( - <StyledQuitButton onClick={this.props.onQuit}> - {messages.pgettext('settings-view', 'Quit app')} - </StyledQuitButton> - ); - } + const accountExpiry = useSelector((state) => state.account.expiry); + const isOutOfTime = accountExpiry ? hasExpired(accountExpiry) : false; + const formattedExpiry = accountExpiry ? formatRemainingTime(accountExpiry).toUpperCase() : ''; + const outOfTimeMessage = messages.pgettext('settings-view', 'OUT OF TIME'); - private renderTopButtons() { - const isLoggedIn = this.props.loginState.type === 'ok'; - if (!isLoggedIn || !this.props.connectedToDaemon) { - return null; - } + return ( + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label> + { + // TRANSLATORS: Navigation button to the 'Account' view + messages.pgettext('settings-view', 'Account') + } + </Cell.Label> + <StyledOutOfTimeSubText isOutOfTime={isOutOfTime}> + {isOutOfTime ? outOfTimeMessage : formattedExpiry} + </StyledOutOfTimeSubText> + </Cell.CellNavigationButton> + ); +} - const isOutOfTime = this.props.accountExpiry ? hasExpired(this.props.accountExpiry) : false; - const formattedExpiry = this.props.accountExpiry - ? formatRemainingTime(this.props.accountExpiry).toUpperCase() - : ''; +function InterfaceSettingsButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.interfaceSettings), [history]); - const outOfTimeMessage = messages.pgettext('settings-view', 'OUT OF TIME'); + return ( + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label> + { + // TRANSLATORS: Navigation button to the 'Interface settings' view + messages.pgettext('settings-view', 'Interface settings') + } + </Cell.Label> + </Cell.CellNavigationButton> + ); +} - return ( - <> - <Cell.CellButton onClick={this.props.onViewAccount}> - <Cell.Label> - { - // TRANSLATORS: Navigation button to the 'Account' view - messages.pgettext('settings-view', 'Account') - } - </Cell.Label> - <StyledOutOfTimeSubText isOutOfTime={isOutOfTime}> - {isOutOfTime ? outOfTimeMessage : formattedExpiry} - </StyledOutOfTimeSubText> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> +function VpnSettingsButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.vpnSettings), [history]); - <Cell.CellButton onClick={this.props.onViewPreferences}> - <Cell.Label> - { - // TRANSLATORS: Navigation button to the 'Preferences' view - messages.pgettext('settings-view', 'Preferences') - } - </Cell.Label> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> + return ( + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label> + { + // TRANSLATORS: Navigation button to the 'VPN settings' view + messages.pgettext('settings-view', 'VPN settings') + } + </Cell.Label> + </Cell.CellNavigationButton> + ); +} - <Cell.CellButton onClick={this.props.onViewAdvancedSettings}> - <Cell.Label> - { - // TRANSLATORS: Navigation button to the 'Advanced' settings view - messages.pgettext('settings-view', 'Advanced') - } - </Cell.Label> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - <StyledCellSpacer /> - </> - ); - } +function SplitTunnelingButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.splitTunneling), [history]); + + return ( + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label> + { + // TRANSLATORS: Navigation button to the 'Split tunneling' view + messages.pgettext('settings-view', 'Split tunneling') + } + </Cell.Label> + </Cell.CellNavigationButton> + ); +} - private renderMiddleButtons() { - let icon; - let footer; - if (!this.props.consistentVersion || !this.props.upToDateVersion) { - const inconsistentVersionMessage = messages.pgettext( - 'settings-view', - 'App is out of sync. Please quit and restart.', - ); +function AppVersionButton() { + const appVersion = useSelector((state) => state.version.current); + const consistentVersion = useSelector((state) => state.version.consistent); + const upToDateVersion = useSelector((state) => (state.version.suggestedUpgrade ? false : true)); + const suggestedIsBeta = useSelector((state) => state.version.suggestedIsBeta ?? false); + const isOffline = useSelector((state) => state.connection.isBlocked); - const updateAvailableMessage = messages.pgettext( - 'settings-view', - 'Update available. Install the latest app version to stay up to date.', - ); + const { openUrl } = useAppContext(); + const openDownloadLink = useCallback( + () => openUrl(suggestedIsBeta ? links.betaDownload : links.download), + [openUrl, suggestedIsBeta], + ); - const message = !this.props.consistentVersion - ? inconsistentVersionMessage - : updateAvailableMessage; + let icon; + let footer; + if (!consistentVersion || !upToDateVersion) { + const inconsistentVersionMessage = messages.pgettext( + 'settings-view', + 'App is out of sync. Please quit and restart.', + ); - icon = <StyledCellIcon source="icon-alert" width={18} tintColor={colors.red} />; - footer = ( - <Cell.Footer> - <Cell.FooterText>{message}</Cell.FooterText> - </Cell.Footer> - ); - } else { - footer = <StyledCellSpacer />; - } + const updateAvailableMessage = messages.pgettext( + 'settings-view', + 'Update available. Install the latest app version to stay up to date.', + ); - return ( - <AriaDescriptionGroup> - <AriaDescribed> - <Cell.CellButton disabled={this.props.isOffline} onClick={this.openDownloadLink}> - {icon} - <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label> - <Cell.SubText>{this.props.appVersion}</Cell.SubText> - <AriaDescription> - <Cell.Icon - height={16} - width={16} - source="icon-extLink" - aria-label={messages.pgettext('accessibility', 'Opens externally')} - /> - </AriaDescription> - </Cell.CellButton> - </AriaDescribed> - {footer} - </AriaDescriptionGroup> + const message = !consistentVersion ? inconsistentVersionMessage : updateAvailableMessage; + + icon = <StyledCellIcon source="icon-alert" width={18} tintColor={colors.red} />; + footer = ( + <Cell.Footer> + <Cell.FooterText>{message}</Cell.FooterText> + </Cell.Footer> ); } - private renderBottomButtons() { - return ( - <> - <Cell.CellButton onClick={this.props.onViewSupport}> - <Cell.Label> - { - // TRANSLATORS: Navigation button to the 'Report a problem' help view - messages.pgettext('settings-view', 'Report a problem') - } - </Cell.Label> - <Cell.Icon height={12} width={7} source="icon-chevron" /> + return ( + <AriaDescriptionGroup> + <AriaDescribed> + <Cell.CellButton disabled={isOffline} onClick={openDownloadLink}> + {icon} + <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label> + <Cell.SubText>{appVersion}</Cell.SubText> + <AriaDescription> + <Cell.Icon + height={16} + width={16} + source="icon-extLink" + aria-label={messages.pgettext('accessibility', 'Opens externally')} + /> + </AriaDescription> </Cell.CellButton> + </AriaDescribed> + {footer} + </AriaDescriptionGroup> + ); +} - <AriaDescriptionGroup> - <AriaDescribed> - <Cell.CellButton disabled={this.props.isOffline} onClick={this.openFaqLink}> - <Cell.Label> - { - // TRANSLATORS: Link to the webpage - messages.pgettext('settings-view', 'FAQs & Guides') - } - </Cell.Label> - <AriaDescription> - <Cell.Icon - height={16} - width={16} - source="icon-extLink" - aria-label={messages.pgettext('accessibility', 'Opens externally')} - /> - </AriaDescription> - </Cell.CellButton> - </AriaDescribed> - </AriaDescriptionGroup> +function SupportButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.support), [history]); - <Cell.CellButton onClick={this.props.onViewSelectLanguage}> - <StyledCellIcon width={24} height={24} source="icon-language" /> - <Cell.Label> - { - // TRANSLATORS: Navigation button to the 'Language' settings view - messages.pgettext('settings-view', 'Language') - } - </Cell.Label> - <Cell.SubText>{this.props.preferredLocaleDisplayName}</Cell.SubText> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - </> - ); - } + return ( + <Cell.CellNavigationButton onClick={navigate}> + <Cell.Label>{messages.pgettext('settings-view', 'Support')}</Cell.Label> + </Cell.CellNavigationButton> + ); +} + +function QuitButton() { + const { quit } = useAppContext(); + + return ( + <StyledQuitButton onClick={quit}> + {messages.pgettext('settings-view', 'Quit app')} + </StyledQuitButton> + ); } diff --git a/gui/src/renderer/components/SettingsStyles.tsx b/gui/src/renderer/components/SettingsStyles.tsx index 88a4c8258a..687b448424 100644 --- a/gui/src/renderer/components/SettingsStyles.tsx +++ b/gui/src/renderer/components/SettingsStyles.tsx @@ -34,12 +34,6 @@ export const StyledSettingsContent = styled.div({ flexDirection: 'column', }); -export const StyledCellSpacer = styled.div({ - height: '20px', - minHeight: '20px', - flex: 0, -}); - export const StyledQuitButton = styled(AppButton.RedButton)({ margin: '20px 22px 22px', }); diff --git a/gui/src/renderer/containers/SettingsPage.tsx b/gui/src/renderer/containers/SettingsPage.tsx deleted file mode 100644 index e771541673..0000000000 --- a/gui/src/renderer/containers/SettingsPage.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { connect } from 'react-redux'; - -import Settings from '../components/Settings'; -import withAppContext, { IAppContext } from '../context'; -import { IHistoryProps, withHistory } from '../lib/history'; -import { RoutePath } from '../lib/routes'; -import { IReduxState, ReduxDispatch } from '../redux/store'; - -const mapStateToProps = (state: IReduxState, props: IAppContext) => ({ - preferredLocaleDisplayName: props.app.getPreferredLocaleDisplayName( - state.settings.guiSettings.preferredLocale, - ), - loginState: state.account.status, - connectedToDaemon: state.userInterface.connectedToDaemon, - accountExpiry: state.account.expiry, - appVersion: state.version.current, - consistentVersion: state.version.consistent, - upToDateVersion: state.version.suggestedUpgrade ? false : true, - suggestedIsBeta: state.version.suggestedIsBeta ?? false, - isOffline: state.connection.isBlocked, -}); -const mapDispatchToProps = (_dispatch: ReduxDispatch, props: IHistoryProps & IAppContext) => { - return { - onQuit: () => props.app.quit(), - onClose: () => props.history.dismiss(), - onViewSelectLanguage: () => props.history.push(RoutePath.selectLanguage), - onViewAccount: () => props.history.push(RoutePath.accountSettings), - onViewSupport: () => props.history.push(RoutePath.problemReport), - onViewPreferences: () => props.history.push(RoutePath.preferences), - onViewAdvancedSettings: () => props.history.push(RoutePath.advancedSettings), - onExternalLink: (url: string) => props.app.openUrl(url), - updateAccountData: () => props.app.updateAccountData(), - }; -}; - -export default withAppContext(withHistory(connect(mapStateToProps, mapDispatchToProps)(Settings))); diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts index a997a97cd3..9c8eb4c493 100644 --- a/gui/src/renderer/lib/routes.ts +++ b/gui/src/renderer/lib/routes.ts @@ -19,7 +19,7 @@ export enum RoutePath { vpnSettings = '/settings/vpn', wireguardSettings = '/settings/advanced/wireguard', openVpnSettings = '/settings/advanced/openvpn', - splitTunneling = '/settings/advanced/split-tunneling', + splitTunneling = '/settings/split-tunneling', support = '/settings/support', problemReport = '/settings/support/problem-report', selectLocation = '/select-location', |
