diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2019-02-04 14:43:45 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2019-02-08 15:26:24 +0100 |
| commit | 43bdfe6f7a64135730479de8fc36d6514e34b288 (patch) | |
| tree | fefff8f9140857dd74a71292a2cbbfccae23526b | |
| parent | 04c35b86c414c490e5bf726e15e5bb9b24b23ad5 (diff) | |
| download | mullvadvpn-43bdfe6f7a64135730479de8fc36d6514e34b288.tar.xz mullvadvpn-43bdfe6f7a64135730479de8fc36d6514e34b288.zip | |
Refactor AdvancedSettings
4 files changed, 182 insertions, 185 deletions
diff --git a/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx b/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx index c8a2b67c93..7fdcb609eb 100644 --- a/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx +++ b/gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx @@ -1,10 +1,8 @@ -/* tslint:disable:jsx-no-lambda */ -// TODO: Refactor this file to fix the jsx-no-lambda warnings - -import { HeaderTitle, ImageView, SettingsHeader } from '@mullvad/components'; +import { HeaderTitle, SettingsHeader } from '@mullvad/components'; import * as React from 'react'; -import { Button, Component, Text, View } from 'reactxp'; +import { Component, View } from 'reactxp'; import { colors } from '../../config.json'; +import { RelayProtocol } from '../../shared/daemon-rpc-types'; import styles from './AdvancedSettingsStyles'; import * as Cell from './Cell'; import { Container, Layout } from './Layout'; @@ -19,17 +17,34 @@ import Switch from './Switch'; const MIN_MSSFIX_VALUE = 1000; const MAX_MSSFIX_VALUE = 1450; +const PROTOCOLS: RelayProtocol[] = ['udp', 'tcp']; +const UDP_PORTS = [1194, 1195, 1196, 1197, 1300, 1301, 1302]; +const TCP_PORTS = [80, 443]; + +const PORT_ITEMS: { [key in RelayProtocol]: Array<ISelectorItem<number>> } = { + udp: UDP_PORTS.map(mapPortToSelectorItem), + tcp: TCP_PORTS.map(mapPortToSelectorItem), +}; + +const PROTOCOL_ITEMS: Array<ISelectorItem<RelayProtocol>> = PROTOCOLS.map((value) => ({ + label: value.toUpperCase(), + value, +})); + +function mapPortToSelectorItem(value: number): ISelectorItem<number> { + return { label: value.toString(), value }; +} interface IProps { enableIpv6: boolean; blockWhenDisconnected: boolean; - protocol: string; + protocol?: RelayProtocol; mssfix?: number; - port: string | number; + port?: number; setEnableIpv6: (value: boolean) => void; setBlockWhenDisconnected: (value: boolean) => void; setOpenVpnMssfix: (value: number | undefined) => void; - onUpdate: (protocol: string, port: string | number) => void; + setRelayProtocolAndPort: (protocol?: RelayProtocol, port?: number) => void; onClose: () => void; } @@ -39,7 +54,7 @@ interface IState { focusOnMssfix: boolean; } -export class AdvancedSettings extends Component<IProps, IState> { +export default class AdvancedSettings extends Component<IProps, IState> { constructor(props: IProps) { super(props); @@ -61,19 +76,9 @@ export class AdvancedSettings extends Component<IProps, IState> { } public render() { - let portSelector = null; - let protocol = this.props.protocol.toUpperCase(); - - if (protocol === 'AUTOMATIC') { - protocol = 'Automatic'; - } else { - portSelector = this.createPortSelector(); - } - const mssfixStyle = this.mssfixIsValid() ? styles.advanced_settings__mssfix_valid_value : styles.advanced_settings__mssfix_invalid_value; - const mssfixValue = this.state.editedMssfix; return ( @@ -116,16 +121,23 @@ export class AdvancedSettings extends Component<IProps, IState> { <View style={styles.advanced_settings__content}> <Selector title={'Network protocols'} - values={['Automatic', 'UDP', 'TCP']} - value={protocol} - onSelect={(selectedProtocol) => { - this.props.onUpdate(selectedProtocol, 'Automatic'); - }} + values={PROTOCOL_ITEMS} + value={this.props.protocol} + onSelect={this.onSelectProtocol} /> <View style={styles.advanced_settings__cell_spacer} /> - {portSelector} + {this.props.protocol ? ( + <Selector + title={`${this.props.protocol.toUpperCase()} port`} + values={PORT_ITEMS[this.props.protocol]} + value={this.props.port} + onSelect={this.onSelectPort} + /> + ) : ( + undefined + )} </View> <Cell.Container> @@ -155,24 +167,13 @@ export class AdvancedSettings extends Component<IProps, IState> { ); } - private createPortSelector() { - const protocol = this.props.protocol.toUpperCase(); - const ports = - protocol === 'TCP' - ? ['Automatic', 80, 443] - : ['Automatic', 1194, 1195, 1196, 1197, 1300, 1301, 1302]; + private onSelectProtocol = (protocol?: RelayProtocol) => { + this.props.setRelayProtocolAndPort(protocol); + }; - return ( - <Selector - title={protocol + ' port'} - values={ports} - value={this.props.port} - onSelect={(port) => { - this.props.onUpdate(protocol, port); - }} - /> - ); - } + private onSelectPort = (port?: number) => { + this.props.setRelayProtocolAndPort(this.props.protocol, port); + }; private onMssfixChange = (mssfixString: string) => { const mssfix = mssfixString.replace(/[^0-9]/g, ''); @@ -204,82 +205,74 @@ export class AdvancedSettings extends Component<IProps, IState> { } } -interface ISelectorProps<T> { - title: string; - values: T[]; +interface ISelectorItem<T> { + label: string; value: T; - onSelect: (value: T) => void; } -interface ISelectorState<T> { - hoveredButtonValue?: T; +interface ISelectorProps<T> { + title: string; + values: Array<ISelectorItem<T>>; + value?: T; + onSelect: (value?: T) => void; } -class Selector<T> extends Component<ISelectorProps<T>, ISelectorState<T>> { - public state: ISelectorState<T> = {}; - +class Selector<T> extends Component<ISelectorProps<T>> { public render() { return ( - <View> - <View style={styles.advanced_settings__section_title}>{this.props.title}</View> - - {this.props.values.map((value) => this.renderCell(value))} - </View> + <Cell.Section> + <Cell.SectionTitle>{this.props.title}</Cell.SectionTitle> + <SelectorCell + key={'auto'} + selected={this.props.value === undefined} + onSelect={this.props.onSelect}> + {'Automatic'} + </SelectorCell> + {this.props.values.map((item, i) => ( + <SelectorCell + key={i} + value={item.value} + selected={item.value === this.props.value} + onSelect={this.props.onSelect}> + {item.label} + </SelectorCell> + ))} + </Cell.Section> ); } +} - private handleButtonHover = (value?: T) => { - this.setState({ hoveredButtonValue: value }); - }; - - private renderCell(value: T) { - const selected = value === this.props.value; - if (selected) { - return this.renderSelectedCell(value); - } else { - return this.renderUnselectedCell(value); - } - } +interface ISelectorCell<T> { + value?: T; + selected: boolean; + onSelect: (value?: T) => void; + children?: React.ReactText; +} - private renderSelectedCell(value: T) { +class SelectorCell<T> extends Component<ISelectorCell<T>> { + public render() { return ( - <Button - style={[ - styles.advanced_settings__cell, - value === this.state.hoveredButtonValue - ? [styles.advanced_settings__cell_selected_hover] - : undefined, - ]} - onPress={() => this.props.onSelect(value)} - onHoverStart={() => this.handleButtonHover(value)} - onHoverEnd={() => this.handleButtonHover(undefined)} - key={value.toString()}> - <ImageView - style={styles.advanced_settings__cell_icon} + <Cell.CellButton + style={this.props.selected ? styles.advanced_settings__cell_selected_hover : undefined} + cellHoverStyle={ + this.props.selected ? styles.advanced_settings__cell_selected_hover : undefined + } + onPress={this.onPress}> + <Cell.Icon + style={this.props.selected ? undefined : styles.advanced_settings__cell_icon_invisible} source="icon-tick" + width={24} + height={24} tintColor={colors.white} /> - <Text style={styles.advanced_settings__cell_label}>{value}</Text> - </Button> + <Cell.Label>{this.props.children}</Cell.Label> + </Cell.CellButton> ); } - private renderUnselectedCell(value: T) { - return ( - <Button - style={[ - styles.advanced_settings__cell_dimmed, - value === this.state.hoveredButtonValue - ? styles.advanced_settings__cell_hover - : undefined, - ]} - onPress={() => this.props.onSelect(value)} - onHoverStart={() => this.handleButtonHover(value)} - onHoverEnd={() => this.handleButtonHover(undefined)} - key={value.toString()}> - <View style={styles.advanced_settings__cell_icon} /> - <Text style={styles.advanced_settings__cell_label}>{value}</Text> - </Button> - ); - } + private onPress = () => { + if (!this.props.selected) { + this.props.onSelect(this.props.value); + } + }; } diff --git a/gui/packages/desktop/src/renderer/components/AdvancedSettingsStyles.tsx b/gui/packages/desktop/src/renderer/components/AdvancedSettingsStyles.tsx index b864c72ebf..fd8be66c98 100644 --- a/gui/packages/desktop/src/renderer/components/AdvancedSettingsStyles.tsx +++ b/gui/packages/desktop/src/renderer/components/AdvancedSettingsStyles.tsx @@ -7,7 +7,6 @@ export default { flex: 1, }), advanced_settings__container: Styles.createViewStyle({ - flexDirection: 'column', flex: 1, }), // plain CSS style @@ -15,20 +14,7 @@ export default { flex: 1, }, advanced_settings__content: Styles.createViewStyle({ - flexDirection: 'column', flex: 0, - overflow: 'visible', - }), - advanced_settings__cell: Styles.createButtonStyle({ - cursor: 'default', - backgroundColor: colors.green, - flexDirection: 'row', - paddingTop: 14, - paddingBottom: 14, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 1, - justifyContent: 'flex-start', }), advanced_settings__cell_hover: Styles.createButtonStyle({ backgroundColor: colors.blue80, @@ -39,36 +25,8 @@ export default { advanced_settings__cell_spacer: Styles.createViewStyle({ height: 24, }), - advanced_settings__cell_icon: Styles.createViewStyle({ - width: 24, - height: 24, - marginRight: 8, - flex: 0, - }), - advanced_settings__cell_dimmed: Styles.createButtonStyle({ - cursor: 'default', - paddingTop: 14, - paddingBottom: 14, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 1, - backgroundColor: colors.blue40, - flexDirection: 'row', - justifyContent: 'flex-start', - }), - - advanced_settings__section_title: Styles.createTextStyle({ - backgroundColor: colors.blue, - paddingTop: 14, - paddingBottom: 14, - paddingLeft: 24, - paddingRight: 24, - marginBottom: 1, - fontFamily: 'DINPro', - fontSize: 20, - fontWeight: '900', - lineHeight: 26, - color: colors.white, + advanced_settings__cell_icon_invisible: Styles.createViewStyle({ + opacity: 0, }), advanced_settings__cell_label: Styles.createTextStyle({ fontFamily: 'DINPro', diff --git a/gui/packages/desktop/src/renderer/components/Cell.tsx b/gui/packages/desktop/src/renderer/components/Cell.tsx index c2dedc9dfc..501e51a0c7 100644 --- a/gui/packages/desktop/src/renderer/components/Cell.tsx +++ b/gui/packages/desktop/src/renderer/components/Cell.tsx @@ -4,19 +4,25 @@ import { Button, Component, Styles, Text, TextInput, Types, View } from 'reactxp import { colors } from '../../config.json'; const styles = { - cellButton: Styles.createButtonStyle({ - backgroundColor: colors.blue, - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 16, - paddingRight: 16, - marginBottom: 1, - flex: 1, - flexDirection: 'row', - alignItems: 'center', - alignContent: 'center', - cursor: 'default', - }), + cellButton: { + base: Styles.createButtonStyle({ + backgroundColor: colors.blue, + paddingVertical: 0, + paddingHorizontal: 16, + marginBottom: 1, + flex: 1, + flexDirection: 'row', + alignItems: 'center', + alignContent: 'center', + cursor: 'default', + }), + section: Styles.createButtonStyle({ + backgroundColor: colors.blue40, + }), + hover: Styles.createButtonStyle({ + backgroundColor: colors.blue80, + }), + }, cellContainer: Styles.createViewStyle({ backgroundColor: colors.blue, flexDirection: 'row', @@ -24,7 +30,6 @@ const styles = { paddingLeft: 16, paddingRight: 12, }), - footer: { container: Styles.createViewStyle({ paddingTop: 8, @@ -41,7 +46,6 @@ const styles = { color: colors.white80, }), }, - label: { container: Styles.createViewStyle({ marginLeft: 8, @@ -58,7 +62,6 @@ const styles = { color: colors.white, }), }, - input: { frame: Styles.createViewStyle({ flexGrow: 0, @@ -77,14 +80,9 @@ const styles = { textAlign: 'right', }), }, - - cellHover: Styles.createButtonStyle({ - backgroundColor: colors.blue80, - }), icon: Styles.createViewStyle({ marginLeft: 8, }), - subtext: Styles.createTextStyle({ color: colors.white60, fontFamily: 'Open Sans', @@ -94,6 +92,17 @@ const styles = { textAlign: 'right', marginLeft: 8, }), + sectionTitle: Styles.createTextStyle({ + backgroundColor: colors.blue, + paddingVertical: 14, + paddingHorizontal: 24, + marginBottom: 1, + fontFamily: 'DINPro', + fontSize: 20, + fontWeight: '900', + lineHeight: 26, + color: colors.white, + }), }; interface ICellButtonProps { @@ -108,6 +117,7 @@ interface IState { hovered: boolean; } +const CellSectionContext = React.createContext<boolean>(false); const CellHoverContext = React.createContext<boolean>(false); export class CellButton extends Component<ICellButtonProps, IState> { @@ -118,15 +128,50 @@ export class CellButton extends Component<ICellButtonProps, IState> { public render() { const { children, style, cellHoverStyle, ...otherProps } = this.props; - const hoverStyle = cellHoverStyle || styles.cellHover; + const hoverStyle = cellHoverStyle || styles.cellButton.hover; + return ( + <CellSectionContext.Consumer> + {(containedInSection) => ( + <Button + style={[ + styles.cellButton.base, + containedInSection ? styles.cellButton.section : undefined, + style, + this.state.hovered ? hoverStyle : undefined, + ]} + onHoverStart={this.onHoverStart} + onHoverEnd={this.onHoverEnd} + {...otherProps}> + <CellHoverContext.Provider value={this.state.hovered}> + {children} + </CellHoverContext.Provider> + </Button> + )} + </CellSectionContext.Consumer> + ); + } +} + +interface ISectionTitleProps { + children?: React.ReactText; +} + +export function SectionTitle(props: ISectionTitleProps) { + return <Text style={styles.sectionTitle}>{props.children}</Text>; +} + +interface ISectionProps { + children?: React.ReactNode; +} + +export class Section extends Component<ISectionProps> { + public render() { return ( - <Button - style={[styles.cellButton, style, this.state.hovered ? hoverStyle : undefined]} - onHoverStart={this.onHoverStart} - onHoverEnd={this.onHoverEnd} - {...otherProps}> - <CellHoverContext.Provider value={this.state.hovered}>{children}</CellHoverContext.Provider> - </Button> + <View> + <CellSectionContext.Provider value={true}> + {this.props.children} + </CellSectionContext.Provider> + </View> ); } } diff --git a/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx b/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx index d70c0a99ee..5fbdd57b25 100644 --- a/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx +++ b/gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx @@ -3,7 +3,7 @@ import log from 'electron-log'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { RelayProtocol } from '../../shared/daemon-rpc-types'; -import { AdvancedSettings } from '../components/AdvancedSettings'; +import AdvancedSettings from '../components/AdvancedSettings'; import RelaySettingsBuilder from '../lib/relay-settings-builder'; import { RelaySettingsRedux } from '../redux/settings/reducers'; @@ -25,8 +25,8 @@ const mapRelaySettingsToProtocolAndPort = (relaySettings: RelaySettingsRedux) => if ('normal' in relaySettings) { const { protocol, port } = relaySettings.normal; return { - protocol: protocol === 'any' ? 'Automatic' : protocol, - port: port === 'any' ? 'Automatic' : port, + protocol: protocol === 'any' ? undefined : protocol, + port: port === 'any' ? undefined : port, }; } else if ('customTunnelEndpoint' in relaySettings) { const { protocol, port } = relaySettings.customTunnelEndpoint; @@ -42,18 +42,19 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: ISharedRouteProps) = onClose: () => { history.goBack(); }, - onUpdate: async (protocol: string, port: string | number) => { + setRelayProtocolAndPort: async (protocol?: RelayProtocol, port?: number) => { const relayUpdate = RelaySettingsBuilder.normal() .tunnel.openvpn((openvpn) => { - if (protocol === 'Automatic') { - openvpn.protocol.any(); + if (protocol) { + openvpn.protocol.exact(protocol); } else { - openvpn.protocol.exact(protocol.toLowerCase() as RelayProtocol); + openvpn.protocol.any(); } - if (typeof port === 'string' && port === 'Automatic') { - openvpn.port.any(); - } else if (typeof port === 'number') { + + if (port) { openvpn.port.exact(port); + } else { + openvpn.port.any(); } }) .build(); |
