summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README.md12
-rw-r--r--gui/packages/components/package.json2
-rw-r--r--gui/packages/desktop/package.json2
-rw-r--r--gui/packages/desktop/src/renderer/components/AdvancedSettings.tsx197
-rw-r--r--gui/packages/desktop/src/renderer/components/AdvancedSettingsStyles.tsx46
-rw-r--r--gui/packages/desktop/src/renderer/components/Cell.tsx103
-rw-r--r--gui/packages/desktop/src/renderer/containers/AdvancedSettingsPage.tsx21
-rw-r--r--gui/yarn.lock8
8 files changed, 195 insertions, 196 deletions
diff --git a/README.md b/README.md
index 0a1fdc0e7e..a81ae5561b 100644
--- a/README.md
+++ b/README.md
@@ -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"