signal-desktop/ts/components/Countdown.tsx

111 lines
2.3 KiB
TypeScript
Raw Normal View History

// Copyright 2019-2021 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
2019-06-26 19:33:13 +00:00
import React from 'react';
export type Props = {
2019-06-26 19:33:13 +00:00
duration: number;
expiresAt: number;
onComplete?: () => unknown;
};
type State = {
2019-06-26 19:33:13 +00:00
ratio: number;
};
2019-06-26 19:33:13 +00:00
const CIRCUMFERENCE = 11.013 * 2 * Math.PI;
export class Countdown extends React.Component<Props, State> {
public looping = false;
constructor(props: Props) {
super(props);
const { duration, expiresAt } = this.props;
const ratio = getRatio(expiresAt, duration);
this.state = { ratio };
}
2020-09-12 00:46:52 +00:00
public componentDidMount(): void {
2019-06-26 19:33:13 +00:00
this.startLoop();
}
2020-09-12 00:46:52 +00:00
public componentDidUpdate(): void {
2019-06-26 19:33:13 +00:00
this.startLoop();
}
2020-09-12 00:46:52 +00:00
public componentWillUnmount(): void {
2019-06-26 19:33:13 +00:00
this.stopLoop();
}
2020-09-12 00:46:52 +00:00
public startLoop(): void {
2019-06-26 19:33:13 +00:00
if (this.looping) {
return;
}
this.looping = true;
requestAnimationFrame(this.loop);
}
2020-09-12 00:46:52 +00:00
public stopLoop(): void {
2019-06-26 19:33:13 +00:00
this.looping = false;
}
2020-09-12 00:46:52 +00:00
public loop = (): void => {
2019-06-26 19:33:13 +00:00
const { onComplete, duration, expiresAt } = this.props;
if (!this.looping) {
return;
}
const ratio = getRatio(expiresAt, duration);
this.setState({ ratio });
if (ratio === 1) {
this.looping = false;
if (onComplete) {
onComplete();
}
} else {
requestAnimationFrame(this.loop);
}
};
2020-09-12 00:46:52 +00:00
public render(): JSX.Element {
2019-06-26 19:33:13 +00:00
const { ratio } = this.state;
const strokeDashoffset = ratio * CIRCUMFERENCE;
return (
<div className="module-countdown">
<svg viewBox="0 0 24 24">
<path
d="M12,1 A11,11,0,1,1,1,12,11.013,11.013,0,0,1,12,1Z"
className="module-countdown__back-path"
style={{
strokeDasharray: `${CIRCUMFERENCE}, ${CIRCUMFERENCE}`,
}}
/>
<path
d="M12,1 A11,11,0,1,1,1,12,11.013,11.013,0,0,1,12,1Z"
className="module-countdown__front-path"
style={{
strokeDasharray: `${CIRCUMFERENCE}, ${CIRCUMFERENCE}`,
strokeDashoffset,
}}
/>
</svg>
</div>
2019-06-26 19:33:13 +00:00
);
}
}
function getRatio(expiresAt: number, duration: number) {
const start = expiresAt - duration;
const end = expiresAt;
const now = Date.now();
const totalTime = end - start;
const elapsed = now - start;
return Math.min(Math.max(0, elapsed / totalTime), 1);
}