diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-11-02 15:24:15 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-11-06 14:45:01 +0100 |
| commit | fcad770e40bd71448533e76803fd2ebd861c11d2 (patch) | |
| tree | 6367723c9231d2d1fd18d9289a544e2834cd759f /gui | |
| parent | 7e14a4e585861aa3f1f0055f744e55da82be7a59 (diff) | |
| download | mullvadvpn-fcad770e40bd71448533e76803fd2ebd861c11d2.tar.xz mullvadvpn-fcad770e40bd71448533e76803fd2ebd861c11d2.zip | |
Add new connection info view
Diffstat (limited to 'gui')
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; - } -} |
