summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
Diffstat (limited to 'gui')
-rw-r--r--gui/packages/components/package.json4
-rw-r--r--gui/packages/components/src/ConnectionInfo.tsx125
-rw-r--r--gui/packages/components/src/ConnectionInfoDisclosure.tsx89
-rw-r--r--gui/packages/components/src/ImageView.tsx2
-rw-r--r--gui/packages/desktop/src/renderer/app.js6
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.js218
-rw-r--r--gui/packages/desktop/src/renderer/components/ConnectStyles.js49
-rw-r--r--gui/packages/desktop/src/renderer/components/SupportStyles.js4
-rw-r--r--gui/packages/desktop/src/renderer/components/TunnelControl.js239
-rw-r--r--gui/packages/desktop/src/renderer/containers/ConnectPage.js6
-rw-r--r--gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js2
-rw-r--r--gui/packages/desktop/src/renderer/redux/store.js16
-rw-r--r--gui/packages/desktop/src/renderer/redux/userinterface/actions.js29
-rw-r--r--gui/packages/desktop/src/renderer/redux/userinterface/reducers.js28
-rw-r--r--gui/packages/desktop/src/renderer/redux/window/actions.js17
-rw-r--r--gui/packages/desktop/src/renderer/redux/window/reducers.js22
16 files changed, 507 insertions, 349 deletions
diff --git a/gui/packages/components/package.json b/gui/packages/components/package.json
index c55198c0b8..6caf64bf0e 100644
--- a/gui/packages/components/package.json
+++ b/gui/packages/components/package.json
@@ -9,8 +9,10 @@
"lint": "tslint -t stylish -p .",
"test": "mocha -R spec --require ts-node/register --require \"test/setup.ts\" \"test/**/*.spec.tsx\"",
"build": "run-s private:build:clean private:build:compile",
+ "watch": "run-s private:build:clean private:build:watch",
"private:build:clean": "rimraf build",
- "private:build:compile": "tsc"
+ "private:build:compile": "tsc",
+ "private:build:watch": "tsc -w"
},
"devDependencies": {
"@types/chai": "^4.1.4",
diff --git a/gui/packages/components/src/ConnectionInfo.tsx b/gui/packages/components/src/ConnectionInfo.tsx
index 546d0f9c44..c4aaeef29f 100644
--- a/gui/packages/components/src/ConnectionInfo.tsx
+++ b/gui/packages/components/src/ConnectionInfo.tsx
@@ -1,21 +1,38 @@
import * as React from 'react';
-import { Component, Styles, Text, View } from 'reactxp';
-import { default as Accordion } from './Accordion';
+import { Component, Styles, Text, Types, View } from 'reactxp';
+import { default as ConnectionInfoDisclosure } from './ConnectionInfoDisclosure';
const styles = {
- toggle: Styles.createTextStyle({
+ row: Styles.createViewStyle({
+ flexDirection: 'row',
+ marginTop: 3,
+ }),
+ caption: Styles.createTextStyle({
fontFamily: 'Open Sans',
- fontSize: 14,
- fontWeight: '800',
- color: 'rgb(255, 255, 255, 0.4)',
- paddingBottom: 2,
+ fontSize: 13,
+ fontWeight: '600',
+ color: 'rgb(255, 255, 255)',
+ flex: 0,
+ flexBasis: 30,
+ marginRight: 8,
}),
- content: Styles.createTextStyle({
+ value: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: '600',
+ color: 'rgb(255, 255, 255)',
+ letterSpacing: -0.2,
+ }),
+ header: Styles.createViewStyle({
+ flexDirection: 'row',
+ alignItems: 'center',
+ }),
+ hostname: Styles.createTextStyle({
fontFamily: 'Open Sans',
fontSize: 16,
- fontWeight: '800',
+ fontWeight: '600',
color: 'rgb(255, 255, 255)',
- paddingBottom: 2,
+ flex: 1,
}),
};
@@ -26,49 +43,79 @@ interface IInAddress {
}
interface IOutAddress {
- ipv4: string | null;
- ipv6: string | null;
+ ipv4?: string;
+ ipv6?: string;
}
interface IProps {
+ hostname?: string;
inAddress?: IInAddress;
- outAddress: IOutAddress;
- isExpanded: boolean;
- onToggle?: () => void;
+ outAddress?: IOutAddress;
+ defaultOpen?: boolean;
+ style?: Types.ViewStyleRuleSet | Types.ViewStyleRuleSet[];
+ onToggle?: (isOpen: boolean) => void;
}
-export default class ConnectionInfo extends Component<IProps> {
+interface IState {
+ isOpen: boolean;
+}
+
+export default class ConnectionInfo extends Component<IProps, IState> {
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ isOpen: props.defaultOpen === true,
+ };
+ }
+
public render() {
const { inAddress, outAddress } = this.props;
return (
- <View>
- <Accordion height={this.props.isExpanded ? 'auto' : 0}>
- {inAddress && (
- <Text style={styles.content}>{`IN: ${inAddress.ip}:${inAddress.port} - ${
- inAddress.protocol
- }`}</Text>
- )}
+ <View style={this.props.style}>
+ <View style={styles.header}>
+ <Text style={styles.hostname}>{this.props.hostname || ''}</Text>
+ <ConnectionInfoDisclosure defaultOpen={this.props.defaultOpen} onToggle={this.onToggle}>
+ {'Connection details'}
+ </ConnectionInfoDisclosure>
+ </View>
+
+ {this.state.isOpen && (
+ <React.Fragment>
+ {inAddress && (
+ <View style={styles.row}>
+ <Text style={styles.caption}>{'In'}</Text>
+ <Text style={styles.value}>
+ {`${inAddress.ip}:${inAddress.port} ${inAddress.protocol.toUpperCase()}`}
+ </Text>
+ </View>
+ )}
- {(outAddress.ipv4 || outAddress.ipv6) && (
- <Text style={styles.content}>
- {'OUT: ' +
- [outAddress.ipv4, outAddress.ipv6]
- .filter((a) => typeof a !== 'undefined')
- .join(' / ')}
- </Text>
- )}
- </Accordion>
- <Text style={styles.toggle} onPress={this.toggle}>
- {this.props.isExpanded ? 'LESS' : 'MORE'}
- </Text>
+ {outAddress &&
+ (outAddress.ipv4 || outAddress.ipv6) && (
+ <View style={styles.row}>
+ <Text style={styles.caption}>{'Out'}</Text>
+ <View>
+ {outAddress.ipv4 && <Text style={styles.value}>{outAddress.ipv4}</Text>}
+ {outAddress.ipv6 && <Text style={styles.value}>{outAddress.ipv6}</Text>}
+ </View>
+ </View>
+ )}
+ </React.Fragment>
+ )}
</View>
);
}
- private toggle = () => {
- if (this.props.onToggle) {
- this.props.onToggle();
- }
+ private onToggle = (isOpen: boolean) => {
+ this.setState(
+ (state) => ({ ...state, isOpen }),
+ () => {
+ if (this.props.onToggle) {
+ this.props.onToggle(isOpen);
+ }
+ },
+ );
};
}
diff --git a/gui/packages/components/src/ConnectionInfoDisclosure.tsx b/gui/packages/components/src/ConnectionInfoDisclosure.tsx
new file mode 100644
index 0000000000..93cd17b1d0
--- /dev/null
+++ b/gui/packages/components/src/ConnectionInfoDisclosure.tsx
@@ -0,0 +1,89 @@
+import * as React from 'react';
+import { Component, Styles, Text, Types, View } from 'reactxp';
+import ImageView from './ImageView';
+
+const styles = {
+ container: Styles.createViewStyle({
+ flexDirection: 'row',
+ alignItems: 'center',
+ }),
+ caption: {
+ base: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: '600',
+ color: 'rgb(255, 255, 255, 0.4)',
+ }),
+ hovered: Styles.createTextStyle({
+ color: 'rgb(255, 255, 255)',
+ }),
+ },
+};
+
+interface IProps {
+ onToggle?: (isOpen: boolean) => void;
+ defaultOpen?: boolean;
+ children: string;
+ style?: Types.ViewStyleRuleSet | Types.ViewStyleRuleSet[];
+}
+
+interface IState {
+ isHovered: boolean;
+ isOpen: boolean;
+}
+
+export default class ConnectionInfoDisclosure extends Component<IProps, IState> {
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ isHovered: false,
+ isOpen: props.defaultOpen === true,
+ };
+ }
+
+ public render() {
+ const tintColor = this.state.isHovered ? 'rgb(255, 255, 255)' : 'rgb(255, 255, 255, 0.4)';
+
+ return (
+ <View
+ style={[styles.container, this.props.style]}
+ onMouseEnter={this.onMouseEnter}
+ onMouseLeave={this.onMouseLeave}
+ onPress={this.onToggle}>
+ <Text
+ style={[styles.caption.base, this.state.isHovered ? styles.caption.hovered : undefined]}>
+ {this.props.children}
+ </Text>
+ <ImageView
+ source={this.state.isOpen ? '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 });
+ };
+
+ private onToggle = () => {
+ this.setState(
+ (state) => ({
+ ...state,
+ isOpen: !state.isOpen,
+ }),
+ () => {
+ if (this.props.onToggle) {
+ this.props.onToggle(this.state.isOpen);
+ }
+ },
+ );
+ };
+}
diff --git a/gui/packages/components/src/ImageView.tsx b/gui/packages/components/src/ImageView.tsx
index 9602130a63..d05c72dba7 100644
--- a/gui/packages/components/src/ImageView.tsx
+++ b/gui/packages/components/src/ImageView.tsx
@@ -7,7 +7,7 @@ interface IProps {
height?: number;
tintColor?: string;
tintHoverColor?: string;
- style?: Types.ViewStyleRuleSet;
+ style?: Types.ViewStyleRuleSet | Types.ViewStyleRuleSet[];
disabled?: boolean;
}
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 2ae7b66f9c..a8b0ed8556 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -24,7 +24,7 @@ import accountActions from './redux/account/actions';
import connectionActions from './redux/connection/actions';
import settingsActions from './redux/settings/actions';
import versionActions from './redux/version/actions';
-import windowActions from './redux/window/actions';
+import userInterfaceActions from './redux/userinterface/actions';
import SettingsProxy from './lib/subscription-proxy/settings-proxy';
import TunnelStateProxy from './lib/subscription-proxy/tunnel-state-proxy';
@@ -96,7 +96,7 @@ export default class AppRenderer {
connection: bindActionCreators(connectionActions, dispatch),
settings: bindActionCreators(settingsActions, dispatch),
version: bindActionCreators(versionActions, dispatch),
- window: bindActionCreators(windowActions, dispatch),
+ userInterface: bindActionCreators(userInterfaceActions, dispatch),
history: bindActionCreators(
{
push: pushHistory,
@@ -120,7 +120,7 @@ export default class AppRenderer {
ipcRenderer.on('update-window-shape', (_event, shapeParams: WindowShapeParameters) => {
if (typeof shapeParams.arrowPosition === 'number') {
- this._reduxActions.window.updateWindowArrowPosition(shapeParams.arrowPosition);
+ this._reduxActions.userInterface.updateWindowArrowPosition(shapeParams.arrowPosition);
}
});
diff --git a/gui/packages/desktop/src/renderer/components/Connect.js b/gui/packages/desktop/src/renderer/components/Connect.js
index 5911d1955a..baacce3a8b 100644
--- a/gui/packages/desktop/src/renderer/components/Connect.js
+++ b/gui/packages/desktop/src/renderer/components/Connect.js
@@ -2,24 +2,17 @@
import moment from 'moment';
import * as React from 'react';
-import { Component, Text, View, Types } from 'reactxp';
-import {
- ConnectionInfo,
- SecuredLabel,
- SecuredDisplayStyle,
- SettingsBarButton,
- Brand,
- HeaderBarStyle,
- ImageView,
-} from '@mullvad/components';
+import { Component, View } from 'reactxp';
+import { SettingsBarButton, Brand, HeaderBarStyle, ImageView } from '@mullvad/components';
import { Layout, Container, Header } from './Layout';
import NotificationArea from './NotificationArea';
import * as AppButton from './AppButton';
+import TunnelControl from './TunnelControl';
import Map from './Map';
import styles from './ConnectStyles';
import { NoCreditError, NoInternetError } from '../errors';
-import type { TunnelState, RelayProtocol } from '../lib/daemon-rpc';
+import type { RelayOutAddress, RelayInAddress } from './TunnelControl';
import type { ConnectionReduxState } from '../redux/connection/reducers';
import type { VersionReduxState } from '../redux/version/reducers';
@@ -28,11 +21,13 @@ type Props = {
version: VersionReduxState,
accountExpiry: ?string,
selectedRelayName: string,
+ connectionInfoOpen: boolean,
onSettings: () => void,
onSelectLocation: () => void,
onConnect: () => void,
onDisconnect: () => void,
onExternalLink: (type: string) => void,
+ onToggleConnectionInfo: (boolean) => void,
};
export default class Connect extends Component<Props> {
@@ -177,11 +172,13 @@ export default class Connect extends Component<Props> {
city={this.props.connection.city}
country={this.props.connection.country}
hostname={this.props.connection.hostname}
+ defaultConnectionInfoOpen={this.props.connectionInfoOpen}
relayInAddress={relayInAddress}
relayOutAddress={relayOutAddress}
onConnect={this.props.onConnect}
onDisconnect={this.props.onDisconnect}
onSelectLocation={this.props.onSelectLocation}
+ onToggleConnectionInfo={this.props.onToggleConnectionInfo}
/>
<NotificationArea
@@ -236,202 +233,3 @@ export default class Connect extends Component<Props> {
return null;
}
}
-
-type RelayInAddress = {
- ip: string,
- port: number,
- protocol: RelayProtocol,
-};
-
-type RelayOutAddress = {
- ipv4: ?string,
- ipv6: ?string,
-};
-
-type TunnelControlProps = {
- tunnelState: TunnelState,
- selectedRelayName: string,
- city: ?string,
- country: ?string,
- hostname: ?string,
- relayInAddress: ?RelayInAddress,
- relayOutAddress: ?RelayOutAddress,
- onConnect: () => void,
- onDisconnect: () => void,
- onSelectLocation: () => void,
-};
-
-type TunnelControlState = {
- showConnectionInfo: boolean,
-};
-
-class TunnelControl extends Component<TunnelControlProps, TunnelControlState> {
- state = {
- showConnectionInfo: false,
- };
-
- render() {
- const Location = ({ children }) => <View style={styles.status_location}>{children}</View>;
- const City = () => <Text style={styles.status_location_text}>{this.props.city}</Text>;
- const Country = () => <Text style={styles.status_location_text}>{this.props.country}</Text>;
- const Hostname = () => <Text style={styles.status_hostname}>{this.props.hostname || ''}</Text>;
-
- const SwitchLocation = () => {
- return (
- <AppButton.TransparentButton
- style={styles.switch_location_button}
- onPress={this.props.onSelectLocation}>
- <AppButton.Label>{'Switch location'}</AppButton.Label>
- </AppButton.TransparentButton>
- );
- };
-
- const SelectedLocation = () => (
- <AppButton.TransparentButton
- style={styles.switch_location_button}
- onPress={this.props.onSelectLocation}>
- <AppButton.Label>{this.props.selectedRelayName}</AppButton.Label>
- <ImageView height={12} width={7} source="icon-chevron" />
- </AppButton.TransparentButton>
- );
-
- const Connect = () => (
- <AppButton.GreenButton onPress={this.props.onConnect}>
- {'Secure my connection'}
- </AppButton.GreenButton>
- );
-
- const Disconnect = () => (
- <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
- {'Disconnect'}
- </AppButton.RedTransparentButton>
- );
-
- const Cancel = () => (
- <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
- {'Cancel'}
- </AppButton.RedTransparentButton>
- );
-
- const Secured = ({ displayStyle }) => (
- <SecuredLabel style={styles.status_security} displayStyle={displayStyle} />
- );
- const Footer = ({ children }) => <View style={styles.footer}>{children}</View>;
-
- const ConnectionDetails = () => {
- return (
- <ConnectionInfo
- inAddress={this.props.relayInAddress}
- outAddress={this.props.relayOutAddress}
- isExpanded={this.state.showConnectionInfo}
- onToggle={() => {
- this.setState((state) => ({ ...state, showConnectionInfo: !state.showConnectionInfo }));
- }}
- />
- );
- };
-
- switch (this.props.tunnelState) {
- case 'connecting':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.securing} />
- <Location>
- <City />
- <Country />
- </Location>
- <Hostname />
- <ConnectionDetails />
- </Body>
- <Footer>
- <SwitchLocation />
- <Cancel />
- </Footer>
- </Wrapper>
- );
- case 'connected':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.secured} />
- <Location>
- <City />
- <Country />
- </Location>
- <Hostname />
- <ConnectionDetails />
- </Body>
- <Footer>
- <SwitchLocation />
- <Disconnect />
- </Footer>
- </Wrapper>
- );
-
- case 'blocked':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.blocked} />
- </Body>
- <Footer>
- <SwitchLocation />
- <Cancel />
- </Footer>
- </Wrapper>
- );
-
- case 'disconnecting':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.secured} />
- <Location>
- <Country />
- </Location>
- </Body>
- <Footer>
- <SelectedLocation />
- <Connect />
- </Footer>
- </Wrapper>
- );
-
- case 'disconnected':
- return (
- <Wrapper>
- <Body>
- <Secured displayStyle={SecuredDisplayStyle.unsecured} />
- <Location>
- <Country />
- </Location>
- </Body>
- <Footer>
- <SelectedLocation />
- <Connect />
- </Footer>
- </Wrapper>
- );
-
- default:
- throw new Error(`Unknown TunnelState: ${(this.props.tunnelState: empty)}`);
- }
- }
-}
-
-type ContainerProps = {
- children?: Types.ReactNode,
-};
-
-class Wrapper extends Component<ContainerProps> {
- render() {
- return <View style={styles.tunnel_control}>{this.props.children}</View>;
- }
-}
-
-class Body extends Component<ContainerProps> {
- render() {
- return <View style={styles.body}>{this.props.children}</View>;
- }
-}
diff --git a/gui/packages/desktop/src/renderer/components/ConnectStyles.js b/gui/packages/desktop/src/renderer/components/ConnectStyles.js
index 1af5c3ed16..a114687457 100644
--- a/gui/packages/desktop/src/renderer/components/ConnectStyles.js
+++ b/gui/packages/desktop/src/renderer/components/ConnectStyles.js
@@ -7,9 +7,6 @@ export default {
connect: Styles.createViewStyle({
flex: 1,
}),
- tunnel_control: Styles.createViewStyle({
- flex: 1,
- }),
map: Styles.createViewStyle({
position: 'absolute',
top: 0,
@@ -20,12 +17,6 @@ export default {
height: '100%',
width: '100%',
}),
- container: Styles.createViewStyle({
- flexDirection: 'column',
- flex: 1,
- position: 'relative' /* need this for z-index to work to cover map */,
- zIndex: 1,
- }),
body: Styles.createViewStyle({
paddingTop: 0,
paddingLeft: 24,
@@ -34,11 +25,11 @@ export default {
marginTop: 186,
flex: 1,
}),
- footer: Styles.createViewStyle({
- flex: 0,
- paddingBottom: 16,
- paddingLeft: 24,
- paddingRight: 24,
+ container: Styles.createViewStyle({
+ flexDirection: 'column',
+ flex: 1,
+ position: 'relative' /* need this for z-index to work to cover map */,
+ zIndex: 1,
}),
status_icon: Styles.createViewStyle({
position: 'absolute',
@@ -47,9 +38,6 @@ export default {
height: 60,
marginTop: 94,
}),
- switch_location_button: Styles.createViewStyle({
- marginBottom: 16,
- }),
notification_area: Styles.createViewStyle({
width: '100%',
position: 'absolute',
@@ -69,31 +57,4 @@ export default {
color: colors.white,
marginBottom: 24,
}),
- status_security: Styles.createTextStyle({
- fontFamily: 'Open Sans',
- fontSize: 16,
- fontWeight: '800',
- lineHeight: 22,
- marginBottom: 4,
- }),
- status_hostname: Styles.createTextStyle({
- fontFamily: 'Open Sans',
- fontSize: 16,
- fontWeight: '800',
- color: colors.white,
- paddingBottom: 2,
- }),
- status_location: Styles.createTextStyle({
- flexDirection: 'column',
- marginBottom: 4,
- }),
- status_location_text: Styles.createTextStyle({
- fontFamily: 'DINPro',
- fontSize: 38,
- fontWeight: '900',
- lineHeight: 40,
- overflow: 'hidden',
- letterSpacing: -0.9,
- color: colors.white,
- }),
};
diff --git a/gui/packages/desktop/src/renderer/components/SupportStyles.js b/gui/packages/desktop/src/renderer/components/SupportStyles.js
index 499ca2aa9c..e3b8491862 100644
--- a/gui/packages/desktop/src/renderer/components/SupportStyles.js
+++ b/gui/packages/desktop/src/renderer/components/SupportStyles.js
@@ -113,10 +113,8 @@ export default {
}),
support__send_status: Styles.createTextStyle({
fontFamily: 'DINPro',
- fontSize: 38,
+ fontSize: 34,
fontWeight: '900',
- maxHeight: 'calc(1.16em * 2)',
- overflow: 'visible',
letterSpacing: -0.9,
color: colors.white,
marginBottom: 4,
diff --git a/gui/packages/desktop/src/renderer/components/TunnelControl.js b/gui/packages/desktop/src/renderer/components/TunnelControl.js
new file mode 100644
index 0000000000..b8e0a45b98
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/components/TunnelControl.js
@@ -0,0 +1,239 @@
+// @flow
+
+import * as React from 'react';
+import { Component, Text, View, Styles, Types } from 'reactxp';
+import { ConnectionInfo, SecuredLabel, SecuredDisplayStyle, ImageView } from '@mullvad/components';
+import * as AppButton from './AppButton';
+import { colors } from '../../config';
+
+import type { TunnelState, RelayProtocol } from '../lib/daemon-rpc';
+
+export type RelayInAddress = {
+ ip: string,
+ port: number,
+ protocol: RelayProtocol,
+};
+
+export type RelayOutAddress = {
+ ipv4: ?string,
+ ipv6: ?string,
+};
+
+type TunnelControlProps = {
+ tunnelState: TunnelState,
+ selectedRelayName: string,
+ city: ?string,
+ country: ?string,
+ hostname: ?string,
+ defaultConnectionInfoOpen?: boolean,
+ relayInAddress: ?RelayInAddress,
+ relayOutAddress: ?RelayOutAddress,
+ onConnect: () => void,
+ onDisconnect: () => void,
+ onSelectLocation: () => void,
+ onToggleConnectionInfo: (boolean) => void,
+};
+
+const styles = {
+ body: Styles.createViewStyle({
+ paddingTop: 0,
+ paddingLeft: 24,
+ paddingRight: 24,
+ paddingBottom: 0,
+ marginTop: 186,
+ flex: 1,
+ }),
+ footer: Styles.createViewStyle({
+ flex: 0,
+ paddingBottom: 16,
+ paddingLeft: 24,
+ paddingRight: 24,
+ }),
+ wrapper: Styles.createViewStyle({
+ flex: 1,
+ }),
+ switch_location_button: Styles.createViewStyle({
+ marginBottom: 16,
+ }),
+ status_security: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 16,
+ fontWeight: '800',
+ lineHeight: 22,
+ marginBottom: 4,
+ }),
+ status_location: Styles.createTextStyle({
+ flexDirection: 'column',
+ marginBottom: 4,
+ }),
+ status_location_text: Styles.createTextStyle({
+ fontFamily: 'DINPro',
+ fontSize: 34,
+ lineHeight: 36,
+ fontWeight: '900',
+ overflow: 'hidden',
+ letterSpacing: -0.9,
+ color: colors.white,
+ }),
+};
+
+export default class TunnelControl extends Component<TunnelControlProps> {
+ render() {
+ const Location = ({ children }) => <View style={styles.status_location}>{children}</View>;
+ const City = () => <Text style={styles.status_location_text}>{this.props.city}</Text>;
+ const Country = () => <Text style={styles.status_location_text}>{this.props.country}</Text>;
+
+ const SwitchLocation = () => {
+ return (
+ <AppButton.TransparentButton
+ style={styles.switch_location_button}
+ onPress={this.props.onSelectLocation}>
+ <AppButton.Label>{'Switch location'}</AppButton.Label>
+ </AppButton.TransparentButton>
+ );
+ };
+
+ const SelectedLocation = () => (
+ <AppButton.TransparentButton
+ style={styles.switch_location_button}
+ onPress={this.props.onSelectLocation}>
+ <AppButton.Label>{this.props.selectedRelayName}</AppButton.Label>
+ <ImageView height={12} width={7} source="icon-chevron" />
+ </AppButton.TransparentButton>
+ );
+
+ const Connect = () => (
+ <AppButton.GreenButton onPress={this.props.onConnect}>
+ {'Secure my connection'}
+ </AppButton.GreenButton>
+ );
+
+ const Disconnect = () => (
+ <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
+ {'Disconnect'}
+ </AppButton.RedTransparentButton>
+ );
+
+ const Cancel = () => (
+ <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
+ {'Cancel'}
+ </AppButton.RedTransparentButton>
+ );
+
+ const Secured = ({ displayStyle }) => (
+ <SecuredLabel style={styles.status_security} displayStyle={displayStyle} />
+ );
+ const Footer = ({ children }) => <View style={styles.footer}>{children}</View>;
+
+ const connectionDetails = (
+ <ConnectionInfo
+ hostname={this.props.hostname}
+ inAddress={this.props.relayInAddress}
+ outAddress={this.props.relayOutAddress}
+ defaultOpen={this.props.defaultConnectionInfoOpen}
+ onToggle={this.props.onToggleConnectionInfo}
+ />
+ );
+
+ switch (this.props.tunnelState) {
+ case 'connecting':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.securing} />
+ <Location>
+ <City />
+ <Country />
+ </Location>
+ {connectionDetails}
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Cancel />
+ </Footer>
+ </Wrapper>
+ );
+ case 'connected':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.secured} />
+ <Location>
+ <City />
+ <Country />
+ </Location>
+ {connectionDetails}
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Disconnect />
+ </Footer>
+ </Wrapper>
+ );
+
+ case 'blocked':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.blocked} />
+ </Body>
+ <Footer>
+ <SwitchLocation />
+ <Cancel />
+ </Footer>
+ </Wrapper>
+ );
+
+ case 'disconnecting':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.secured} />
+ <Location>
+ <Country />
+ </Location>
+ </Body>
+ <Footer>
+ <SelectedLocation />
+ <Connect />
+ </Footer>
+ </Wrapper>
+ );
+
+ case 'disconnected':
+ return (
+ <Wrapper>
+ <Body>
+ <Secured displayStyle={SecuredDisplayStyle.unsecured} />
+ <Location>
+ <Country />
+ </Location>
+ </Body>
+ <Footer>
+ <SelectedLocation />
+ <Connect />
+ </Footer>
+ </Wrapper>
+ );
+
+ default:
+ throw new Error(`Unknown TunnelState: ${(this.props.tunnelState: empty)}`);
+ }
+ }
+}
+
+type ContainerProps = {
+ children?: Types.ReactNode,
+};
+
+class Wrapper extends Component<ContainerProps> {
+ render() {
+ return <View style={styles.wrapper}>{this.props.children}</View>;
+ }
+}
+
+class Body extends Component<ContainerProps> {
+ render() {
+ return <View style={styles.body}>{this.props.children}</View>;
+ }
+}
diff --git a/gui/packages/desktop/src/renderer/containers/ConnectPage.js b/gui/packages/desktop/src/renderer/containers/ConnectPage.js
index 25e0cbfc61..fbd7b8aa6b 100644
--- a/gui/packages/desktop/src/renderer/containers/ConnectPage.js
+++ b/gui/packages/desktop/src/renderer/containers/ConnectPage.js
@@ -7,6 +7,7 @@ import { bindActionCreators } from 'redux';
import { push } from 'connected-react-router';
import { links } from '../../config';
import Connect from '../components/Connect';
+import userInterfaceActions from '../redux/userinterface/actions';
import type { ReduxState, ReduxDispatch } from '../redux/store';
import type { SharedRouteProps } from '../routes';
@@ -61,13 +62,18 @@ const mapStateToProps = (state: ReduxState) => {
selectedRelayName: getRelayName(state.settings.relaySettings, state.settings.relayLocations),
connection: state.connection,
version: state.version,
+ connectionInfoOpen: state.userInterface.connectionInfoOpen,
};
};
const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+ const userInterface = bindActionCreators(userInterfaceActions, dispatch);
const history = bindActionCreators({ push }, dispatch);
return {
+ onToggleConnectionInfo: (isOpen: boolean) => {
+ userInterface.updateConnectionInfoOpen(isOpen);
+ },
onSettings: () => {
history.push('/settings');
},
diff --git a/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js b/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js
index 4d48b60a95..a8a154572d 100644
--- a/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js
+++ b/gui/packages/desktop/src/renderer/containers/PlatformWindowContainer.js
@@ -7,7 +7,7 @@ import type { ReduxState, ReduxDispatch } from '../redux/store';
import type { SharedRouteProps } from '../routes';
const mapStateToProps = (state: ReduxState) => ({
- arrowPosition: state.window.arrowPosition,
+ arrowPosition: state.userInterface.arrowPosition,
});
const mapDispatchToProps = (_dispatch: ReduxDispatch, _props: SharedRouteProps) => ({});
diff --git a/gui/packages/desktop/src/renderer/redux/store.js b/gui/packages/desktop/src/renderer/redux/store.js
index 961b14d4b6..7f28e94663 100644
--- a/gui/packages/desktop/src/renderer/redux/store.js
+++ b/gui/packages/desktop/src/renderer/redux/store.js
@@ -13,8 +13,8 @@ import supportReducer from './support/reducers';
import supportActions from './support/actions';
import versionReducer from './version/reducers';
import versionActions from './version/actions';
-import windowReducer from './window/reducers';
-import windowActions from './window/actions';
+import userInterfaceReducer from './userinterface/reducers';
+import userInterfaceActions from './userinterface/actions';
import type { Store, StoreEnhancer } from 'redux';
import type { History } from 'history';
@@ -23,14 +23,14 @@ import type { ConnectionReduxState } from './connection/reducers';
import type { SettingsReduxState } from './settings/reducers';
import type { SupportReduxState } from './support/reducers';
import type { VersionReduxState } from './version/reducers';
-import type { WindowReduxState } from './window/reducers';
+import type { UserInterfaceReduxState } from './userinterface/reducers';
import type { AccountAction } from './account/actions';
import type { ConnectionAction } from './connection/actions';
import type { SettingsAction } from './settings/actions';
import type { SupportAction } from './support/actions';
import type { VersionAction } from './version/actions';
-import type { WindowAction } from './window/actions';
+import type { UserInterfaceAction } from './userinterface/actions';
export type ReduxState = {
account: AccountReduxState,
@@ -38,7 +38,7 @@ export type ReduxState = {
settings: SettingsReduxState,
support: SupportReduxState,
version: VersionReduxState,
- window: WindowReduxState,
+ userInterface: UserInterfaceReduxState,
};
export type ReduxAction =
@@ -47,7 +47,7 @@ export type ReduxAction =
| SettingsAction
| SupportAction
| VersionAction
- | WindowAction;
+ | UserInterfaceAction;
export type ReduxStore = Store<ReduxState, ReduxAction, ReduxDispatch>;
export type ReduxGetState = () => ReduxState;
export type ReduxDispatch = (action: ReduxAction) => any;
@@ -64,7 +64,7 @@ export default function configureStore(
...settingsActions,
...supportActions,
...versionActions,
- ...windowActions,
+ ...userInterfaceActions,
pushRoute: (route) => push(route),
replaceRoute: (route) => replace(route),
};
@@ -75,7 +75,7 @@ export default function configureStore(
settings: settingsReducer,
support: supportReducer,
version: versionReducer,
- window: windowReducer,
+ userInterface: userInterfaceReducer,
};
const middlewares = [router];
diff --git a/gui/packages/desktop/src/renderer/redux/userinterface/actions.js b/gui/packages/desktop/src/renderer/redux/userinterface/actions.js
new file mode 100644
index 0000000000..fdc0b2423c
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/redux/userinterface/actions.js
@@ -0,0 +1,29 @@
+// @flow
+
+export type UpdateWindowArrowPositionAction = {
+ type: 'UPDATE_WINDOW_ARROW_POSITION',
+ arrowPosition: number,
+};
+
+export type UpdateConnectionInfoOpenAction = {
+ type: 'UPDATE_CONNECTION_INFO_OPEN',
+ isOpen: boolean,
+};
+
+export type UserInterfaceAction = UpdateWindowArrowPositionAction | UpdateConnectionInfoOpenAction;
+
+function updateWindowArrowPosition(arrowPosition: number): UpdateWindowArrowPositionAction {
+ return {
+ type: 'UPDATE_WINDOW_ARROW_POSITION',
+ arrowPosition,
+ };
+}
+
+function updateConnectionInfoOpen(isOpen: boolean): UpdateConnectionInfoOpenAction {
+ return {
+ type: 'UPDATE_CONNECTION_INFO_OPEN',
+ isOpen,
+ };
+}
+
+export default { updateWindowArrowPosition, updateConnectionInfoOpen };
diff --git a/gui/packages/desktop/src/renderer/redux/userinterface/reducers.js b/gui/packages/desktop/src/renderer/redux/userinterface/reducers.js
new file mode 100644
index 0000000000..68d2e45463
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/redux/userinterface/reducers.js
@@ -0,0 +1,28 @@
+// @flow
+
+import type { ReduxAction } from '../store';
+
+export type UserInterfaceReduxState = {
+ arrowPosition?: number,
+ connectionInfoOpen: boolean,
+};
+
+const initialState: UserInterfaceReduxState = {
+ connectionInfoOpen: false,
+};
+
+export default function(
+ state: UserInterfaceReduxState = initialState,
+ action: ReduxAction,
+): UserInterfaceReduxState {
+ switch (action.type) {
+ case 'UPDATE_WINDOW_ARROW_POSITION':
+ return { ...state, arrowPosition: action.arrowPosition };
+
+ case 'UPDATE_CONNECTION_INFO_OPEN':
+ return { ...state, connectionInfoOpen: action.isOpen };
+
+ default:
+ return state;
+ }
+}
diff --git a/gui/packages/desktop/src/renderer/redux/window/actions.js b/gui/packages/desktop/src/renderer/redux/window/actions.js
deleted file mode 100644
index 3da45e6c65..0000000000
--- a/gui/packages/desktop/src/renderer/redux/window/actions.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// @flow
-
-export type UpdateWindowArrowPositionAction = {
- type: 'UPDATE_WINDOW_ARROW_POSITION',
- arrowPosition: number,
-};
-
-export type WindowAction = UpdateWindowArrowPositionAction;
-
-function updateWindowArrowPosition(arrowPosition: number): UpdateWindowArrowPositionAction {
- return {
- type: 'UPDATE_WINDOW_ARROW_POSITION',
- arrowPosition,
- };
-}
-
-export default { updateWindowArrowPosition };
diff --git a/gui/packages/desktop/src/renderer/redux/window/reducers.js b/gui/packages/desktop/src/renderer/redux/window/reducers.js
deleted file mode 100644
index 91fcfeadd9..0000000000
--- a/gui/packages/desktop/src/renderer/redux/window/reducers.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// @flow
-
-import type { ReduxAction } from '../store';
-
-export type WindowReduxState = {
- arrowPosition?: number,
-};
-
-const initialState: WindowReduxState = {};
-
-export default function(
- state: WindowReduxState = initialState,
- action: ReduxAction,
-): WindowReduxState {
- switch (action.type) {
- case 'UPDATE_WINDOW_ARROW_POSITION':
- return { ...state, arrowPosition: action.arrowPosition };
-
- default:
- return state;
- }
-}