Files
buzz/app/widget/donate/goal/page.tsx
T

104 lines
3.1 KiB
TypeScript

"use client";
import { motion } from "motion/react";
import { useEffect, useRef, useState } from "react";
import { sse } from "@/lib/db/sse-endpoints";
import { pausePass } from "@/lib/utils";
import { getDonateBar } from "./api";
type Data = Awaited<ReturnType<typeof getDonateBar>>;
export default function TopDonateWidget() {
const [bar, setBar] = useState<Data>();
const textRef = useRef<HTMLDivElement | null>(null);
const stableSize = useRef<Record<string, number>>({});
const [resizeTick, setResizeTick] = useState(0);
useEffect(() => {
async function update() {
if (textRef.current)
void getDonateBar().then(pausePass(10000)).then(setBar);
else void getDonateBar().then(setBar);
}
queueMicrotask(update);
const { clean } = sse.donate.subMany({
update,
ping: update,
refresh: () => location.reload(),
});
const backupInterval = setInterval(update, 300000);
return () => {
clean();
clearInterval(backupInterval);
};
}, []);
// the most idiomatic way possible but it works
useEffect(() => {
// oxlint-disable-next-line no-unused-expressions
resizeTick; // trigger re-run on window resize
if (!textRef.current) return;
var fontSize = 48,
count = 0;
const el = textRef.current,
parent = el.parentElement!,
key = `${bar?.goal}`;
if (stableSize.current[key]) {
el.classList.add("opacity-100");
el.style.fontSize = `${stableSize.current[key]}px`;
return;
}
el.classList.remove("opacity-100");
const interval = setInterval(() => {
console.log(el.scrollHeight, parent.clientHeight);
fontSize = (fontSize * parent.clientHeight) / el.scrollHeight;
el.style.fontSize = `${fontSize}px`;
count++;
if (count > 10) {
stableSize.current[key] = fontSize;
el.classList.add("opacity-100");
clearInterval(interval);
}
}, 100);
return () => {
el.classList.add("opacity-100");
clearInterval(interval);
};
}, [bar, resizeTick]);
useEffect(() => {
const onResize = () => {
stableSize.current = {};
setResizeTick((t) => t + 1);
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, [bar]);
return bar ? (
<div className="relative h-full max-h-[20svw] w-full overflow-hidden rounded-full bg-[#252525]">
<div className="absolute inset-2 rounded-full bg-black" />
<motion.div
className="absolute inset-1.5 rounded-full bg-linear-to-b from-[#FD0000] to-[#830000]"
animate={{
width: `calc(${Math.min(1, Number(bar.amount ?? "0") / (bar.goal ?? 1)) * 100}% - ${Math.min(1, Number(bar.amount ?? "0") / (bar.goal ?? 1)) * 12}px)`,
// width: `calc(6px + ${Math.min(1, Number(bar.amount) / bar.goal)} * (100% - 12px))`,
}}
initial={{
width: 0,
}}
/>
<div
ref={textRef}
className="absolute top-1/2 right-8 -translate-y-1/2 pb-1 font-bold opacity-0 transition-opacity"
>
{bar.goal}฿
</div>
</div>
) : (
""
);
}