summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-05-28 10:43:56 +0200
committerOskar Nyberg <oskar@mullvad.net>2020-06-01 10:16:50 +0200
commit4521193b909f816ad391c47d06ddfc1464a08a50 (patch)
tree4a35d9957d06b931995ca49316d13104d780f60f /gui/src
parent9681b2245e75e36ef2896e0f78bc7994689aa89b (diff)
downloadmullvadvpn-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.tsx119
-rw-r--r--gui/src/renderer/components/TunnelControl.tsx25
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 (