Live video on the AT Protocol
1import { Text, zero } from "@streamplace/components";
2import * as chrono from "chrono-node";
3import { useEffect, useState } from "react";
4import { View, useWindowDimensions } from "react-native";
5
6interface CountdownProps {
7 from?: string;
8 to?: string;
9 small?: boolean;
10}
11
12interface LabelBoxProps {
13 children: React.ReactNode;
14 small?: boolean;
15}
16
17const LabelBox = ({ children, small }: LabelBoxProps) => {
18 return (
19 <View
20 style={[
21 {
22 borderColor: "white",
23 borderWidth: 0,
24 borderTopWidth: small ? 2 : 4,
25 borderStyle: "solid",
26 },
27 ]}
28 >
29 <Text
30 style={[
31 small ? { fontSize: 18 } : { fontSize: 24 },
32 small ? { lineHeight: 24 } : { lineHeight: 28 },
33 ]}
34 >
35 {children}
36 </Text>
37 </View>
38 );
39};
40
41export function Countdown({ from, to, small }: CountdownProps) {
42 const [now, setNow] = useState(Date.now());
43 const [dest, setDest] = useState<number | null>(null);
44 const { width, height } = useWindowDimensions();
45
46 useEffect(() => {
47 if (from) {
48 const fromDate = chrono.parseDate(from);
49 if (fromDate === null) {
50 throw new Error("could not parse from");
51 }
52 setDest(fromDate.getTime());
53 } else if (to) {
54 const toDate = chrono.parseDate(to);
55 if (toDate === null) {
56 throw new Error("could not parse to");
57 }
58 setDest(toDate.getTime());
59 } else {
60 throw new Error("must provide either from or to");
61 }
62 }, [from, to]);
63
64 useEffect(() => {
65 const tick = () => {
66 if (!running) {
67 return;
68 }
69 requestAnimationFrame(tick);
70 setNow(Date.now());
71 };
72 let running = true;
73 tick();
74 return () => {
75 running = false;
76 };
77 }, []);
78
79 if (dest === null) {
80 return <View />;
81 }
82
83 let diff = Math.abs(dest - now);
84 if (to && now > dest) {
85 diff = 0;
86 } else if (from && now < dest) {
87 diff = 0;
88 }
89 small = small ?? width <= 600;
90 const [years, days, hrs, min, sec, ms] = toLabels(diff);
91
92 const unitStyle = [
93 zero.mx[small ? 2 : 4],
94 zero.flex.values[0],
95 { flexDirection: "column" },
96 ];
97
98 const timeTextStyle = [
99 { fontFamily: "monospace" },
100 small ? { fontSize: 18 } : { fontSize: 128 },
101 small ? { lineHeight: 24 } : { lineHeight: 40 },
102 ];
103
104 return (
105 <View
106 style={[
107 { flexDirection: "row" },
108 { alignSelf: small ? "auto" : "center" },
109 ]}
110 >
111 <View style={[{ flexDirection: "row" }, { justifyContent: "flex-end" }]}>
112 <View style={unitStyle}>
113 <Text style={timeTextStyle}>{years}</Text>
114 <LabelBox small={small}>YEARS</LabelBox>
115 </View>
116 <View style={unitStyle}>
117 <Text style={timeTextStyle}>{days}</Text>
118 <LabelBox small={small}>DAYS</LabelBox>
119 </View>
120 <View style={unitStyle}>
121 <Text style={timeTextStyle}>{hrs}</Text>
122 <LabelBox small={small}>HRS</LabelBox>
123 </View>
124 </View>
125 <View style={[{ flexDirection: "row" }, { justifyContent: "flex-end" }]}>
126 <View style={unitStyle}>
127 <Text style={timeTextStyle}>{min}</Text>
128 <LabelBox small={small}>MIN</LabelBox>
129 </View>
130 <View style={unitStyle}>
131 <Text style={timeTextStyle}>{sec}</Text>
132 <LabelBox small={small}>SEC</LabelBox>
133 </View>
134 <View style={unitStyle}>
135 <Text style={timeTextStyle}>{ms}</Text>
136 <LabelBox small={small}>MS</LabelBox>
137 </View>
138 </View>
139 </View>
140 );
141}
142
143const toLabels = (
144 now: number,
145): [string, string, string, string, string, string] => {
146 const ms = now % 1000;
147 now = Math.floor(now / 1000);
148
149 const sec = now % 60;
150 now = Math.floor(now / 60);
151
152 const min = now % 60;
153 now = Math.floor(now / 60);
154
155 const hrs = now % 24;
156 now = Math.floor(now / 24);
157
158 const days = now % 365;
159 now = Math.floor(now / 365);
160
161 const years = now;
162
163 return [
164 pad(years, 4),
165 pad(days, 3),
166 pad(hrs, 2),
167 pad(min, 2),
168 pad(sec, 2),
169 pad(ms, 3),
170 ];
171};
172
173const pad = (num: number, n: number): string => {
174 let str = `${num}`;
175 while (str.length < n) {
176 str = "0" + str;
177 }
178 return str;
179};