summaryrefslogtreecommitdiffhomepage
path: root/gui/packages/components
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-11-02 15:24:15 +0100
committerAndrej Mihajlov <and@mullvad.net>2018-11-06 14:45:01 +0100
commitfcad770e40bd71448533e76803fd2ebd861c11d2 (patch)
tree6367723c9231d2d1fd18d9289a544e2834cd759f /gui/packages/components
parent7e14a4e585861aa3f1f0055f744e55da82be7a59 (diff)
downloadmullvadvpn-fcad770e40bd71448533e76803fd2ebd861c11d2.tar.xz
mullvadvpn-fcad770e40bd71448533e76803fd2ebd861c11d2.zip
Add new connection info view
Diffstat (limited to 'gui/packages/components')
-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
4 files changed, 179 insertions, 41 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;
}