diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-05-28 10:43:56 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-06-01 10:16:50 +0200 |
| commit | 4521193b909f816ad391c47d06ddfc1464a08a50 (patch) | |
| tree | 4a35d9957d06b931995ca49316d13104d780f60f /gui/src | |
| parent | 9681b2245e75e36ef2896e0f78bc7994689aa89b (diff) | |
| download | mullvadvpn-4521193b909f816ad391c47d06ddfc1464a08a50.tar.xz mullvadvpn-4521193b909f816ad391c47d06ddfc1464a08a50.zip | |
Convert Marquee to a styled component
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/components/Marquee.tsx | 119 | ||||
| -rw-r--r-- | gui/src/renderer/components/TunnelControl.tsx | 25 |
2 files changed, 78 insertions, 66 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..6324ceb304 100644 --- a/gui/src/renderer/components/TunnelControl.tsx +++ b/gui/src/renderer/components/TunnelControl.tsx @@ -55,26 +55,25 @@ const styles = { 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 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 City = () => <StyledMarquee>{this.props.city}</StyledMarquee>; + const Country = () => <StyledMarquee>{this.props.country}</StyledMarquee>; const SwitchLocation = () => { return ( |
