diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-06-01 17:31:30 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-06-01 17:31:30 +0200 |
| commit | 4e03a95e5a21ccea92933ec1c9b6bb07b8bcaf15 (patch) | |
| tree | 9417db8c804a09b59b8915af31723eec0e327cd6 | |
| parent | 9681b2245e75e36ef2896e0f78bc7994689aa89b (diff) | |
| parent | 9f73d666727e9a50d8260fc42e1ec707d0041f32 (diff) | |
| download | mullvadvpn-4e03a95e5a21ccea92933ec1c9b6bb07b8bcaf15.tar.xz mullvadvpn-4e03a95e5a21ccea92933ec1c9b6bb07b8bcaf15.zip | |
Merge branch 'convert-marquee-to-styled-component'
| -rw-r--r-- | gui/src/renderer/components/Marquee.tsx | 119 | ||||
| -rw-r--r-- | gui/src/renderer/components/TunnelControl.tsx | 96 |
2 files changed, 107 insertions, 108 deletions
diff --git a/gui/src/renderer/components/Marquee.tsx b/gui/src/renderer/components/Marquee.tsx index 8dc6724797..d2d5bcbccd 100644 --- a/gui/src/renderer/components/Marquee.tsx +++ b/gui/src/renderer/components/Marquee.tsx @@ -1,82 +1,95 @@ -import * as React from 'react'; -import { Animated, Component, Styles, Types, UserInterface, View } from 'reactxp'; +import React from 'react'; +import styled from 'styled-components'; import { Scheduler } from '../../shared/scheduler'; -const styles = { - text: Styles.createTextStyle({ - // @ts-ignore - width: 'fit-content', +const Container = styled.div({ + overflow: 'hidden', +}); + +const Text = styled.span( + { + position: 'relative', whiteSpace: 'nowrap', + }, + (props: { overflow: number; alignRight: boolean }) => ({ + left: props.alignRight ? -props.overflow + 'px' : '0', + transition: `left linear ${props.overflow * 80}ms`, }), -}; +); interface IMarqueeProps { - style?: Types.StyleRuleSetRecursive<Types.ButtonStyleRuleSet>; + className?: string; + children?: React.ReactNode; +} + +interface IMarqueeState { + alignRight: boolean; + // uniqueKey is used to force the Text component to remount to achieve the initial position of the + // text without using a transition. + uniqueKey: number; } -export default class Marquee extends Component<IMarqueeProps> { - private initialLeft = Animated.createValue(0.0); - private textAnimation = Styles.createAnimatedTextStyle({ left: this.initialLeft }); - private textRef = React.createRef<Animated.Text>(); +export default class Marquee extends React.Component<IMarqueeProps, IMarqueeState> { + private textRef = React.createRef<HTMLSpanElement>(); + private scheduler = new Scheduler(); - private animationScheduler = new Scheduler(); - private animation?: Types.Animated.CompositeAnimation; + public state = { + alignRight: false, + uniqueKey: 0, + }; public componentDidMount() { - this.startAnimation(); + this.startAnimationIfOverflow(); } - public componentDidUpdate() { - this.startAnimation(); + public componentDidUpdate(prevProps: IMarqueeProps) { + if (this.props.children !== prevProps.children) { + this.scheduler.cancel(); + this.setState( + (state) => ({ + alignRight: false, + uniqueKey: state.uniqueKey + 1, + }), + this.startAnimationIfOverflow, + ); + } } public componentWillUnmount() { - this.stopAnimation(); + this.scheduler.cancel(); } public render() { return ( - <View> - <Animated.Text + <Container> + <Text + key={this.state.uniqueKey} ref={this.textRef} - style={[styles.text, this.textAnimation, this.props.style]}> + className={this.props.className} + overflow={this.calculateOverflow()} + alignRight={this.state.alignRight} + onTransitionEnd={this.scheduleToggleAlignRight}> {this.props.children} - </Animated.Text> - </View> + </Text> + </Container> ); } - private startAnimation() { - this.stopAnimation(); - - this.animationScheduler.schedule(async () => { - if (this.textRef.current) { - const textLayout = await UserInterface.measureLayoutRelativeToWindow(this.textRef.current); - const viewLayout = await UserInterface.measureLayoutRelativeToWindow(this); - this.startAnimationImpl(textLayout.width - viewLayout.width, false); - } - }, 1000); - } - - private startAnimationImpl(length: number, reverse: boolean) { - if (length >= 0) { - this.animation = Animated.timing(this.initialLeft, { - toValue: reverse ? 0.0 : -length, - duration: length * 80, - delay: 2000, - easing: Animated.Easing.Linear(), - }); - - this.animation.start(({ finished }) => { - if (finished) { - this.startAnimationImpl(length, !reverse); - } - }); + private startAnimationIfOverflow = () => { + if (this.calculateOverflow() > 0) { + this.scheduleToggleAlignRight(); } - } + }; + + private scheduleToggleAlignRight = () => { + this.scheduler.schedule(() => { + this.setState((state) => ({ alignRight: !state.alignRight })); + }, 2000); + }; - private stopAnimation() { - this.animationScheduler.cancel(); - this.animation?.stop(); + private calculateOverflow() { + const textWidth = this.textRef.current?.offsetWidth ?? 0; + const parentWidth = this.textRef.current?.parentElement?.offsetWidth ?? 0; + return textWidth - parentWidth; } } diff --git a/gui/src/renderer/components/TunnelControl.tsx b/gui/src/renderer/components/TunnelControl.tsx index 7043c900c8..5bf00fb9fa 100644 --- a/gui/src/renderer/components/TunnelControl.tsx +++ b/gui/src/renderer/components/TunnelControl.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Component, Styles, Types, View } from 'reactxp'; +import { Component, Styles, View } from 'reactxp'; import styled from 'styled-components'; import { colors } from '../../config.json'; import { TunnelState } from '../../shared/daemon-rpc-types'; @@ -27,23 +27,12 @@ const SwitchLocationButton = styled(AppButton.TransparentButton)({ }); const styles = { - body: Styles.createViewStyle({ - paddingTop: 0, - paddingLeft: 24, - paddingRight: 24, - paddingBottom: 0, - marginTop: 176, - flex: 1, - }), footer: Styles.createViewStyle({ flex: 0, paddingBottom: 16, paddingLeft: 24, paddingRight: 24, }), - wrapper: Styles.createViewStyle({ - flex: 1, - }), status_security: Styles.createTextStyle({ fontFamily: 'Open Sans', fontSize: 16, @@ -51,31 +40,40 @@ const styles = { lineHeight: 22, marginBottom: 2, }), - status_location: Styles.createTextStyle({ - flexDirection: 'column', - marginBottom: 2, - }), - status_location_text: Styles.createTextStyle({ - fontFamily: 'DINPro', - fontSize: 34, - lineHeight: 38, - fontWeight: '900', - overflow: 'hidden', - letterSpacing: -0.9, - color: colors.white, - }), }; +const Body = styled.div({ + display: 'flex', + flexDirection: 'column', + padding: '0 24px', + marginTop: '176px', + flex: 1, +}); + +const Wrapper = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, +}); + +const Location = styled.div({ + display: 'flex', + flexDirection: 'column', + marginBottom: 2, +}); + +const StyledMarquee = styled(Marquee)({ + fontFamily: 'DINPro', + fontSize: '34px', + lineHeight: '38px', + fontWeight: 900, + overflow: 'hidden', + letterSpacing: -0.9, + color: colors.white, +}); + export default class TunnelControl extends Component<ITunnelControlProps> { public render() { - const Location = ({ children }: { children?: React.ReactNode }) => ( - <View style={styles.status_location}>{children}</View> - ); - const City = () => <Marquee style={styles.status_location_text}>{this.props.city}</Marquee>; - const Country = () => ( - <Marquee style={styles.status_location_text}>{this.props.country}</Marquee> - ); - const SwitchLocation = () => { return ( <SwitchLocationButton onClick={this.props.onSelectLocation}> @@ -153,8 +151,8 @@ export default class TunnelControl extends Component<ITunnelControlProps> { <Body> <Secured displayStyle={SecuredDisplayStyle.securing} /> <Location> - <City /> - <Country /> + {this.renderCity()} + {this.renderCountry()} </Location> <ConnectionPanelContainer /> </Body> @@ -170,8 +168,8 @@ export default class TunnelControl extends Component<ITunnelControlProps> { <Body> <Secured displayStyle={SecuredDisplayStyle.secured} /> <Location> - <City /> - <Country /> + {this.renderCity()} + {this.renderCountry()} </Location> <ConnectionPanelContainer /> </Body> @@ -217,9 +215,7 @@ export default class TunnelControl extends Component<ITunnelControlProps> { <Wrapper> <Body> <Secured displayStyle={SecuredDisplayStyle.secured} /> - <Location> - <Country /> - </Location> + <Location>{this.renderCountry()}</Location> </Body> <Footer> <SelectedLocation /> @@ -233,9 +229,7 @@ export default class TunnelControl extends Component<ITunnelControlProps> { <Wrapper> <Body> <Secured displayStyle={SecuredDisplayStyle.unsecured} /> - <Location> - <Country /> - </Location> + <Location>{this.renderCountry()}</Location> </Body> <Footer> <SelectedLocation /> @@ -248,20 +242,12 @@ export default class TunnelControl extends Component<ITunnelControlProps> { throw new Error(`Unknown TunnelState: ${this.props.tunnelState}`); } } -} - -interface IContainerProps { - children?: Types.ReactNode; -} -class Wrapper extends Component<IContainerProps> { - public render() { - return <View style={styles.wrapper}>{this.props.children}</View>; + private renderCity() { + return <StyledMarquee>{this.props.city}</StyledMarquee>; } -} -class Body extends Component<IContainerProps> { - public render() { - return <View style={styles.body}>{this.props.children}</View>; + private renderCountry() { + return <StyledMarquee>{this.props.country}</StyledMarquee>; } } |
