kuru-kuru/islands/CounterCard.tsx

121 lines
3.4 KiB
TypeScript
Raw Normal View History

import type { Signal } from "@preact/signals";
import { Button } from "../components/Button.tsx";
import { useEffect, useState } from "preact/hooks";
import axios from "axios-web";
interface SharedProps {
hasClicked: Signal<boolean>;
globalCount: number;
}
/**
* Scrolls the mascot from right to left relative to the viewport
*/
export function animateMascot() {
// create a new element to animate
let id = 0;
const mascotId = Math.floor(Math.random() * 2) + 1;
const scrollSpeed = Math.floor(Math.random() * 30) + 20;
const reversalSpeed= 100 - Math.floor(scrollSpeed);
const counterButton = document.getElementById("ctr-btn") as HTMLElement;
const mascotEl = document.createElement("img");
mascotEl.src = `/assets/img/hertaa${mascotId}.gif`;
mascotEl.style.position = "absolute";
mascotEl.style.right = "-500px";
mascotEl.style.top = counterButton.getClientRects()[0].bottom + scrollY - 430 + "px";
mascotEl.style.zIndex = "-10";
document.body.appendChild(mascotEl);
let pos = -500;
const limit = window.innerWidth + 500;
clearInterval(id);
id = setInterval(() => {
if (pos >= limit) {
clearInterval(id);
mascotEl.remove();
} else {
pos += Math.floor(window.innerWidth/ reversalSpeed);
mascotEl.style.right = pos + "px";
}
}, 12);
}
export default function Counter(props: SharedProps) {
const [count, setCount] = useState(0);
const [globalCount, setGlobalCount] = useState(props.globalCount ?? 0);
const [internalCount, setInternalCount] = useState(0);
const onClick = (evt: MouseEvent) => {
setInternalCount(internalCount + 1);
setCount(count + 1);
animateMascot();
};
useEffect(() => {
// set a timer to update the global count, resetting
// whenever a user activity is detected
let timer: number;
window.onclick = () => {
clearTimeout(timer);
timer = setTimeout(() => {
console.info(
`[${new Date()}] Updating global count: ${internalCount + 1}`,
);
axios.post(
window.location.href,
JSON.stringify({ data: internalCount + 1 }),
);
setInternalCount(0);
}, 5000);
};
});
useEffect(() => {
let es = new EventSource(window.location.href);
es.addEventListener("open", () => {
2023-09-11 04:57:06 +00:00
console.log(`Connected to statistics stream`);
});
es.addEventListener("message", (e) => {
2023-09-11 04:57:06 +00:00
console.log(` Received global count: ${e.data}`);
const data = JSON.parse(e.data);
setGlobalCount(parseInt(data.globalCount));
});
// TODO: Reconnect backoff logic could be improved
es.addEventListener("error", () => {
console.warn(
2023-09-11 04:57:06 +00:00
`Disconnected from statistics stream, attempting to reconnect...`,
);
const backoff = 1000 + Math.random() * 5000;
new Promise((resolve) => setTimeout(resolve, backoff));
es = new EventSource(window.location.href);
});
}, []);
return (
<div class="max-w-sm text-center rounded overflow-hidden">
<div class="px-6 py-4">
<p class="text-3xl">{count}</p>
<p class="text-gray-700 text-base">Times clicked</p>
</div>
<div class="px-6 pt-4 pb-2">
<Button id="ctr-btn" onClick={onClick}>Squish that button</Button>
</div>
<div class="px-6 pt-4 pb-2">
<p>
Everyone has clicked the button {globalCount} times!
</p>
</div>
</div>
);
}