diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-08-19 10:44:29 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-08-19 10:44:29 +0200 |
| commit | eccdb59ddf88eb42736c880331d3034766f3e21b (patch) | |
| tree | 9ee68713f40c68a52e577407e6187de763ebe1fd /gui/src/renderer | |
| parent | dd1689df906ae8f4782f25970aba56bd3e3bf9d5 (diff) | |
| parent | 00a9d8c9942369f0869294a0473f763bc93e3b06 (diff) | |
| download | mullvadvpn-eccdb59ddf88eb42736c880331d3034766f3e21b.tar.xz mullvadvpn-eccdb59ddf88eb42736c880331d3034766f3e21b.zip | |
Merge branch 'convert-components-from-reactxp' into master
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/components/AdvancedSettings.tsx | 446 | ||||
| -rw-r--r-- | gui/src/renderer/components/AdvancedSettingsStyles.tsx | 60 | ||||
| -rw-r--r-- | gui/src/renderer/components/ConnectionPanel.tsx | 91 | ||||
| -rw-r--r-- | gui/src/renderer/components/ConnectionPanelDisclosure.tsx | 96 | ||||
| -rw-r--r-- | gui/src/renderer/components/ErrorBoundary.tsx | 76 | ||||
| -rw-r--r-- | gui/src/renderer/components/Launch.tsx | 69 | ||||
| -rw-r--r-- | gui/src/renderer/components/Preferences.tsx | 262 | ||||
| -rw-r--r-- | gui/src/renderer/components/PreferencesStyles.tsx | 35 | ||||
| -rw-r--r-- | gui/src/renderer/components/SelectLanguage.tsx | 87 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 140 | ||||
| -rw-r--r-- | gui/src/renderer/components/SettingsStyles.tsx | 65 |
11 files changed, 666 insertions, 761 deletions
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx index 7d6724b001..40131d58ff 100644 --- a/gui/src/renderer/components/AdvancedSettings.tsx +++ b/gui/src/renderer/components/AdvancedSettings.tsx @@ -1,17 +1,22 @@ import * as React from 'react'; -import { Component, Text, View } from 'reactxp'; import { sprintf } from 'sprintf-js'; import { BridgeState, RelayProtocol, TunnelProtocol } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import { WgKeyState } from '../redux/settings/reducers'; -import styles, { - InputFrame, +import { + StyledBottomCellGroup, + StyledContainer, + StyledInputFrame, StyledNavigationScrollbars, - TunnelProtocolSelector, + StyledNoWireguardKeyError, + StyledNoWireguardKeyErrorContainer, + StyledSelectorContainer, + StyledTunnelProtocolSelector, + StyledTunnelProtocolContainer, } from './AdvancedSettingsStyles'; import * as AppButton from './AppButton'; import * as Cell from './Cell'; -import { Container, Layout } from './Layout'; +import { Layout } from './Layout'; import { ModalAlert, ModalAlertType, ModalContainer, ModalMessage } from './Modal'; import { BackBarItem, @@ -70,7 +75,7 @@ interface IState { showConfirmBlockWhenDisconnectedAlert: boolean; } -export default class AdvancedSettings extends Component<IProps, IState> { +export default class AdvancedSettings extends React.Component<IProps, IState> { private portItems: { [key in RelayProtocol]: Array<ISelectorItem<OptionalPort>> }; private protocolItems: Array<ISelectorItem<OptionalRelayProtocol>>; private bridgeStateItems: Array<ISelectorItem<BridgeState>>; @@ -138,248 +143,229 @@ export default class AdvancedSettings extends Component<IProps, IState> { return ( <ModalContainer> <Layout> - <Container> - <View style={styles.advanced_settings}> - <NavigationContainer> - <NavigationBar> - <NavigationItems> - <BackBarItem action={this.props.onClose}> - { - // TRANSLATORS: Back button in navigation bar - messages.pgettext('navigation-bar', 'Settings') - } - </BackBarItem> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('advanced-settings-nav', 'Advanced') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <StyledContainer> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <BackBarItem action={this.props.onClose}> + { + // TRANSLATORS: Back button in navigation bar + messages.pgettext('navigation-bar', 'Settings') + } + </BackBarItem> + <TitleBarItem> + { + // TRANSLATORS: Title label in navigation bar + messages.pgettext('advanced-settings-nav', 'Advanced') + } + </TitleBarItem> + </NavigationItems> + </NavigationBar> - <View style={styles.advanced_settings__container}> - <StyledNavigationScrollbars> - <SettingsHeader> - <HeaderTitle> - {messages.pgettext('advanced-settings-view', 'Advanced')} - </HeaderTitle> - </SettingsHeader> + <StyledNavigationScrollbars> + <SettingsHeader> + <HeaderTitle> + {messages.pgettext('advanced-settings-view', 'Advanced')} + </HeaderTitle> + </SettingsHeader> - <Cell.Container> - <Cell.Label> - {messages.pgettext('advanced-settings-view', 'Enable IPv6')} - </Cell.Label> - <Cell.Switch - isOn={this.props.enableIpv6} - onChange={this.props.setEnableIpv6} - /> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {messages.pgettext( - 'advanced-settings-view', - 'Enable IPv6 communication through the tunnel.', - )} - </Cell.FooterText> - </Cell.Footer> + <Cell.Container> + <Cell.Label> + {messages.pgettext('advanced-settings-view', 'Enable IPv6')} + </Cell.Label> + <Cell.Switch isOn={this.props.enableIpv6} onChange={this.props.setEnableIpv6} /> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {messages.pgettext( + 'advanced-settings-view', + 'Enable IPv6 communication through the tunnel.', + )} + </Cell.FooterText> + </Cell.Footer> - <Cell.Container> - <Cell.Label> - {messages.pgettext('advanced-settings-view', 'Always require VPN')} - </Cell.Label> - <Cell.Switch - isOn={this.props.blockWhenDisconnected} - onChange={this.setBlockWhenDisconnected} - /> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> + <Cell.Container> + <Cell.Label> + {messages.pgettext('advanced-settings-view', 'Always require VPN')} + </Cell.Label> + <Cell.Switch + isOn={this.props.blockWhenDisconnected} + onChange={this.setBlockWhenDisconnected} + /> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {messages.pgettext( + 'advanced-settings-view', + 'If you disconnect or quit the app, this setting will block your internet.', + )} + </Cell.FooterText> + </Cell.Footer> + + <StyledTunnelProtocolContainer> + <StyledTunnelProtocolSelector + title={messages.pgettext('advanced-settings-view', 'Tunnel protocol')} + values={this.tunnelProtocolItems(hasWireguardKey)} + value={this.props.tunnelProtocol} + onSelect={this.onSelectTunnelProtocol} + /> + {!hasWireguardKey && ( + <StyledNoWireguardKeyErrorContainer> + <StyledNoWireguardKeyError> {messages.pgettext( 'advanced-settings-view', - 'If you disconnect or quit the app, this setting will block your internet.', + 'To enable WireGuard, generate a key under the "WireGuard key" setting below.', )} - </Cell.FooterText> - </Cell.Footer> - - <View - style={[ - styles.advanced_settings__content, - styles.advanced_settings__cell_bottom_margin, - ]}> - <TunnelProtocolSelector - title={messages.pgettext('advanced-settings-view', 'Tunnel protocol')} - values={this.tunnelProtocolItems(hasWireguardKey)} - value={this.props.tunnelProtocol} - onSelect={this.onSelectTunnelProtocol} - /> - {!hasWireguardKey && ( - <Text style={styles.advanced_settings__wg_no_key}> - {messages.pgettext( - 'advanced-settings-view', - 'To enable WireGuard, generate a key under the "WireGuard key" setting below.', - )} - </Text> - )} - </View> - - {this.props.tunnelProtocol !== 'wireguard' ? ( - <View style={styles.advanced_settings__content}> - <Selector - title={messages.pgettext( - 'advanced-settings-view', - 'OpenVPN transport protocol', - )} - values={this.protocolItems} - value={this.props.openvpn.protocol} - onSelect={this.onSelectOpenvpnProtocol} - /> - - {this.props.openvpn.protocol ? ( - <Selector - title={sprintf( - // TRANSLATORS: The title for the port selector section. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(portType)s - a selected protocol (either TCP or UDP) - messages.pgettext( - 'advanced-settings-view', - 'OpenVPN %(portType)s port', - ), - { - portType: this.props.openvpn.protocol.toUpperCase(), - }, - )} - values={this.portItems[this.props.openvpn.protocol]} - value={this.props.openvpn.port} - onSelect={this.onSelectOpenVpnPort} - /> - ) : undefined} - </View> - ) : undefined} - - {this.props.tunnelProtocol === 'wireguard' ? ( - <View style={styles.advanced_settings__content}> - <Selector - // TRANSLATORS: The title for the shadowsocks bridge selector section. - title={messages.pgettext('advanced-settings-view', 'WireGuard port')} - values={this.wireguardPortItems} - value={this.props.wireguard.port} - onSelect={this.onSelectWireguardPort} - /> - </View> - ) : undefined} + </StyledNoWireguardKeyError> + </StyledNoWireguardKeyErrorContainer> + )} + </StyledTunnelProtocolContainer> + {this.props.tunnelProtocol !== 'wireguard' ? ( + <StyledSelectorContainer> <Selector - title={ - // TRANSLATORS: The title for the shadowsocks bridge selector section. - messages.pgettext('advanced-settings-view', 'Bridge mode') - } - values={this.bridgeStateItems} - value={this.props.bridgeState} - onSelect={this.onSelectBridgeState} + title={messages.pgettext( + 'advanced-settings-view', + 'OpenVPN transport protocol', + )} + values={this.protocolItems} + value={this.props.openvpn.protocol} + onSelect={this.onSelectOpenvpnProtocol} /> - <Cell.Container> - <Cell.Label> - {messages.pgettext('advanced-settings-view', 'OpenVPN Mssfix')} - </Cell.Label> - <InputFrame> - <Cell.AutoSizingTextInput - value={this.props.mssfix ? this.props.mssfix.toString() : ''} - inputMode={'numeric'} - maxLength={4} - placeholder={messages.pgettext('advanced-settings-view', 'Default')} - onSubmitValue={this.onMssfixSubmit} - validateValue={AdvancedSettings.mssfixIsValid} - submitOnBlur={true} - modifyValue={AdvancedSettings.removeNonNumericCharacters} - /> - </InputFrame> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {sprintf( - // TRANSLATORS: The hint displayed below the Mssfix input field. + {this.props.openvpn.protocol ? ( + <Selector + title={sprintf( + // TRANSLATORS: The title for the port selector section. // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(max)d - the maximum possible mssfix value - // TRANSLATORS: %(min)d - the minimum possible mssfix value - messages.pgettext( - 'advanced-settings-view', - 'Set OpenVPN MSS value. Valid range: %(min)d - %(max)d.', - ), + // TRANSLATORS: %(portType)s - a selected protocol (either TCP or UDP) + messages.pgettext('advanced-settings-view', 'OpenVPN %(portType)s port'), { - min: MIN_MSSFIX_VALUE, - max: MAX_MSSFIX_VALUE, + portType: this.props.openvpn.protocol.toUpperCase(), }, )} - </Cell.FooterText> - </Cell.Footer> + values={this.portItems[this.props.openvpn.protocol]} + value={this.props.openvpn.port} + onSelect={this.onSelectOpenVpnPort} + /> + ) : undefined} + </StyledSelectorContainer> + ) : undefined} - <Cell.Container> - <Cell.Label> - {messages.pgettext('advanced-settings-view', 'WireGuard MTU')} - </Cell.Label> - <InputFrame> - <Cell.AutoSizingTextInput - value={this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''} - inputMode={'numeric'} - maxLength={4} - placeholder={messages.pgettext('advanced-settings-view', 'Default')} - onSubmitValue={this.onWireguardMtuSubmit} - validateValue={AdvancedSettings.wireguarMtuIsValid} - submitOnBlur={true} - modifyValue={AdvancedSettings.removeNonNumericCharacters} - /> - </InputFrame> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {sprintf( - // TRANSLATORS: The hint displayed below the WireGuard MTU input field. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value - // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value - messages.pgettext( - 'advanced-settings-view', - 'Set WireGuard MTU value. Valid range: %(min)d - %(max)d.', - ), - { - min: MIN_WIREGUARD_MTU_VALUE, - max: MAX_WIREGUARD_MTU_VALUE, - }, - )} - </Cell.FooterText> - </Cell.Footer> + {this.props.tunnelProtocol === 'wireguard' ? ( + <StyledSelectorContainer> + <Selector + // TRANSLATORS: The title for the shadowsocks bridge selector section. + title={messages.pgettext('advanced-settings-view', 'WireGuard port')} + values={this.wireguardPortItems} + value={this.props.wireguard.port} + onSelect={this.onSelectWireguardPort} + /> + </StyledSelectorContainer> + ) : undefined} - <View - style={ - process.platform !== 'linux' - ? styles.advanced_settings__last_cell_bottom_margin - : undefined - }> - <Cell.CellButton onClick={this.props.onViewWireguardKeys}> - <Cell.Label> - {messages.pgettext('advanced-settings-view', 'WireGuard key')} - </Cell.Label> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - </View> + <Selector + title={ + // TRANSLATORS: The title for the shadowsocks bridge selector section. + messages.pgettext('advanced-settings-view', 'Bridge mode') + } + values={this.bridgeStateItems} + value={this.props.bridgeState} + onSelect={this.onSelectBridgeState} + /> - {process.platform === 'linux' && ( - <View style={styles.advanced_settings__last_cell_bottom_margin}> - <Cell.CellButton onClick={this.props.onViewLinuxSplitTunneling}> - <Cell.Label> - {messages.pgettext('advanced-settings-view', 'Split tunneling')} - </Cell.Label> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - </View> + <Cell.Container> + <Cell.Label> + {messages.pgettext('advanced-settings-view', 'OpenVPN Mssfix')} + </Cell.Label> + <StyledInputFrame> + <Cell.AutoSizingTextInput + value={this.props.mssfix ? this.props.mssfix.toString() : ''} + inputMode={'numeric'} + maxLength={4} + placeholder={messages.pgettext('advanced-settings-view', 'Default')} + onSubmitValue={this.onMssfixSubmit} + validateValue={AdvancedSettings.mssfixIsValid} + submitOnBlur={true} + modifyValue={AdvancedSettings.removeNonNumericCharacters} + /> + </StyledInputFrame> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {sprintf( + // TRANSLATORS: The hint displayed below the Mssfix input field. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(max)d - the maximum possible mssfix value + // TRANSLATORS: %(min)d - the minimum possible mssfix value + messages.pgettext( + 'advanced-settings-view', + 'Set OpenVPN MSS value. Valid range: %(min)d - %(max)d.', + ), + { + min: MIN_MSSFIX_VALUE, + max: MAX_MSSFIX_VALUE, + }, )} - </StyledNavigationScrollbars> - </View> - </NavigationContainer> - </View> - </Container> + </Cell.FooterText> + </Cell.Footer> + + <Cell.Container> + <Cell.Label> + {messages.pgettext('advanced-settings-view', 'WireGuard MTU')} + </Cell.Label> + <StyledInputFrame> + <Cell.AutoSizingTextInput + value={this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''} + inputMode={'numeric'} + maxLength={4} + placeholder={messages.pgettext('advanced-settings-view', 'Default')} + onSubmitValue={this.onWireguardMtuSubmit} + validateValue={AdvancedSettings.wireguarMtuIsValid} + submitOnBlur={true} + modifyValue={AdvancedSettings.removeNonNumericCharacters} + /> + </StyledInputFrame> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {sprintf( + // TRANSLATORS: The hint displayed below the WireGuard MTU input field. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value + // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value + messages.pgettext( + 'advanced-settings-view', + 'Set WireGuard MTU value. Valid range: %(min)d - %(max)d.', + ), + { + min: MIN_WIREGUARD_MTU_VALUE, + max: MAX_WIREGUARD_MTU_VALUE, + }, + )} + </Cell.FooterText> + </Cell.Footer> + + <StyledBottomCellGroup> + <Cell.CellButton onClick={this.props.onViewWireguardKeys}> + <Cell.Label> + {messages.pgettext('advanced-settings-view', 'WireGuard key')} + </Cell.Label> + <Cell.Icon height={12} width={7} source="icon-chevron" /> + </Cell.CellButton> + + {process.platform === 'linux' && ( + <Cell.CellButton onClick={this.props.onViewLinuxSplitTunneling}> + <Cell.Label> + {messages.pgettext('advanced-settings-view', 'Split tunneling')} + </Cell.Label> + <Cell.Icon height={12} width={7} source="icon-chevron" /> + </Cell.CellButton> + )} + </StyledBottomCellGroup> + </StyledNavigationScrollbars> + </NavigationContainer> + </StyledContainer> </Layout> {this.state.showConfirmBlockWhenDisconnectedAlert && diff --git a/gui/src/renderer/components/AdvancedSettingsStyles.tsx b/gui/src/renderer/components/AdvancedSettingsStyles.tsx index e39a7b4ed7..f9bd6db82b 100644 --- a/gui/src/renderer/components/AdvancedSettingsStyles.tsx +++ b/gui/src/renderer/components/AdvancedSettingsStyles.tsx @@ -1,46 +1,46 @@ -import { Styles } from 'reactxp'; import styled from 'styled-components'; import { colors } from '../../config.json'; import * as Cell from './Cell'; +import { Container } from './Layout'; import { NavigationScrollbars } from './NavigationBar'; import Selector from './Selector'; -export const InputFrame = styled(Cell.InputFrame)({ +export const StyledContainer = styled(Container)({ + backgroundColor: colors.darkBlue, +}); + +export const StyledInputFrame = styled(Cell.InputFrame)({ flex: 0, }); -export const TunnelProtocolSelector = (styled(Selector)({ +export const StyledSelectorContainer = styled.div({ + flex: 0, +}); + +export const StyledTunnelProtocolSelector = (styled(Selector)({ marginBottom: 0, }) as unknown) as new <T>() => Selector<T>; +export const StyledTunnelProtocolContainer = styled(StyledSelectorContainer)({ + marginBottom: '20px', +}); + export const StyledNavigationScrollbars = styled(NavigationScrollbars)({ flex: 1, }); -export default { - advanced_settings: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - advanced_settings__container: Styles.createViewStyle({ - flex: 1, - }), - advanced_settings__content: Styles.createViewStyle({ - flex: 0, - }), - advanced_settings__cell_bottom_margin: Styles.createViewStyle({ - marginBottom: 20, - }), - advanced_settings__last_cell_bottom_margin: Styles.createViewStyle({ - marginBottom: 22, - }), - advanced_settings__wg_no_key: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '800', - lineHeight: 20, - color: colors.red, - marginTop: 12, - paddingHorizontal: 22, - }), -}; +export const StyledBottomCellGroup = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + marginBottom: '22px', +}); + +export const StyledNoWireguardKeyErrorContainer = styled(Cell.Footer)({ + paddingBottom: 0, +}); + +export const StyledNoWireguardKeyError = styled(Cell.FooterText)({ + fontWeight: 800, + color: colors.red, +}); diff --git a/gui/src/renderer/components/ConnectionPanel.tsx b/gui/src/renderer/components/ConnectionPanel.tsx index 156a46ff89..248d466706 100644 --- a/gui/src/renderer/components/ConnectionPanel.tsx +++ b/gui/src/renderer/components/ConnectionPanel.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; -import { Component, Styles, Text, Types, View } from 'reactxp'; import { sprintf } from 'sprintf-js'; +import styled from 'styled-components'; +import { colors } from '../../config.json'; import { ProxyType, proxyTypeToString, @@ -38,80 +39,76 @@ interface IProps { bridgeInfo?: IBridgeData; outAddress?: IOutAddress; onToggle: () => void; - style?: Types.ViewStyleRuleSet | Types.ViewStyleRuleSet[]; + className?: string; } -const styles = { - row: Styles.createViewStyle({ - flexDirection: 'row', - marginTop: 3, - }), - caption: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - lineHeight: 15, - fontWeight: '600', - color: 'rgb(255, 255, 255)', - flex: 0, - marginRight: 8, - }), - value: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - lineHeight: 15, - fontWeight: '600', - color: 'rgb(255, 255, 255)', - }), - header: Styles.createViewStyle({ - flexDirection: 'row', - alignItems: 'center', - }), -}; +const Row = styled.div({ + display: 'flex', + marginTop: '3px', +}); -export default class ConnectionPanel extends Component<IProps> { +const Text = styled.span({ + fontFamily: 'Open Sans', + fontSize: '13px', + lineHeight: '15px', + fontWeight: 600, + color: colors.white, +}); + +const Caption = styled(Text)({ + flex: 0, + marginRight: '8px', +}); + +const Header = styled.div({ + display: 'flex', + alignItems: 'center', +}); + +export default class ConnectionPanel extends React.Component<IProps> { public render() { const { inAddress, outAddress, bridgeInfo } = this.props; const entryPoint = bridgeInfo && inAddress ? bridgeInfo : inAddress; return ( - <View style={this.props.style}> + <div className={this.props.className}> {this.props.hostname && ( - <View style={styles.header}> + <Header> <ConnectionPanelDisclosure pointsUp={this.props.isOpen} onToggle={this.props.onToggle}> {this.hostnameLine()} </ConnectionPanelDisclosure> - </View> + </Header> )} {this.props.isOpen && this.props.hostname && ( <React.Fragment> {this.props.inAddress && ( - <View style={styles.row}> - <Text style={styles.value}>{this.transportLine()}</Text> - </View> + <Row> + <Text>{this.transportLine()}</Text> + </Row> )} {entryPoint && ( - <View style={styles.row}> - <Text style={styles.caption}>{messages.pgettext('connection-info', 'In')}</Text> - <Text style={styles.value}> + <Row> + <Caption>{messages.pgettext('connection-info', 'In')}</Caption> + <Text> {`${entryPoint.ip}:${entryPoint.port} ${entryPoint.protocol.toUpperCase()}`} </Text> - </View> + </Row> )} {outAddress && (outAddress.ipv4 || outAddress.ipv6) && ( - <View style={styles.row}> - <Text style={styles.caption}>{messages.pgettext('connection-info', 'Out')}</Text> - <View> - {outAddress.ipv4 && <Text style={styles.value}>{outAddress.ipv4}</Text>} - {outAddress.ipv6 && <Text style={styles.value}>{outAddress.ipv6}</Text>} - </View> - </View> + <Row> + <Caption>{messages.pgettext('connection-info', 'Out')}</Caption> + <div> + {outAddress.ipv4 && <Text>{outAddress.ipv4}</Text>} + {outAddress.ipv6 && <Text>{outAddress.ipv6}</Text>} + </div> + </Row> )} </React.Fragment> )} - </View> + </div> ); } diff --git a/gui/src/renderer/components/ConnectionPanelDisclosure.tsx b/gui/src/renderer/components/ConnectionPanelDisclosure.tsx index 17c3dc9919..ad7c092f9c 100644 --- a/gui/src/renderer/components/ConnectionPanelDisclosure.tsx +++ b/gui/src/renderer/components/ConnectionPanelDisclosure.tsx @@ -1,73 +1,47 @@ import * as React from 'react'; -import { Component, Styles, Text, Types, View } from 'reactxp'; +import styled from 'styled-components'; +import { colors } from '../../config.json'; import ImageView from './ImageView'; -const styles = { - container: Styles.createViewStyle({ - flexDirection: 'row', - alignItems: 'center', - }), - caption: { - base: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 15, - fontWeight: '600', - lineHeight: 20, - color: 'rgb(255, 255, 255, 0.4)', - }), - hovered: Styles.createTextStyle({ - color: 'rgb(255, 255, 255)', - }), +const Container = styled.div({ + display: 'flex', + alignItems: 'center', +}); + +const Caption = styled.span((props: { open: boolean }) => ({ + fontFamily: 'Open Sans', + fontSize: '15px', + fontWeight: 600, + lineHeight: '20px', + color: props.open ? colors.white : colors.white40, + [Container + ':hover &']: { + color: colors.white, + }, +})); + +const Chevron = styled(ImageView)({ + [Container + ':hover &']: { + backgroundColor: colors.white, }, -}; +}); interface IProps { pointsUp: boolean; onToggle?: () => void; children: React.ReactText; - style?: Types.ViewStyleRuleSet | Types.ViewStyleRuleSet[]; -} - -interface IState { - isHovered: boolean; + className?: string; } -export default class ConnectionPanelDisclosure extends Component<IProps, IState> { - constructor(props: IProps) { - super(props); - - this.state = { - isHovered: false, - }; - } - - public render() { - const tintColor = this.state.isHovered ? 'rgb(255, 255, 255)' : 'rgb(255, 255, 255, 0.4)'; - const textHoverStyle = - this.props.pointsUp || this.state.isHovered ? styles.caption.hovered : undefined; - - return ( - <View - style={[styles.container, this.props.style]} - onMouseEnter={this.onMouseEnter} - onMouseLeave={this.onMouseLeave} - onPress={this.props.onToggle}> - <Text style={[styles.caption.base, textHoverStyle]}>{this.props.children}</Text> - <ImageView - source={this.props.pointsUp ? 'icon-chevron-up' : 'icon-chevron-down'} - width={24} - height={24} - tintColor={tintColor} - /> - </View> - ); - } - - private onMouseEnter = () => { - this.setState({ isHovered: true }); - }; - - private onMouseLeave = () => { - this.setState({ isHovered: false }); - }; +export default function ConnectionPanelDisclosure(props: IProps) { + return ( + <Container className={props.className} onClick={props.onToggle}> + <Caption open={props.pointsUp}>{props.children}</Caption> + <Chevron + source={props.pointsUp ? 'icon-chevron-up' : 'icon-chevron-down'} + width={24} + height={24} + tintColor={colors.white40} + /> + </Container> + ); } diff --git a/gui/src/renderer/components/ErrorBoundary.tsx b/gui/src/renderer/components/ErrorBoundary.tsx index 8be0e8b5b8..bd20abcbdc 100644 --- a/gui/src/renderer/components/ErrorBoundary.tsx +++ b/gui/src/renderer/components/ErrorBoundary.tsx @@ -1,6 +1,5 @@ import log from 'electron-log'; -import * as React from 'react'; -import { Component, Styles, Text, View } from 'reactxp'; +import React from 'react'; import styled from 'styled-components'; import { colors, links } from '../../config.json'; import { messages } from '../../shared/gettext'; @@ -16,40 +15,41 @@ interface IState { hasError: boolean; } -const styles = { - container: Styles.createViewStyle({ - flex: 1, - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: colors.blue, - }), - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 24, - fontWeight: '900', - lineHeight: 30, - color: colors.white60, - marginBottom: 4, - }), - subtitle: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 14, - lineHeight: 20, - color: colors.white40, - marginHorizontal: 22, - textAlign: 'center', - }), - email: Styles.createTextStyle({ - fontWeight: '900', - }), -}; +const StyledContainer = styled(Container)({ + flex: 1, + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: colors.blue, +}); + +const Title = styled.span({ + fontFamily: 'DINPro', + fontSize: '24px', + fontWeight: 900, + lineHeight: '30px', + color: colors.white60, + marginBottom: '4px', +}); + +const Subtitle = styled.span({ + fontFamily: 'Open Sans', + fontSize: '14px', + lineHeight: '20px', + color: colors.white40, + marginHorizontal: '22px', + textAlign: 'center', +}); + +const Email = styled.span({ + fontWeight: 900, +}); const Logo = styled(ImageView)({ marginBottom: '5px', }); -export default class ErrorBoundary extends Component<IProps, IState> { +export default class ErrorBoundary extends React.Component<IProps, IState> { public state = { hasError: false }; public componentDidCatch(error: Error, info: React.ErrorInfo) { @@ -71,18 +71,16 @@ export default class ErrorBoundary extends Component<IProps, IState> { messages .pgettext('error-boundary-view', 'Something went wrong. Please contact us at %(email)s') .split('%(email)s', 2); - reachBackMessage.splice(1, 0, <Text style={styles.email}>{links.supportEmail}</Text>); + reachBackMessage.splice(1, 0, <Email>{links.supportEmail}</Email>); return ( <PlatformWindowContainer> <Layout> - <Container> - <View style={styles.container}> - <Logo height={106} width={106} source="logo-icon" /> - <Text style={styles.title}>{messages.pgettext('generic', 'MULLVAD VPN')}</Text> - <Text style={styles.subtitle}>{reachBackMessage}</Text> - </View> - </Container> + <StyledContainer> + <Logo height={106} width={106} source="logo-icon" /> + <Title>{messages.pgettext('generic', 'MULLVAD VPN')}</Title> + <Subtitle>{reachBackMessage}</Subtitle> + </StyledContainer> </Layout> </PlatformWindowContainer> ); diff --git a/gui/src/renderer/components/Launch.tsx b/gui/src/renderer/components/Launch.tsx index 62c561c7b8..9cb7a23d44 100644 --- a/gui/src/renderer/components/Launch.tsx +++ b/gui/src/renderer/components/Launch.tsx @@ -1,5 +1,4 @@ -import * as React from 'react'; -import { Styles, Text, View } from 'reactxp'; +import React from 'react'; import styled from 'styled-components'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; @@ -7,31 +6,31 @@ import { HeaderBarSettingsButton } from './HeaderBar'; import ImageView from './ImageView'; import { Container, Header, Layout } from './Layout'; -const styles = { - container: Styles.createViewStyle({ - flex: 1, - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - marginTop: -150, - }), - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 24, - fontWeight: '900', - lineHeight: 30, - color: colors.white60, - marginBottom: 4, - }), - subtitle: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 14, - lineHeight: 20, - marginHorizontal: 22, - color: colors.white40, - textAlign: 'center', - }), -}; +const StyledContainer = styled(Container)({ + flex: 1, + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + marginTop: '-150px', +}); + +const Title = styled.span({ + fontFamily: 'DINPro', + fontSize: '24px', + fontWeight: 900, + lineHeight: '30px', + color: colors.white60, + marginBottom: '4px', +}); + +const Subtitle = styled.span({ + fontFamily: 'Open Sans', + fontSize: '14px', + lineHeight: '20px', + marginHorizontal: '22px', + color: colors.white40, + textAlign: 'center', +}); const Logo = styled(ImageView)({ marginBottom: '5px', @@ -43,15 +42,13 @@ export default function Launch() { <Header> <HeaderBarSettingsButton /> </Header> - <Container> - <View style={styles.container}> - <Logo height={106} width={106} source="logo-icon" /> - <Text style={styles.title}>{messages.pgettext('generic', 'MULLVAD VPN')}</Text> - <Text style={styles.subtitle}> - {messages.pgettext('launch-view', 'Connecting to Mullvad system service...')} - </Text> - </View> - </Container> + <StyledContainer> + <Logo height={106} width={106} source="logo-icon" /> + <Title>{messages.pgettext('generic', 'MULLVAD VPN')}</Title> + <Subtitle> + {messages.pgettext('launch-view', 'Connecting to Mullvad system service...')} + </Subtitle> + </StyledContainer> </Layout> ); } diff --git a/gui/src/renderer/components/Preferences.tsx b/gui/src/renderer/components/Preferences.tsx index a7d28e145d..0b56c53221 100644 --- a/gui/src/renderer/components/Preferences.tsx +++ b/gui/src/renderer/components/Preferences.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; -import { Component, View } from 'reactxp'; import { messages } from '../../shared/gettext'; import * as Cell from './Cell'; -import { Container, Layout } from './Layout'; +import { Layout } from './Layout'; import { BackBarItem, NavigationBar, @@ -11,7 +10,7 @@ import { NavigationScrollbars, TitleBarItem, } from './NavigationBar'; -import styles from './PreferencesStyles'; +import { StyledContainer, StyledContent, StyledSeparator } from './PreferencesStyles'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; export interface IProps { @@ -34,166 +33,151 @@ export interface IProps { onClose: () => void; } -export default class Preferences extends Component<IProps> { +export default class Preferences extends React.Component<IProps> { public render() { return ( <Layout> - <Container> - <View style={styles.preferences}> - <NavigationContainer> - <NavigationBar> - <NavigationItems> - <BackBarItem action={this.props.onClose}> - { - // TRANSLATORS: Back button in navigation bar - messages.pgettext('navigation-bar', 'Settings') - } - </BackBarItem> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('preferences-nav', 'Preferences') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <StyledContainer> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <BackBarItem action={this.props.onClose}> + { + // TRANSLATORS: Back button in navigation bar + messages.pgettext('navigation-bar', 'Settings') + } + </BackBarItem> + <TitleBarItem> + { + // TRANSLATORS: Title label in navigation bar + messages.pgettext('preferences-nav', 'Preferences') + } + </TitleBarItem> + </NavigationItems> + </NavigationBar> - <View style={styles.preferences__container}> - <NavigationScrollbars> - <SettingsHeader> - <HeaderTitle> - {messages.pgettext('preferences-view', 'Preferences')} - </HeaderTitle> - </SettingsHeader> + <NavigationScrollbars> + <SettingsHeader> + <HeaderTitle>{messages.pgettext('preferences-view', 'Preferences')}</HeaderTitle> + </SettingsHeader> - <View style={styles.preferences__content}> - <Cell.Container> - <Cell.Label> - {messages.pgettext('preferences-view', 'Launch app on start-up')} - </Cell.Label> - <Cell.Switch isOn={this.props.autoStart} onChange={this.props.setAutoStart} /> - </Cell.Container> - <View style={styles.preferences__separator} /> + <StyledContent> + <Cell.Container> + <Cell.Label> + {messages.pgettext('preferences-view', 'Launch app on start-up')} + </Cell.Label> + <Cell.Switch isOn={this.props.autoStart} onChange={this.props.setAutoStart} /> + </Cell.Container> + <StyledSeparator /> - <Cell.Container> - <Cell.Label> - {messages.pgettext('preferences-view', 'Auto-connect')} - </Cell.Label> - <Cell.Switch - isOn={this.props.autoConnect} - onChange={this.props.setAutoConnect} - /> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {messages.pgettext( - 'preferences-view', - 'Automatically connect to a server when the app launches.', - )} - </Cell.FooterText> - </Cell.Footer> + <Cell.Container> + <Cell.Label>{messages.pgettext('preferences-view', 'Auto-connect')}</Cell.Label> + <Cell.Switch isOn={this.props.autoConnect} onChange={this.props.setAutoConnect} /> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {messages.pgettext( + 'preferences-view', + 'Automatically connect to a server when the app launches.', + )} + </Cell.FooterText> + </Cell.Footer> - <Cell.Container> - <Cell.Label> - {messages.pgettext('preferences-view', 'Local network sharing')} - </Cell.Label> - <Cell.Switch isOn={this.props.allowLan} onChange={this.props.setAllowLan} /> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {messages.pgettext( - 'preferences-view', - 'Allows access to other devices on the same network for sharing, printing etc.', - )} - </Cell.FooterText> - </Cell.Footer> + <Cell.Container> + <Cell.Label> + {messages.pgettext('preferences-view', 'Local network sharing')} + </Cell.Label> + <Cell.Switch isOn={this.props.allowLan} onChange={this.props.setAllowLan} /> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {messages.pgettext( + 'preferences-view', + 'Allows access to other devices on the same network for sharing, printing etc.', + )} + </Cell.FooterText> + </Cell.Footer> + + <Cell.Container> + <Cell.Label>{messages.pgettext('preferences-view', 'Notifications')}</Cell.Label> + <Cell.Switch + isOn={this.props.enableSystemNotifications} + onChange={this.props.setEnableSystemNotifications} + /> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {messages.pgettext( + 'preferences-view', + 'Enable or disable system notifications. The critical notifications will always be displayed.', + )} + </Cell.FooterText> + </Cell.Footer> + <Cell.Container> + <Cell.Label> + {messages.pgettext('preferences-view', 'Monochromatic tray icon')} + </Cell.Label> + <Cell.Switch + isOn={this.props.monochromaticIcon} + onChange={this.props.setMonochromaticIcon} + /> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {messages.pgettext( + 'preferences-view', + 'Use a monochromatic tray icon instead of a colored one.', + )} + </Cell.FooterText> + </Cell.Footer> + + {this.props.enableStartMinimizedToggle ? ( + <React.Fragment> <Cell.Container> <Cell.Label> - {messages.pgettext('preferences-view', 'Notifications')} + {messages.pgettext('preferences-view', 'Start minimized')} </Cell.Label> <Cell.Switch - isOn={this.props.enableSystemNotifications} - onChange={this.props.setEnableSystemNotifications} + isOn={this.props.startMinimized} + onChange={this.props.setStartMinimized} /> </Cell.Container> <Cell.Footer> <Cell.FooterText> {messages.pgettext( 'preferences-view', - 'Enable or disable system notifications. The critical notifications will always be displayed.', + 'Show only the tray icon when the app starts.', )} </Cell.FooterText> </Cell.Footer> + </React.Fragment> + ) : undefined} - <Cell.Container> - <Cell.Label> - {messages.pgettext('preferences-view', 'Monochromatic tray icon')} - </Cell.Label> - <Cell.Switch - isOn={this.props.monochromaticIcon} - onChange={this.props.setMonochromaticIcon} - /> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {messages.pgettext( + <Cell.Container disabled={this.props.isBeta}> + <Cell.Label>{messages.pgettext('preferences-view', 'Beta program')}</Cell.Label> + <Cell.Switch + isOn={this.props.showBetaReleases} + onChange={this.props.setShowBetaReleases} + /> + </Cell.Container> + <Cell.Footer> + <Cell.FooterText> + {this.props.isBeta + ? messages.pgettext( + 'preferences-view', + 'This option is unavailable while using a beta version.', + ) + : messages.pgettext( 'preferences-view', - 'Use a monochromatic tray icon instead of a colored one.', + 'Enable to get notified when new beta versions of the app are released.', )} - </Cell.FooterText> - </Cell.Footer> - - {this.props.enableStartMinimizedToggle ? ( - <React.Fragment> - <Cell.Container> - <Cell.Label> - {messages.pgettext('preferences-view', 'Start minimized')} - </Cell.Label> - <Cell.Switch - isOn={this.props.startMinimized} - onChange={this.props.setStartMinimized} - /> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {messages.pgettext( - 'preferences-view', - 'Show only the tray icon when the app starts.', - )} - </Cell.FooterText> - </Cell.Footer> - </React.Fragment> - ) : undefined} - - <Cell.Container disabled={this.props.isBeta}> - <Cell.Label> - {messages.pgettext('preferences-view', 'Beta program')} - </Cell.Label> - <Cell.Switch - isOn={this.props.showBetaReleases} - onChange={this.props.setShowBetaReleases} - /> - </Cell.Container> - <Cell.Footer> - <Cell.FooterText> - {this.props.isBeta - ? messages.pgettext( - 'preferences-view', - 'This option is unavailable while using a beta version.', - ) - : messages.pgettext( - 'preferences-view', - 'Enable to get notified when new beta versions of the app are released.', - )} - </Cell.FooterText> - </Cell.Footer> - </View> - </NavigationScrollbars> - </View> - </NavigationContainer> - </View> - </Container> + </Cell.FooterText> + </Cell.Footer> + </StyledContent> + </NavigationScrollbars> + </NavigationContainer> + </StyledContainer> </Layout> ); } diff --git a/gui/src/renderer/components/PreferencesStyles.tsx b/gui/src/renderer/components/PreferencesStyles.tsx index 73c9691f7f..b9986f2c38 100644 --- a/gui/src/renderer/components/PreferencesStyles.tsx +++ b/gui/src/renderer/components/PreferencesStyles.tsx @@ -1,21 +1,18 @@ -import { Styles } from 'reactxp'; +import styled from 'styled-components'; import { colors } from '../../config.json'; +import { Container } from './Layout'; -export default { - preferences: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - preferences__container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), - preferences__content: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - marginBottom: 2, - }), - preferences__separator: Styles.createViewStyle({ - height: 1, - }), -}; +export const StyledContainer = styled(Container)({ + backgroundColor: colors.darkBlue, +}); + +export const StyledContent = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + marginBottom: '2px', +}); + +export const StyledSeparator = styled.div({ + height: '1px', +}); diff --git a/gui/src/renderer/components/SelectLanguage.tsx b/gui/src/renderer/components/SelectLanguage.tsx index 58f4a7b922..30a37a8a1d 100644 --- a/gui/src/renderer/components/SelectLanguage.tsx +++ b/gui/src/renderer/components/SelectLanguage.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; -import { Component, Styles, View } from 'reactxp'; import styled from 'styled-components'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; @@ -28,15 +27,9 @@ interface IState { source: Array<ISelectorItem<string>>; } -const styles = { - page: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - container: Styles.createViewStyle({ - flex: 1, - }), -}; +const StyledContainer = styled(Container)({ + backgroundColor: colors.darkBlue, +}); const StyledNavigationScrollbars = styled(NavigationScrollbars)({ flex: 1, @@ -46,7 +39,7 @@ const StyledSelector = (styled(Selector)({ marginBottom: 0, }) as unknown) as new <T>() => Selector<T>; -export default class SelectLanguage extends Component<IProps, IState> { +export default class SelectLanguage extends React.Component<IProps, IState> { private scrollView = React.createRef<CustomScrollbars>(); private selectedCellRef = React.createRef<SelectorCell<string>>(); @@ -67,45 +60,41 @@ export default class SelectLanguage extends Component<IProps, IState> { public render() { return ( <Layout> - <Container> - <View style={styles.page}> - <NavigationContainer> - <NavigationBar> - <NavigationItems> - <BackBarItem action={this.props.onClose}> - { - // TRANSLATORS: Back button in navigation bar - messages.pgettext('navigation-bar', 'Settings') - } - </BackBarItem> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('select-language-nav', 'Select language') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <StyledContainer> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <BackBarItem action={this.props.onClose}> + { + // TRANSLATORS: Back button in navigation bar + messages.pgettext('navigation-bar', 'Settings') + } + </BackBarItem> + <TitleBarItem> + { + // TRANSLATORS: Title label in navigation bar + messages.pgettext('select-language-nav', 'Select language') + } + </TitleBarItem> + </NavigationItems> + </NavigationBar> - <View style={styles.container}> - <StyledNavigationScrollbars> - <SettingsHeader> - <HeaderTitle> - {messages.pgettext('select-language-nav', 'Select language')} - </HeaderTitle> - </SettingsHeader> - <StyledSelector - title="" - values={this.state.source} - value={this.props.preferredLocale} - onSelect={this.props.setPreferredLocale} - selectedCellRef={this.selectedCellRef} - /> - </StyledNavigationScrollbars> - </View> - </NavigationContainer> - </View> - </Container> + <StyledNavigationScrollbars> + <SettingsHeader> + <HeaderTitle> + {messages.pgettext('select-language-nav', 'Select language')} + </HeaderTitle> + </SettingsHeader> + <StyledSelector + title="" + values={this.state.source} + value={this.props.preferredLocale} + onSelect={this.props.setPreferredLocale} + selectedCellRef={this.selectedCellRef} + /> + </StyledNavigationScrollbars> + </NavigationContainer> + </StyledContainer> </Layout> ); } diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx index dd3fef403c..a852d084ea 100644 --- a/gui/src/renderer/components/Settings.tsx +++ b/gui/src/renderer/components/Settings.tsx @@ -1,11 +1,9 @@ import * as React from 'react'; -import { Component, Text, View } from 'reactxp'; import { colors, links } from '../../config.json'; import { hasExpired, formatRemainingTime } from '../../shared/account-expiry'; import { messages } from '../../shared/gettext'; -import * as AppButton from './AppButton'; import * as Cell from './Cell'; -import { Container, Layout } from './Layout'; +import { Layout } from './Layout'; import { CloseBarItem, NavigationBar, @@ -14,7 +12,15 @@ import { TitleBarItem, } from './NavigationBar'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; -import styles, { CellIcon, OutOfTimeSubText, StyledNavigationScrollbars } from './SettingsStyles'; +import { + StyledCellIcon, + StyledCellSpacer, + StyledContainer, + StyledContent, + StyledNavigationScrollbars, + StyledOutOfTimeSubText, + StyledQuitButton, +} from './SettingsStyles'; import { LoginState } from '../redux/account/reducers'; @@ -37,47 +43,43 @@ export interface IProps { onExternalLink: (url: string) => void; } -export default class Settings extends Component<IProps> { +export default class Settings extends React.Component<IProps> { public render() { const showLargeTitle = this.props.loginState.type !== 'ok'; return ( <Layout> - <Container> - <View style={styles.settings}> - <NavigationContainer> - <NavigationBar alwaysDisplayBarTitle={!showLargeTitle}> - <NavigationItems> - <CloseBarItem action={this.props.onClose} /> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('navigation-bar', 'Settings') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <StyledContainer> + <NavigationContainer> + <NavigationBar alwaysDisplayBarTitle={!showLargeTitle}> + <NavigationItems> + <CloseBarItem action={this.props.onClose} /> + <TitleBarItem> + { + // TRANSLATORS: Title label in navigation bar + messages.pgettext('navigation-bar', 'Settings') + } + </TitleBarItem> + </NavigationItems> + </NavigationBar> + + <StyledNavigationScrollbars> + <StyledContent> + {showLargeTitle && ( + <SettingsHeader> + <HeaderTitle>{messages.pgettext('navigation-bar', 'Settings')}</HeaderTitle> + </SettingsHeader> + )} + + {this.renderTopButtons()} + {this.renderMiddleButtons()} + {this.renderBottomButtons()} - <View style={styles.container}> - <StyledNavigationScrollbars> - <View style={styles.content}> - {showLargeTitle && ( - <SettingsHeader> - <HeaderTitle>{messages.pgettext('navigation-bar', 'Settings')}</HeaderTitle> - </SettingsHeader> - )} - <View> - {this.renderTopButtons()} - {this.renderMiddleButtons()} - {this.renderBottomButtons()} - </View> - {this.renderQuitButton()} - </View> - </StyledNavigationScrollbars> - </View> - </NavigationContainer> - </View> - </Container> + {this.renderQuitButton()} + </StyledContent> + </StyledNavigationScrollbars> + </NavigationContainer> + </StyledContainer> </Layout> ); } @@ -87,11 +89,9 @@ export default class Settings extends Component<IProps> { private renderQuitButton() { return ( - <View style={styles.quitButtonFooter}> - <AppButton.RedButton onClick={this.props.onQuit}> - {messages.pgettext('settings-view', 'Quit app')} - </AppButton.RedButton> - </View> + <StyledQuitButton onClick={this.props.onQuit}> + {messages.pgettext('settings-view', 'Quit app')} + </StyledQuitButton> ); } @@ -109,21 +109,19 @@ export default class Settings extends Component<IProps> { const outOfTimeMessage = messages.pgettext('settings-view', 'OUT OF TIME'); return ( - <View> - <View> - <Cell.CellButton onClick={this.props.onViewAccount}> - <Cell.Label> - { - // TRANSLATORS: Navigation button to the 'Account' view - messages.pgettext('settings-view', 'Account') - } - </Cell.Label> - <OutOfTimeSubText isOutOfTime={isOutOfTime}> - {isOutOfTime ? outOfTimeMessage : formattedExpiry} - </OutOfTimeSubText> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - </View> + <> + <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> <Cell.CellButton onClick={this.props.onViewPreferences}> <Cell.Label> @@ -144,8 +142,8 @@ export default class Settings extends Component<IProps> { </Cell.Label> <Cell.Icon height={12} width={7} source="icon-chevron" /> </Cell.CellButton> - <View style={styles.cellSpacer} /> - </View> + <StyledCellSpacer /> + </> ); } @@ -167,18 +165,18 @@ export default class Settings extends Component<IProps> { ? inconsistentVersionMessage : updateAvailableMessage; - icon = <CellIcon source="icon-alert" tintColor={colors.red} />; + icon = <StyledCellIcon source="icon-alert" tintColor={colors.red} />; footer = ( - <View style={styles.cellFooter}> - <Text style={styles.cellFooterLabel}>{message}</Text> - </View> + <Cell.Footer> + <Cell.FooterText>{message}</Cell.FooterText> + </Cell.Footer> ); } else { - footer = <View style={styles.cellSpacer} />; + footer = <StyledCellSpacer />; } return ( - <View> + <> <Cell.CellButton disabled={this.props.isOffline} onClick={this.openDownloadLink}> {icon} <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label> @@ -186,13 +184,13 @@ export default class Settings extends Component<IProps> { <Cell.Icon height={16} width={16} source="icon-extLink" /> </Cell.CellButton> {footer} - </View> + </> ); } private renderBottomButtons() { return ( - <View> + <> <Cell.CellButton onClick={this.props.onViewSupport}> <Cell.Label> { @@ -214,7 +212,7 @@ export default class Settings extends Component<IProps> { </Cell.CellButton> <Cell.CellButton onClick={this.props.onViewSelectLanguage}> - <CellIcon width={24} height={24} source="icon-language" /> + <StyledCellIcon width={24} height={24} source="icon-language" /> <Cell.Label> { // TRANSLATORS: Navigation button to the 'Language' settings view @@ -224,7 +222,7 @@ export default class Settings extends Component<IProps> { <Cell.SubText>{this.props.preferredLocaleDisplayName}</Cell.SubText> <Cell.Icon height={12} width={7} source="icon-chevron" /> </Cell.CellButton> - </View> + </> ); } } diff --git a/gui/src/renderer/components/SettingsStyles.tsx b/gui/src/renderer/components/SettingsStyles.tsx index e971273f14..d396faaecc 100644 --- a/gui/src/renderer/components/SettingsStyles.tsx +++ b/gui/src/renderer/components/SettingsStyles.tsx @@ -1,14 +1,15 @@ -import { Styles } from 'reactxp'; import styled from 'styled-components'; import { colors } from '../../config.json'; +import * as AppButton from './AppButton'; import * as Cell from './Cell'; +import { Container } from './Layout'; import { NavigationScrollbars } from './NavigationBar'; -export const OutOfTimeSubText = styled(Cell.SubText)((props: { isOutOfTime: boolean }) => ({ +export const StyledOutOfTimeSubText = styled(Cell.SubText)((props: { isOutOfTime: boolean }) => ({ color: props.isOutOfTime ? colors.red : undefined, })); -export const CellIcon = styled(Cell.UntintedIcon)({ +export const StyledCellIcon = styled(Cell.UntintedIcon)({ marginRight: '8px', }); @@ -16,40 +17,24 @@ export const StyledNavigationScrollbars = styled(NavigationScrollbars)({ flex: 1, }); -export default { - settings: Styles.createViewStyle({ - backgroundColor: colors.darkBlue, - flex: 1, - }), - container: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - }), - content: Styles.createViewStyle({ - flexDirection: 'column', - flex: 1, - justifyContent: 'space-between', - overflow: 'visible', - }), - cellSpacer: Styles.createViewStyle({ - height: 20, - flex: 0, - }), - cellFooter: Styles.createViewStyle({ - paddingTop: 6, - paddingHorizontal: 22, - paddingBottom: 20, - }), - quitButtonFooter: Styles.createViewStyle({ - paddingTop: 20, - paddingBottom: 22, - paddingHorizontal: 22, - }), - cellFooterLabel: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - lineHeight: 20, - color: colors.white60, - }), -}; +export const StyledContainer = styled(Container)({ + backgroundColor: colors.darkBlue, +}); + +export const StyledContent = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + justifyContent: 'space-between', + overflow: 'visible', +}); + +export const StyledCellSpacer = styled.div({ + height: '20px', + minHeight: '20px', + flex: 0, +}); + +export const StyledQuitButton = styled(AppButton.RedButton)({ + margin: '20px 22px 22px', +}); |
