Blog/Components/Pages/Rvrb.razor.js 2.7 K · 85 lines · raw · history

1 // The page itself is server-rendered and static. This adds the two things a status page wants that
2 // a snapshot can't carry: times that stay relative to now, and a track counter that keeps running
3 // between refreshes.
4
5 const REFRESH_KEY = "rvrb-auto-refresh";
6 const REFRESH_MS = 30_000;
7
8 wireAutoRefresh();
9 startTicking();
10
11 function wireAutoRefresh() {
12 const checkbox = document.getElementById("autoRefresh");
13 if (!checkbox) return;
14
15 // Read back from storage, since the setting has to survive the very reload it causes.
16 checkbox.checked = localStorage.getItem(REFRESH_KEY) === "on";
17 checkbox.addEventListener("change", () => {
18 localStorage.setItem(REFRESH_KEY, checkbox.checked ? "on" : "off");
19 });
20 }
21
22 function startTicking() {
23 const started = performance.now();
24 const progress = document.getElementById("trackProgress");
25 const elapsedLabel = document.getElementById("trackElapsed");
26 const times = document.querySelectorAll("time[data-relative]");
27 const checkbox = document.getElementById("autoRefresh");
28
29 const tick = () => {
30 const sinceRender = performance.now() - started;
31
32 if (progress) advanceTrack(progress, elapsedLabel, sinceRender);
33 times.forEach(relabel);
34
35 if (checkbox?.checked && sinceRender >= REFRESH_MS) location.reload();
36 };
37
38 tick();
39 setInterval(tick, 1000);
40 }
41
42 // The snapshot says how far into the track the bot was when it was taken, and how long ago that
43 // was; everything after that is just wall clock.
44 function advanceTrack(progress, label, sinceRender) {
45 const duration = Number(progress.dataset.durationMs);
46 if (!duration) return;
47
48 const elapsed = Math.min(
49 Number(progress.dataset.elapsedMs) + Number(progress.dataset.ageMs) + sinceRender,
50 duration);
51
52 progress.value = Math.round((elapsed / duration) * Number(progress.max));
53 if (label) label.textContent = formatDuration(elapsed);
54 }
55
56 function relabel(time) {
57 const at = Date.parse(time.dateTime);
58 if (Number.isNaN(at)) return;
59
60 time.title ||= time.textContent;
61 time.textContent = formatAgo(Date.now() - at);
62 }
63
64 function formatAgo(ms) {
65 if (ms < 0) return "just now";
66
67 const seconds = Math.floor(ms / 1000);
68 if (seconds < 60) return `${seconds}s ago`;
69
70 const minutes = Math.floor(seconds / 60);
71 if (minutes < 60) return `${minutes}m ago`;
72
73 const hours = Math.floor(minutes / 60);
74 if (hours < 24) return `${hours}h ago`;
75
76 const days = Math.floor(hours / 24);
77 return days < 30 ? `${days}d ago` : `${Math.floor(days / 30)}mo ago`;
78 }
79
80 function formatDuration(ms) {
81 const seconds = Math.max(Math.floor(ms / 1000), 0);
82 const minutes = Math.floor(seconds / 60);
83
84 return minutes > 0 ? `${minutes}m ${seconds % 60}s` : `${seconds}s`;
85 }