diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-08-24 19:46:26 +0300 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-08-24 19:46:26 +0300 |
| commit | 2e599ba1095894e5845d6db09a88df736d48e4f6 (patch) | |
| tree | 0cb921d1f667d071b7ba3830a420e3273397c645 /gui/packages | |
| parent | dbf0d70aa8d69d3300bc56d89b24e839654199e9 (diff) | |
| parent | 3a9d8e1251562467de443b506a73d56471e84c01 (diff) | |
| download | mullvadvpn-2e599ba1095894e5845d6db09a88df736d48e4f6.tar.xz mullvadvpn-2e599ba1095894e5845d6db09a88df736d48e4f6.zip | |
Merge branch 'typescript-stage1'
Diffstat (limited to 'gui/packages')
| -rw-r--r-- | gui/packages/components/package.json | 37 | ||||
| -rw-r--r-- | gui/packages/components/src/Accordion.js | 141 | ||||
| -rw-r--r-- | gui/packages/components/src/Accordion.tsx | 139 | ||||
| -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.js | 33 | ||||
| -rw-r--r-- | gui/packages/components/test/setup.ts | 10 | ||||
| -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.json | 18 | ||||
| -rw-r--r-- | gui/packages/components/tslint.json | 12 | ||||
| -rw-r--r-- | gui/packages/desktop/package.json | 4 | ||||
| -rw-r--r-- | gui/packages/mobile/package.json | 2 |
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" }, |
