summaryrefslogtreecommitdiffhomepage
path: root/gui/packages
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-08-24 19:46:26 +0300
committerAndrej Mihajlov <and@mullvad.net>2018-08-24 19:46:26 +0300
commit2e599ba1095894e5845d6db09a88df736d48e4f6 (patch)
tree0cb921d1f667d071b7ba3830a420e3273397c645 /gui/packages
parentdbf0d70aa8d69d3300bc56d89b24e839654199e9 (diff)
parent3a9d8e1251562467de443b506a73d56471e84c01 (diff)
downloadmullvadvpn-2e599ba1095894e5845d6db09a88df736d48e4f6.tar.xz
mullvadvpn-2e599ba1095894e5845d6db09a88df736d48e4f6.zip
Merge branch 'typescript-stage1'
Diffstat (limited to 'gui/packages')
-rw-r--r--gui/packages/components/package.json37
-rw-r--r--gui/packages/components/src/Accordion.js141
-rw-r--r--gui/packages/components/src/Accordion.tsx139
-rw-r--r--gui/packages/components/src/index.ts (renamed from gui/packages/components/src/index.js)2
-rw-r--r--gui/packages/components/test/setup.js33
-rw-r--r--gui/packages/components/test/setup.ts10
-rw-r--r--gui/packages/components/test/test-stub.spec.tsx (renamed from gui/packages/components/test/test-stub.spec.js)2
-rw-r--r--gui/packages/components/tsconfig.json18
-rw-r--r--gui/packages/components/tslint.json12
-rw-r--r--gui/packages/desktop/package.json4
-rw-r--r--gui/packages/mobile/package.json2
11 files changed, 202 insertions, 198 deletions
diff --git a/gui/packages/components/package.json b/gui/packages/components/package.json
index 65e66d71a2..d1f78a7fb0 100644
--- a/gui/packages/components/package.json
+++ b/gui/packages/components/package.json
@@ -6,35 +6,38 @@
"private": true,
"scripts": {
"postinstall": "yarn run build",
- "test": "mocha -R spec --require babel-core/register --require \"test/setup.js\" \"test/**/*.spec.js\"",
+ "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",
"private:build:clean": "rimraf build",
- "private:build:compile": "babel src/ --copy-files --out-dir build"
+ "private:build:compile": "tsc"
},
"devDependencies": {
- "babel-cli": "^6.26.0",
- "babel-core": "^6.26.3",
- "babel-plugin-transform-class-properties": "^6.24.1",
- "babel-plugin-transform-object-rest-spread": "^6.26.0",
- "babel-preset-env": "^1.7.0",
- "babel-preset-react": "^6.24.1",
+ "@types/chai": "^4.1.4",
+ "@types/enzyme": "^3.1.13",
+ "@types/enzyme-adapter-react-16": "^1.0.3",
+ "@types/jsdom": "^11.0.6",
+ "@types/mocha": "^5.2.5",
+ "@types/react": "^16.4.11",
"chai": "^4.1.2",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"jsdom": "^11.12.0",
"mocha": "^5.2.0",
"npm-run-all": "^4.1.3",
- "react": "^16.0.0",
- "react-dom": "^16.0.0",
+ "react": "^16.4.0",
+ "react-dom": "^16.4.0",
"reactxp": "^1.3.3",
- "rimraf": "^2.6.2"
- },
- "dependencies": {
- "babel-runtime": "^6.26.0"
+ "rimraf": "^2.6.2",
+ "ts-node": "^7.0.1",
+ "tslint": "^5.11.0",
+ "tslint-config-prettier": "^1.15.0",
+ "typescript": "^3.0.1"
},
+ "dependencies": {},
"peerDependencies": {
- "react": "^16.0.0",
- "react-dom": "^16.0.0",
- "reactxp": "^1.3.2"
+ "react": "^16.4.0",
+ "react-dom": "^16.4.0",
+ "reactxp": "^1.3.3"
}
}
diff --git a/gui/packages/components/src/Accordion.js b/gui/packages/components/src/Accordion.js
deleted file mode 100644
index 6be18b00b1..0000000000
--- a/gui/packages/components/src/Accordion.js
+++ /dev/null
@@ -1,141 +0,0 @@
-// @flow
-
-import * as React from 'react';
-import { Component, View, Styles, Animated, UserInterface } from 'reactxp';
-
-type Props = {
- height: number | 'auto',
- animationDuration?: number,
- children?: React.Node,
-};
-
-type State = {
- animatedValue: ?Animated.Value,
-};
-
-const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' });
-
-export default class Accordion extends Component<Props, State> {
- static defaultProps = {
- height: 'auto',
- animationDuration: 350,
- };
-
- state: State = {
- animatedValue: null,
- animation: null,
- };
-
- _containerView: ?React.Node;
- _contentHeight = 0;
- _animation = (null: ?Animated.CompositeAnimation);
-
- constructor(props: Props) {
- super(props);
-
- // set the initial height if it's known
- if (typeof props.height === 'number') {
- this.state = {
- animatedValue: Animated.createValue(props.height),
- };
- }
- }
-
- componentWillUnmount() {
- if (this._animation) {
- this._animation.stop();
- }
- }
-
- shouldComponentUpdate(nextProps: Props, nextState: State) {
- return (
- nextState.animatedValue !== this.state.animatedValue ||
- nextProps.height !== this.props.height ||
- nextProps.children !== this.props.children
- );
- }
-
- componentDidUpdate(prevProps: Props, _prevState: State) {
- if (prevProps.height !== this.props.height) {
- this._animateHeightChanges();
- }
- }
-
- render() {
- const {
- style: style,
- height: _height,
- children,
- animationDuration: _animationDuration,
- ...otherProps
- } = this.props;
- const containerStyles = [style];
-
- if (this.state.animatedValue !== null) {
- const animatedStyle = Styles.createAnimatedViewStyle({
- height: this.state.animatedValue,
- });
-
- containerStyles.push(containerOverflowStyle, animatedStyle);
- }
-
- return (
- <Animated.View
- {...otherProps}
- style={containerStyles}
- ref={(node) => (this._containerView = node)}>
- <View onLayout={this._contentLayoutDidChange}>{children}</View>
- </Animated.View>
- );
- }
-
- async _animateHeightChanges() {
- const containerView = this._containerView;
- if (!containerView) {
- return;
- }
-
- if (this._animation) {
- this._animation.stop();
- this._animation = null;
- }
-
- try {
- const layout = await UserInterface.measureLayoutRelativeToWindow(containerView);
- const fromValue = this.state.animatedValue || Animated.createValue(layout.height);
- const toValue = this.props.height === 'auto' ? this._contentHeight : this.props.height;
-
- // calculate the animation duration based on travel distance
- const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this._contentHeight);
- const duration = Math.ceil(this.props.animationDuration * multiplier);
-
- const animation = Animated.timing(fromValue, {
- toValue: toValue,
- easing: Animated.Easing.InOut(),
- duration: duration,
- useNativeDriver: true,
- });
-
- this._animation = animation;
- this.setState({ animatedValue: fromValue }, () => {
- animation.start(this._onAnimationEnd);
- });
- } catch (error) {
- // TODO: log error
- }
- }
-
- _onAnimationEnd = ({ finished }) => {
- if (finished) {
- this._animation = null;
-
- // reset height after transition to let element layout naturally
- // if animation finished without interruption
- if (this.props.height === 'auto') {
- this.setState({ animatedValue: null });
- }
- }
- };
-
- _contentLayoutDidChange = ({ height }) => (this._contentHeight = height);
-}
diff --git a/gui/packages/components/src/Accordion.tsx b/gui/packages/components/src/Accordion.tsx
new file mode 100644
index 0000000000..370cce7e28
--- /dev/null
+++ b/gui/packages/components/src/Accordion.tsx
@@ -0,0 +1,139 @@
+import * as React from 'react';
+import { Animated, Component, Styles, Types, UserInterface, View } from 'reactxp';
+
+interface IProps {
+ height: number | 'auto';
+ animationDuration?: number;
+ style?: Types.AnimatedViewStyleRuleSet;
+ children?: React.ReactNode;
+}
+
+interface IState {
+ animatedValue: Animated.Value | null;
+}
+
+const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' });
+
+export default class Accordion extends Component<IProps, IState> {
+ public static defaultProps = {
+ height: 'auto',
+ animationDuration: 350,
+ };
+
+ public state: IState = {
+ animatedValue: null,
+ };
+
+ private containerRef = React.createRef<Animated.View>();
+ private contentHeight = 0;
+ private animation: Types.Animated.CompositeAnimation | null = null;
+
+ constructor(props: IProps) {
+ super(props);
+
+ // set the initial height if it's known
+ if (typeof props.height === 'number') {
+ this.state = {
+ animatedValue: Animated.createValue(props.height),
+ };
+ }
+ }
+
+ public componentWillUnmount() {
+ if (this.animation) {
+ this.animation.stop();
+ }
+ }
+
+ public shouldComponentUpdate(nextProps: IProps, nextState: IState) {
+ return (
+ nextState.animatedValue !== this.state.animatedValue ||
+ nextProps.height !== this.props.height ||
+ nextProps.children !== this.props.children
+ );
+ }
+
+ public componentDidUpdate(prevProps: IProps, prevState: IState) {
+ if (prevProps.height !== this.props.height) {
+ this.animateHeightChanges();
+ }
+ }
+
+ public render() {
+ const { style, height, children, animationDuration, ...otherProps } = this.props;
+ const containerStyles = [style];
+
+ if (this.state.animatedValue !== null) {
+ const animatedStyle = Styles.createAnimatedViewStyle({
+ height: this.state.animatedValue,
+ });
+
+ containerStyles.push(containerOverflowStyle, animatedStyle);
+ }
+
+ return (
+ <Animated.View
+ {...otherProps}
+ style={containerStyles}
+ ref={
+ /* Fix: cast to any because reactxp has out of date annotations
+ See: https://github.com/Microsoft/reactxp/issues/784
+ */
+ this.containerRef as any
+ }>
+ <View onLayout={this.contentLayoutDidChange}>{children}</View>
+ </Animated.View>
+ );
+ }
+
+ private async animateHeightChanges() {
+ const containerView = this.containerRef.current;
+ if (!containerView) {
+ return;
+ }
+
+ if (this.animation) {
+ this.animation.stop();
+ this.animation = null;
+ }
+
+ try {
+ const layout = await UserInterface.measureLayoutRelativeToWindow(containerView);
+ const fromValue = this.state.animatedValue || Animated.createValue(layout.height);
+ const toValue = this.props.height === 'auto' ? this.contentHeight : this.props.height;
+
+ // calculate the animation duration based on travel distance
+ const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this.contentHeight);
+ const duration = Math.ceil(this.props.animationDuration! * multiplier);
+
+ const animation = Animated.timing(fromValue, {
+ toValue,
+ easing: Animated.Easing.InOut(),
+ duration,
+ useNativeDriver: true,
+ });
+
+ this.animation = animation;
+ this.setState({ animatedValue: fromValue }, () => {
+ animation.start(this.onAnimationEnd);
+ });
+ } catch (error) {
+ // TODO: log error
+ }
+ }
+
+ private onAnimationEnd = ({ finished }: Types.Animated.EndResult) => {
+ if (finished) {
+ this.animation = null;
+
+ // reset height after transition to let element layout naturally
+ // if animation finished without interruption
+ if (this.props.height === 'auto') {
+ this.setState({ animatedValue: null });
+ }
+ }
+ };
+
+ private contentLayoutDidChange = ({ height }: Types.ViewOnLayoutEvent) =>
+ (this.contentHeight = height);
+}
diff --git a/gui/packages/components/src/index.js b/gui/packages/components/src/index.ts
index 10cc634888..2a416a4f1d 100644
--- a/gui/packages/components/src/index.js
+++ b/gui/packages/components/src/index.ts
@@ -1,3 +1 @@
-// @flow
-
export { default as Accordion } from './Accordion';
diff --git a/gui/packages/components/test/setup.js b/gui/packages/components/test/setup.js
deleted file mode 100644
index fefae9ebec..0000000000
--- a/gui/packages/components/test/setup.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const { JSDOM } = require('jsdom');
-const Enzyme = require('enzyme');
-const Adapter = require('enzyme-adapter-react-16');
-const chai = require('chai');
-
-Enzyme.configure({
- adapter: new Adapter(),
-});
-
-const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
-const { window } = jsdom;
-
-function copyProps(src, target) {
- const props = Object.getOwnPropertyNames(src)
- .filter((prop) => typeof target[prop] === 'undefined')
- .reduce(
- (result, prop) => ({
- ...result,
- [prop]: Object.getOwnPropertyDescriptor(src, prop),
- }),
- {},
- );
- Object.defineProperties(target, props);
-}
-
-global.window = window;
-global.document = window.document;
-global.navigator = {
- userAgent: 'node.js',
-};
-copyProps(window, global);
-
-global.expect = chai.expect;
diff --git a/gui/packages/components/test/setup.ts b/gui/packages/components/test/setup.ts
new file mode 100644
index 0000000000..fdcc4e49d8
--- /dev/null
+++ b/gui/packages/components/test/setup.ts
@@ -0,0 +1,10 @@
+import { JSDOM } from 'jsdom';
+import * as Enzyme from 'enzyme';
+import * as Adapter from 'enzyme-adapter-react-16';
+import * as chai from 'chai';
+
+Enzyme.configure({
+ adapter: new Adapter(),
+});
+
+const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
diff --git a/gui/packages/components/test/test-stub.spec.js b/gui/packages/components/test/test-stub.spec.tsx
index 3b70b327e9..e90e25e1d5 100644
--- a/gui/packages/components/test/test-stub.spec.js
+++ b/gui/packages/components/test/test-stub.spec.tsx
@@ -1,3 +1 @@
-// @flow
-
describe('No tests', () => {});
diff --git a/gui/packages/components/tsconfig.json b/gui/packages/components/tsconfig.json
new file mode 100644
index 0000000000..9ed6e8e8d9
--- /dev/null
+++ b/gui/packages/components/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "outDir": "./build",
+ "target": "es2017",
+ "module": "commonjs",
+ "jsx": "react",
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": [
+ "./src/*.ts",
+ "./src/*.tsx"
+ ],
+ "exclude": [
+ "node_modules",
+ "test"
+ ]
+}
diff --git a/gui/packages/components/tslint.json b/gui/packages/components/tslint.json
new file mode 100644
index 0000000000..613436b4c3
--- /dev/null
+++ b/gui/packages/components/tslint.json
@@ -0,0 +1,12 @@
+{
+ "defaultSeverity": "error",
+ "extends": [
+ "tslint:latest",
+ "tslint-config-prettier"
+ ],
+ "jsRules": {},
+ "rules": {
+ "object-literal-sort-keys": false
+ },
+ "rulesDirectory": []
+}
diff --git a/gui/packages/desktop/package.json b/gui/packages/desktop/package.json
index d5b7ba89a9..98b6ab3c85 100644
--- a/gui/packages/desktop/package.json
+++ b/gui/packages/desktop/package.json
@@ -22,8 +22,8 @@
"mkdirp": "^0.5.1",
"moment": "^2.20.1",
"rbush": "^2.0.2",
- "react": "^16.0.0",
- "react-dom": "^16.0.0",
+ "react": "^16.4.0",
+ "react-dom": "^16.4.0",
"react-redux": "^5.0.7",
"react-router": "^4.3.1",
"react-simple-maps": "^0.10.1",
diff --git a/gui/packages/mobile/package.json b/gui/packages/mobile/package.json
index 16fdfad070..5bae95069f 100644
--- a/gui/packages/mobile/package.json
+++ b/gui/packages/mobile/package.json
@@ -12,7 +12,7 @@
"license": "GPL-3.0",
"dependencies": {
"@mullvad/components": "0.1.0",
- "react": "^16.0.0",
+ "react": "^16.4.0",
"react-native": "^0.56",
"reactxp": "^1.3.3"
},