2020-10-30 20:34:04 +00:00
|
|
|
// Copyright 2019-2020 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2019-06-26 19:33:13 +00:00
|
|
|
import React from 'react';
|
|
|
|
|
2020-08-20 22:08:12 +00:00
|
|
|
export interface Props {
|
2019-06-26 19:33:13 +00:00
|
|
|
duration: number;
|
|
|
|
expiresAt: number;
|
|
|
|
onComplete?: () => unknown;
|
|
|
|
}
|
|
|
|
interface State {
|
|
|
|
ratio: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (
|
2019-05-31 22:42:01 +00:00
|
|
|
<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);
|
|
|
|
}
|