diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2019-02-27 15:15:09 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2019-03-01 16:22:24 +0100 |
| commit | 8fa37db7558debcf6031799826c22d148eac652a (patch) | |
| tree | cb7a36be232f43f4912b8c2dd1a3c5e334efc9e1 /gui/packages/components/src | |
| parent | 6d8f294f21a39a9c3d4d4899ff2b4083718c28ea (diff) | |
| download | mullvadvpn-8fa37db7558debcf6031799826c22d148eac652a.tar.xz mullvadvpn-8fa37db7558debcf6031799826c22d148eac652a.zip | |
Move the components sources to desktop package
Diffstat (limited to 'gui/packages/components/src')
| -rw-r--r-- | gui/packages/components/src/Accordion.tsx | 136 | ||||
| -rw-r--r-- | gui/packages/components/src/ClipboardLabel.tsx | 51 | ||||
| -rw-r--r-- | gui/packages/components/src/ConnectionInfo.tsx | 121 | ||||
| -rw-r--r-- | gui/packages/components/src/ConnectionInfoDisclosure.tsx | 89 | ||||
| -rw-r--r-- | gui/packages/components/src/HeaderBar.tsx | 155 | ||||
| -rw-r--r-- | gui/packages/components/src/ImageView.tsx | 73 | ||||
| -rw-r--r-- | gui/packages/components/src/Modal.tsx | 52 | ||||
| -rw-r--r-- | gui/packages/components/src/SecuredLabel.tsx | 66 | ||||
| -rw-r--r-- | gui/packages/components/src/SettingsHeader.tsx | 53 | ||||
| -rw-r--r-- | gui/packages/components/src/index.ts | 8 |
10 files changed, 0 insertions, 804 deletions
diff --git a/gui/packages/components/src/Accordion.tsx b/gui/packages/components/src/Accordion.tsx deleted file mode 100644 index 19c9ddee56..0000000000 --- a/gui/packages/components/src/Accordion.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import * as React from 'react'; -import { Animated, Component, Styles, Types, UserInterface, View } from 'reactxp'; - -interface IProps { - expanded: boolean; - animationDuration: number; - style?: Types.AnimatedViewStyleRuleSet; - children?: React.ReactNode; -} - -interface IState { - applyAnimatedStyle: boolean; - mountChildren: boolean; -} - -const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' }); - -export default class Accordion extends Component<IProps, IState> { - public static defaultProps = { - expanded: true, - animationDuration: 350, - }; - - public state: IState = { - applyAnimatedStyle: false, - mountChildren: false, - }; - - private heightValue = Animated.createValue(0); - private animatedStyle = Styles.createAnimatedViewStyle({ - height: this.heightValue, - }); - - private containerRef = React.createRef<Animated.View>(); - private contentRef = React.createRef<View>(); - private animation?: Types.Animated.CompositeAnimation = undefined; - - constructor(props: IProps) { - super(props); - - this.state = { - applyAnimatedStyle: !props.expanded, - mountChildren: props.expanded, - }; - } - - public componentWillUnmount() { - if (this.animation) { - this.animation.stop(); - } - } - - public componentDidUpdate(oldProps: IProps, oldState: IState) { - if (this.props.expanded !== oldProps.expanded) { - // make sure the children are mounted first before expanding the accordion - if (this.props.expanded && !this.state.mountChildren) { - this.setState({ mountChildren: true }); - } else { - this.animate(this.props.expanded); - } - } else if (this.state.mountChildren && !oldState.mountChildren) { - // run animations once the children are mounted - this.animate(this.props.expanded); - } - } - - public render() { - const { style, children, expanded, animationDuration, ...otherProps } = this.props; - const containerStyles = this.state.applyAnimatedStyle - ? [style, containerOverflowStyle, this.animatedStyle] - : [style]; - - return ( - <Animated.View {...otherProps} style={containerStyles} ref={this.containerRef}> - <View ref={this.contentRef}>{this.state.mountChildren && children}</View> - </Animated.View> - ); - } - - private async animate(expand: boolean) { - const containerView = this.containerRef.current; - const contentView = this.contentRef.current; - if (!containerView || !contentView) { - return; - } - - if (this.animation) { - this.animation.stop(); - this.animation = undefined; - } - - const containerLayout = await UserInterface.measureLayoutRelativeToWindow(containerView); - const contentLayout = await UserInterface.measureLayoutRelativeToAncestor( - contentView, - containerView, - ); - - // the content is expanded when the animated style is not applied, - // so reset the initial animated value to the current layout's height. - if (!this.state.applyAnimatedStyle) { - this.heightValue.setValue(containerLayout.height); - } - - const toValue = expand ? contentLayout.height : 0; - - // calculate the animation duration based on travel distance - const multiplier = - Math.abs(toValue - containerLayout.height) / Math.max(1, contentLayout.height); - const duration = Math.ceil(this.props.animationDuration * multiplier); - - const animation = Animated.timing(this.heightValue, { - toValue, - easing: Animated.Easing.InOut(), - duration, - useNativeDriver: true, - }); - - this.animation = animation; - - const onAnimationEnd = ({ finished }: Types.Animated.EndResult) => { - if (finished) { - this.animation = undefined; - - // reset the height after transition to let element layout naturally - // if animation finished without interruption - if (expand) { - this.setState({ applyAnimatedStyle: false }); - } - } - }; - - this.setState({ applyAnimatedStyle: true }, () => { - animation.start(onAnimationEnd); - }); - } -} diff --git a/gui/packages/components/src/ClipboardLabel.tsx b/gui/packages/components/src/ClipboardLabel.tsx deleted file mode 100644 index 9cabe96ffd..0000000000 --- a/gui/packages/components/src/ClipboardLabel.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from 'react'; -import { Clipboard, Component, Text, Types } from 'reactxp'; - -interface IProps { - value: string; - delay: number; - message: string; - style?: Types.TextStyleRuleSet; -} - -interface IState { - showsMessage: boolean; -} - -export default class ClipboardLabel extends Component<IProps, IState> { - public static defaultProps: Partial<IProps> = { - delay: 3000, - message: 'Copied!', - }; - - public state: IState = { - showsMessage: false, - }; - - private timer: NodeJS.Timer | null = null; - - public componentWillUnmount() { - if (this.timer) { - clearTimeout(this.timer); - } - } - - public render() { - return ( - <Text style={this.props.style} onPress={this.handlePress}> - {this.state.showsMessage ? this.props.message : this.props.value} - </Text> - ); - } - - private handlePress = () => { - if (this.timer) { - clearTimeout(this.timer); - } - - this.timer = setTimeout(() => this.setState({ showsMessage: false }), this.props.delay); - this.setState({ showsMessage: true }); - - Clipboard.setText(this.props.value); - }; -} diff --git a/gui/packages/components/src/ConnectionInfo.tsx b/gui/packages/components/src/ConnectionInfo.tsx deleted file mode 100644 index 5306ec97a3..0000000000 --- a/gui/packages/components/src/ConnectionInfo.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import * as React from 'react'; -import { Component, Styles, Text, Types, View } from 'reactxp'; -import { default as ConnectionInfoDisclosure } from './ConnectionInfoDisclosure'; - -const styles = { - row: Styles.createViewStyle({ - flexDirection: 'row', - marginTop: 3, - }), - caption: Styles.createTextStyle({ - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - color: 'rgb(255, 255, 255)', - flex: 0, - flexBasis: 30, - marginRight: 8, - }), - 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, - lineHeight: 20, - fontWeight: '600', - color: 'rgb(255, 255, 255)', - flex: 1, - }), -}; - -interface IInAddress { - ip: string; - port: number; - protocol: string; -} - -interface IOutAddress { - ipv4?: string; - ipv6?: string; -} - -interface IProps { - hostname?: string; - inAddress?: IInAddress; - outAddress?: IOutAddress; - defaultOpen?: boolean; - style?: Types.ViewStyleRuleSet | Types.ViewStyleRuleSet[]; - onToggle?: (isOpen: boolean) => void; -} - -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 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 && (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 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 deleted file mode 100644 index 93cd17b1d0..0000000000 --- a/gui/packages/components/src/ConnectionInfoDisclosure.tsx +++ /dev/null @@ -1,89 +0,0 @@ -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/HeaderBar.tsx b/gui/packages/components/src/HeaderBar.tsx deleted file mode 100644 index 50a5aace31..0000000000 --- a/gui/packages/components/src/HeaderBar.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import * as React from 'react'; -import { Button, Component, Styles, Text, Types, View } from 'reactxp'; -import ImageView from './ImageView'; - -export enum HeaderBarStyle { - default = 'default', - defaultDark = 'defaultDark', - error = 'error', - success = 'success', -} - -export interface IHeaderBarProps { - barStyle: HeaderBarStyle; - style?: Types.ViewStyleRuleSet; -} - -const headerBarStyles = { - container: { - base: Styles.createViewStyle({ - paddingTop: 12, - paddingBottom: 12, - paddingLeft: 12, - paddingRight: 12, - }), - platformOverride: { - darwin: Styles.createViewStyle({ - paddingTop: 24, - }), - linux: Styles.createViewStyle({ - // WebKitAppRegion is not standard :/ - // @ts-ignore - WebkitAppRegion: 'drag', - }), - }, - }, - content: Styles.createViewStyle({ - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-end', - // the size of "brand" logo - minHeight: 51, - }), - barStyle: { - default: Styles.createViewStyle({ - backgroundColor: 'rgb(41, 77, 115)', // colors.blue - }), - defaultDark: Styles.createViewStyle({ - backgroundColor: 'rgb(25, 46, 69)', // colors.darkBlue - }), - error: Styles.createViewStyle({ - backgroundColor: 'rgb(208, 2, 27)', // colors.red - }), - success: Styles.createViewStyle({ - backgroundColor: 'rgb(68, 173, 77)', // colors.green - }), - }, -}; - -export default class HeaderBar extends Component<IHeaderBarProps> { - public static defaultProps: IHeaderBarProps = { - barStyle: HeaderBarStyle.default, - }; - - public render() { - const style = [ - headerBarStyles.container.base, - // @ts-ignore - headerBarStyles.container.platformOverride[process.platform], - headerBarStyles.barStyle[this.props.barStyle], - this.props.style, - ]; - - return ( - <View style={style}> - <View style={headerBarStyles.content}>{this.props.children}</View> - </View> - ); - } -} - -const brandStyles = { - container: Styles.createViewStyle({ - flex: 1, - flexDirection: 'row', - alignItems: 'center', - }), - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 24, - fontWeight: '900', - lineHeight: 30, - letterSpacing: -0.5, - color: 'rgba(255, 255, 255, 0.6)', // colors.white60 - marginLeft: 8, - }), -}; - -export class Brand extends Component { - public render() { - return ( - <View style={brandStyles.container}> - <ImageView width={50} height={50} source="logo-icon" /> - <Text style={brandStyles.title}> - { - // TODO: perhaps translate? - 'MULLVAD VPN' - } - </Text> - </View> - ); - } -} - -interface ISettingsButtonProps { - onPress?: () => void; -} - -const settingsBarButtonStyles = { - container: { - base: Styles.createViewStyle({ - cursor: 'default', - padding: 0, - marginLeft: 8, - }), - platformOverride: { - linux: Styles.createViewStyle({ - // WebKitAppRegion is not standard :/ - // @ts-ignore - WebkitAppRegion: 'no-drag', - }), - }, - }, -}; - -export class SettingsBarButton extends Component<ISettingsButtonProps> { - public render() { - return ( - <Button - style={[ - settingsBarButtonStyles.container.base, - // @ts-ignore - settingsBarButtonStyles.container.platformOverride[process.platform], - ]} - onPress={this.props.onPress}> - <ImageView - height={24} - width={24} - source="icon-settings" - tintColor={'rgba(255, 255, 255, 0.6)'} - tintHoverColor={'rgba(255, 255, 255, 0.8)'} - /> - </Button> - ); - } -} diff --git a/gui/packages/components/src/ImageView.tsx b/gui/packages/components/src/ImageView.tsx deleted file mode 100644 index 1f6a90493a..0000000000 --- a/gui/packages/components/src/ImageView.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import * as React from 'react'; -import { Component, Types, View } from 'reactxp'; - -interface IProps { - source: string; - width?: number; - height?: number; - tintColor?: string; - tintHoverColor?: string; - disabled?: boolean; - onPress?: (event: Types.SyntheticEvent) => void; - style?: Types.StyleRuleSetRecursive<Types.ViewStyleRuleSet>; -} - -interface IState { - hovered: boolean; -} - -export default class ImageView extends Component<IProps, IState> { - public state = { hovered: false }; - - public render() { - const { source, width, height, tintColor, tintHoverColor, ...otherProps } = this.props; - const url = `../../assets/images/${source}.svg`; - let image; - - const activeTintColor = (this.state.hovered && tintHoverColor) || tintColor; - - if (activeTintColor) { - const maskWidth = typeof width === 'number' ? `${width}px` : 'auto'; - const maskHeight = typeof height === 'number' ? `${height}px` : 'auto'; - image = ( - <div - style={{ - WebkitMaskImage: `url('${url}')`, - WebkitMaskRepeat: 'no-repeat', - WebkitMaskSize: `${maskWidth} ${maskHeight}`, - backgroundColor: activeTintColor, - lineHeight: 0, - }}> - <img - src={url} - width={width} - height={height} - style={{ - visibility: 'hidden', - }} - /> - </div> - ); - } else { - image = <img src={url} width={width} height={height} />; - } - - return ( - <View {...otherProps} onMouseEnter={this.onHoverStart} onMouseLeave={this.onHoverEnd}> - {image} - </View> - ); - } - - private onHoverStart = () => { - if (!this.props.disabled) { - this.setState({ hovered: true }); - } - }; - - private onHoverEnd = () => { - if (!this.props.disabled) { - this.setState({ hovered: false }); - } - }; -} diff --git a/gui/packages/components/src/Modal.tsx b/gui/packages/components/src/Modal.tsx deleted file mode 100644 index 5742e68f2b..0000000000 --- a/gui/packages/components/src/Modal.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from 'react'; - -export class ModalContent extends React.Component { - public render() { - return ( - <div - style={{ - position: 'absolute', - display: 'flex', - flexDirection: 'column', - flex: 1, - top: 0, - left: 0, - right: 0, - bottom: 0, - }}> - {this.props.children} - </div> - ); - } -} - -export class ModalAlert extends React.Component { - public render() { - return ( - <div - style={{ - backgroundColor: 'rgba(0,0,0,0.5)', - position: 'absolute', - display: 'flex', - flexDirection: 'column', - flex: 1, - top: 0, - left: 0, - right: 0, - bottom: 0, - }}> - {this.props.children} - </div> - ); - } -} - -interface IModalContainerProps { - children?: React.ReactNode; -} - -export class ModalContainer extends React.Component<IModalContainerProps> { - public render() { - return <div style={{ position: 'relative', flex: 1 }}>{this.props.children}</div>; - } -} diff --git a/gui/packages/components/src/SecuredLabel.tsx b/gui/packages/components/src/SecuredLabel.tsx deleted file mode 100644 index 1d33755d11..0000000000 --- a/gui/packages/components/src/SecuredLabel.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react'; -import { Component, Styles, Text, Types } from 'reactxp'; - -export enum SecuredDisplayStyle { - secured, - blocked, - securing, - unsecured, -} - -interface IProps { - displayStyle: SecuredDisplayStyle; - style: Types.TextStyleRuleSet; -} - -const styles = { - securing: Styles.createTextStyle({ - color: 'rgb(255, 255, 255)', // white - }), - secured: Styles.createTextStyle({ - color: 'rgb(68, 173, 77)', // green - }), - unsecured: Styles.createTextStyle({ - color: 'rgb(208, 2, 27)', // red - }), -}; - -export default class SecuredLabel extends Component<IProps> { - public render() { - return <Text style={[this.props.style, this.getTextStyle()]}>{this.getText()}</Text>; - } - - private getText() { - switch (this.props.displayStyle) { - case SecuredDisplayStyle.secured: - // TODO: translate - return 'SECURE CONNECTION'; - - case SecuredDisplayStyle.blocked: - // TODO: translate - return 'BLOCKED CONNECTION'; - - case SecuredDisplayStyle.securing: - // TODO: translate - return 'CREATING SECURE CONNECTION'; - - case SecuredDisplayStyle.unsecured: - // TODO: translate - return 'UNSECURED CONNECTION'; - } - } - - private getTextStyle() { - switch (this.props.displayStyle) { - case SecuredDisplayStyle.secured: - case SecuredDisplayStyle.blocked: - return styles.secured; - - case SecuredDisplayStyle.securing: - return styles.securing; - - case SecuredDisplayStyle.unsecured: - return styles.unsecured; - } - } -} diff --git a/gui/packages/components/src/SettingsHeader.tsx b/gui/packages/components/src/SettingsHeader.tsx deleted file mode 100644 index e099d29a2d..0000000000 --- a/gui/packages/components/src/SettingsHeader.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from 'react'; -import { Component, Styles, Text, Types, View } from 'reactxp'; - -const styles = { - header: { - default: Styles.createViewStyle({ - flex: 0, - paddingTop: 4, - paddingRight: 24, - paddingLeft: 24, - paddingBottom: 24, - }), - }, - title: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 32, - fontWeight: '900', - lineHeight: 40, - color: 'rgb(255, 255, 255)', - }), - subtitle: Styles.createTextStyle({ - marginTop: 4, - fontFamily: 'Open Sans', - fontSize: 13, - fontWeight: '600', - overflow: 'visible', - color: 'rgba(255, 255, 255, 0.8)', // colors.white80 - lineHeight: 20, - letterSpacing: -0.2, - }), -}; - -interface ISettingsHeaderProps { - style?: Types.ViewStyleRuleSet; -} - -export default class SettingsHeader extends Component<ISettingsHeaderProps> { - public render() { - return <View style={[styles.header.default, this.props.style]}>{this.props.children}</View>; - } -} - -export class HeaderTitle extends Component { - public render() { - return <Text style={[styles.title]}>{this.props.children}</Text>; - } -} - -export class HeaderSubTitle extends Component { - public render() { - return <Text style={[styles.subtitle]}>{this.props.children}</Text>; - } -} diff --git a/gui/packages/components/src/index.ts b/gui/packages/components/src/index.ts deleted file mode 100644 index 31c5ee6fec..0000000000 --- a/gui/packages/components/src/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { default as Accordion } from './Accordion'; -export { default as ClipboardLabel } from './ClipboardLabel'; -export { default as ConnectionInfo } from './ConnectionInfo'; -export { default as SecuredLabel, SecuredDisplayStyle } from './SecuredLabel'; -export { default as HeaderBar, HeaderBarStyle, Brand, SettingsBarButton } from './HeaderBar'; -export { default as SettingsHeader, HeaderTitle, HeaderSubTitle } from './SettingsHeader'; -export { default as ImageView } from './ImageView'; -export { ModalContainer, ModalAlert, ModalContent } from './Modal'; |
