diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2019-02-09 13:28:57 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2019-02-09 13:28:57 +0100 |
| commit | 9f70827cbd1e5df4e6ee7cbba13d3cc7cea654a1 (patch) | |
| tree | 290b013c0d08fb0f5e9bcbde3a3d18c4965b3eab | |
| parent | 04c35b86c414c490e5bf726e15e5bb9b24b23ad5 (diff) | |
| parent | 81d4263043c5304273379be830af8519b9e50b9f (diff) | |
| download | mullvadvpn-9f70827cbd1e5df4e6ee7cbba13d3cc7cea654a1.tar.xz mullvadvpn-9f70827cbd1e5df4e6ee7cbba13d3cc7cea654a1.zip | |
Merge branch 'improve-advanced-settings'
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | gui/packages/components/package.json | 2 | ||||
| -rw-r--r-- | gui/packages/desktop/package.json | 2 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx | 197 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/components/AdvancedSettingsStyles.tsx | 46 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/components/Cell.tsx | 103 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx | 21 | ||||
| -rw-r--r-- | gui/yarn.lock | 8 |
8 files changed, 195 insertions, 196 deletions
@@ -254,12 +254,14 @@ this procedure, the `integration-tests.sh` script can be used to run all integra - **gui/packages/** - **components/** - Platform agnostic shared react components - **desktop/** - The desktop implementation + - **assets/** - graphical assets and stylesheets - **src/** - - **assets/** - graphical assets and stylesheets + - **main/** + - **index.ts** - entry file for the main process - **renderer/** - - **app.js** - entry file for renderer process - - **routes.js** - routes configurator - - **transitions.js** - transition rules between views + - **app.ts** - entry file for the renderer process + - **routes.ts** - routes configurator + - **transitions.ts** - transition rules between views - **config.json** - App color definitions and URLs to external resources - **test/** - Electron GUI tests - **dist-assets/** - Icons, binaries and other files used when creating the distributables @@ -370,7 +372,7 @@ environment variable. ### GUI The GUI has a specific settings file that is configured for each user. The path is set in the -`gui/packages/desktop/main/gui-settings.js` file. +`gui/packages/desktop/main/gui-settings.ts` file. | Platform | Path | |----------|------| diff --git a/gui/packages/components/package.json b/gui/packages/components/package.json index fb96900c35..a173877573 100644 --- a/gui/packages/components/package.json +++ b/gui/packages/components/package.json @@ -36,7 +36,7 @@ "rimraf": "^2.6.2", "ts-node": "^7.0.1", "tslint": "^5.12.1", - "typescript": "^3.2.4" + "typescript": "^3.3.3" }, "dependencies": {}, "peerDependencies": { diff --git a/gui/packages/desktop/package.json b/gui/packages/desktop/package.json index 68fe6a59fd..37e360b76b 100644 --- a/gui/packages/desktop/package.json +++ b/gui/packages/desktop/package.json @@ -69,7 +69,7 @@ "sinon": "^7.1.1", "ts-node": "^7.0.1", "tslint": "^5.12.1", - "typescript": "^3.2.4" + "typescript": "^3.3.3" }, "scripts": { "postinstall": "electron-builder install-app-deps", 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(); diff --git a/gui/yarn.lock b/gui/yarn.lock index e50b299936..29007dbd94 100644 --- a/gui/yarn.lock +++ b/gui/yarn.lock @@ -7919,10 +7919,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d" - integrity sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg== +typescript@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3.tgz#f1657fc7daa27e1a8930758ace9ae8da31403221" + integrity sha512-Y21Xqe54TBVp+VDSNbuDYdGw0BpoR/Q6wo/+35M8PAU0vipahnyduJWirxxdxjsAkS7hue53x2zp8gz7F05u0A== ua-parser-js@0.7.17: version "0.7.17" |
