2023-09-10 12:22:22 +00:00
|
|
|
import { Button } from "../components/Button.tsx";
|
2023-09-11 04:05:53 +00:00
|
|
|
import { useEffect, useState } from "preact/hooks";
|
|
|
|
import axios from "axios-web";
|
2023-09-10 12:22:22 +00:00
|
|
|
|
|
|
|
interface SharedProps {
|
2023-10-22 05:56:36 +00:00
|
|
|
globalCount: bigint;
|
2023-10-22 05:45:44 +00:00
|
|
|
audioFiles: string[];
|
2023-09-10 12:22:22 +00:00
|
|
|
}
|
|
|
|
|
2023-09-19 02:39:17 +00:00
|
|
|
/**
|
|
|
|
* Scrolls the mascot from right to left relative to the viewport
|
|
|
|
*/
|
|
|
|
export function animateMascot() {
|
|
|
|
// create a new element to animate
|
|
|
|
let id = 0;
|
2023-09-19 03:30:07 +00:00
|
|
|
const mascotId = Math.floor(Math.random() * 2) + 1;
|
2023-09-19 02:39:17 +00:00
|
|
|
const scrollSpeed = Math.floor(Math.random() * 30) + 20;
|
2023-09-19 03:30:07 +00:00
|
|
|
const reversalSpeed = 100 - Math.floor(scrollSpeed);
|
2023-09-19 02:39:17 +00:00
|
|
|
const counterButton = document.getElementById("ctr-btn") as HTMLElement;
|
|
|
|
const mascotEl = document.createElement("img");
|
2023-09-19 03:50:56 +00:00
|
|
|
const parentEl = document.getElementById("mascot-tgt") as HTMLElement;
|
2023-09-19 02:39:17 +00:00
|
|
|
|
|
|
|
mascotEl.src = `/assets/img/hertaa${mascotId}.gif`;
|
|
|
|
mascotEl.style.right = "-500px";
|
2023-10-21 13:24:38 +00:00
|
|
|
mascotEl.style.opacity = "60%";
|
|
|
|
mascotEl.style.top = counterButton.getClientRects()[0].top + scrollY -
|
|
|
|
408 + "px";
|
|
|
|
mascotEl.classList.add("z-[0]", "absolute", "bg-scroll");
|
2023-09-19 03:50:56 +00:00
|
|
|
parentEl.appendChild(mascotEl);
|
2023-09-19 02:39:17 +00:00
|
|
|
|
|
|
|
let pos = -500;
|
|
|
|
const limit = window.innerWidth + 500;
|
|
|
|
clearInterval(id);
|
|
|
|
|
|
|
|
id = setInterval(() => {
|
|
|
|
if (pos >= limit) {
|
|
|
|
clearInterval(id);
|
|
|
|
mascotEl.remove();
|
|
|
|
} else {
|
2023-09-19 03:30:07 +00:00
|
|
|
pos += Math.floor(window.innerWidth / reversalSpeed);
|
2023-09-19 02:39:17 +00:00
|
|
|
mascotEl.style.right = pos + "px";
|
|
|
|
}
|
|
|
|
}, 12);
|
|
|
|
}
|
|
|
|
|
2023-09-10 12:22:22 +00:00
|
|
|
export default function Counter(props: SharedProps) {
|
2023-09-11 03:55:06 +00:00
|
|
|
const [count, setCount] = useState(0);
|
2023-10-22 05:50:37 +00:00
|
|
|
const [globalCount, setGlobalCount] = useState(
|
|
|
|
BigInt(props.globalCount ?? 0),
|
|
|
|
);
|
2023-09-11 04:05:53 +00:00
|
|
|
const [internalCount, setInternalCount] = useState(0);
|
2023-09-19 03:30:07 +00:00
|
|
|
const [timer, setTimer] = useState(0);
|
2023-09-11 04:00:53 +00:00
|
|
|
|
2023-09-19 03:50:56 +00:00
|
|
|
const onClick = () => {
|
2023-09-11 04:05:53 +00:00
|
|
|
setInternalCount(internalCount + 1);
|
|
|
|
setCount(count + 1);
|
2023-09-19 02:39:17 +00:00
|
|
|
animateMascot();
|
2023-09-19 05:17:36 +00:00
|
|
|
|
2023-10-22 05:45:44 +00:00
|
|
|
let audioFile =
|
|
|
|
props.audioFiles[Math.floor(Math.random() * props.audioFiles.length)];
|
2023-10-10 04:56:45 +00:00
|
|
|
let lastAudioPlayed = audioFile;
|
|
|
|
const audio = new Audio();
|
2023-10-22 05:45:44 +00:00
|
|
|
|
2023-10-10 04:56:45 +00:00
|
|
|
// Check if the audio file is the same as the last one played
|
|
|
|
// If so, pick another one
|
|
|
|
if (lastAudioPlayed === audioFile) {
|
2023-10-22 05:45:44 +00:00
|
|
|
audioFile =
|
|
|
|
props.audioFiles[Math.floor(Math.random() * props.audioFiles.length)];
|
2023-10-10 04:56:45 +00:00
|
|
|
lastAudioPlayed = audioFile;
|
|
|
|
audio.src = audioFile;
|
|
|
|
} else {
|
|
|
|
audio.src = audioFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
audio.play();
|
2023-09-19 05:17:36 +00:00
|
|
|
|
2023-09-19 03:30:07 +00:00
|
|
|
clearTimeout(timer);
|
|
|
|
setTimer(setTimeout(() => {
|
2023-10-22 05:45:44 +00:00
|
|
|
// guard against numbers that are beyond MAX_SAFE_INTEGER.
|
|
|
|
if (internalCount === Number.MAX_SAFE_INTEGER) {
|
|
|
|
console.warn(
|
|
|
|
"Data too large to be submitted and represented safely. Disposing.",
|
|
|
|
);
|
|
|
|
setCount(0);
|
|
|
|
setInternalCount(0);
|
|
|
|
} else {
|
|
|
|
axios.post(
|
|
|
|
window.location.href,
|
|
|
|
JSON.stringify({ data: internalCount + 1 }),
|
|
|
|
);
|
2023-10-22 05:47:48 +00:00
|
|
|
console.info(
|
|
|
|
`[${new Date()}] Updating global count: ${internalCount + 1}`,
|
|
|
|
);
|
2023-10-22 05:45:44 +00:00
|
|
|
setInternalCount(0);
|
|
|
|
}
|
2023-09-19 03:30:07 +00:00
|
|
|
}, 5000));
|
|
|
|
};
|
2023-09-10 13:05:07 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
let es = new EventSource(window.location.href);
|
|
|
|
|
|
|
|
es.addEventListener("open", () => {
|
2023-10-21 14:02:59 +00:00
|
|
|
console.log(`[${new Date()}] Connected to statistics stream`);
|
2023-09-11 04:05:53 +00:00
|
|
|
});
|
2023-09-10 13:05:07 +00:00
|
|
|
|
|
|
|
es.addEventListener("message", (e) => {
|
2023-10-21 14:02:59 +00:00
|
|
|
console.log(`[${new Date()}] Received global count: ${e.data}`);
|
2023-09-12 06:28:01 +00:00
|
|
|
const data = JSON.parse(e.data);
|
2023-10-22 05:50:37 +00:00
|
|
|
setGlobalCount(BigInt(parseInt(data.globalCount)));
|
2023-09-10 13:05:07 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// TODO: Reconnect backoff logic could be improved
|
|
|
|
es.addEventListener("error", () => {
|
2023-09-11 04:05:53 +00:00
|
|
|
console.warn(
|
2023-10-21 14:06:35 +00:00
|
|
|
`[${new Date()}] Disconnected from statistics stream, attempting to reconnect...`,
|
2023-09-11 04:05:53 +00:00
|
|
|
);
|
2023-09-10 13:05:07 +00:00
|
|
|
const backoff = 1000 + Math.random() * 5000;
|
|
|
|
new Promise((resolve) => setTimeout(resolve, backoff));
|
|
|
|
es = new EventSource(window.location.href);
|
2023-09-11 04:05:53 +00:00
|
|
|
});
|
|
|
|
}, []);
|
2023-09-10 13:05:07 +00:00
|
|
|
|
2023-09-10 12:22:22 +00:00
|
|
|
return (
|
2023-09-19 03:50:56 +00:00
|
|
|
<div class="max-w-sm text-center rounded overflow-hidden z-10">
|
2023-09-10 12:22:22 +00:00
|
|
|
<div class="px-6 py-4">
|
2023-09-19 07:07:42 +00:00
|
|
|
<p class="text-3xl text-white">{count.toLocaleString()}</p>
|
2023-10-21 08:27:08 +00:00
|
|
|
<p class="text-gray-100">Times the kuru was squished~</p>
|
2023-09-10 12:22:22 +00:00
|
|
|
</div>
|
|
|
|
<div class="px-6 pt-4 pb-2">
|
2023-09-19 02:46:31 +00:00
|
|
|
<Button id="ctr-btn" onClick={onClick}>Squish that kuru~</Button>
|
2023-09-10 12:22:22 +00:00
|
|
|
</div>
|
2023-09-19 03:50:56 +00:00
|
|
|
<div class="px-6 pt-4 pb-2 text-white">
|
2023-09-11 04:05:53 +00:00
|
|
|
<p>
|
2023-09-19 07:07:42 +00:00
|
|
|
Everyone has squished the kuru {globalCount.toLocaleString()} times!
|
2023-09-11 04:05:53 +00:00
|
|
|
</p>
|
2023-09-10 12:22:22 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|