refactor: organize components flatter
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { Particle } from "@/api/types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type TextParticle = Extract<Particle, { type: "text" }>;
|
||||
|
||||
interface TextParticleViewProps {
|
||||
particle: TextParticle;
|
||||
paused: boolean;
|
||||
onEnded: () => void;
|
||||
onProgress?: (ratio: number) => void;
|
||||
}
|
||||
|
||||
const WORDS_PER_MINUTE = 200;
|
||||
const MIN_DURATION_S = 3;
|
||||
const MAX_DURATION_S = 15;
|
||||
const TICK_MS = 100;
|
||||
|
||||
function computeReadDuration(text: string): number {
|
||||
const wordCount = text.trim().split(/\s+/).length;
|
||||
const seconds = (wordCount / WORDS_PER_MINUTE) * 60;
|
||||
return Math.min(Math.max(seconds, MIN_DURATION_S), MAX_DURATION_S);
|
||||
}
|
||||
|
||||
function getTextStyle(length: number) {
|
||||
if (length < 50) return { size: "text-5xl", weight: "font-semibold" };
|
||||
if (length < 150) return { size: "text-3xl", weight: "font-semibold" };
|
||||
if (length < 300) return { size: "text-2xl", weight: "font-normal" };
|
||||
return { size: "text-lg", weight: "font-normal" };
|
||||
}
|
||||
|
||||
export function TextParticleView({
|
||||
particle,
|
||||
paused,
|
||||
onEnded,
|
||||
onProgress,
|
||||
}: TextParticleViewProps) {
|
||||
const style = getTextStyle(particle.properties.content.length);
|
||||
const durationS = computeReadDuration(particle.properties.content);
|
||||
const elapsedRef = useRef(0);
|
||||
|
||||
// Reset elapsed when particle changes
|
||||
useEffect(() => {
|
||||
elapsedRef.current = 0;
|
||||
}, [particle.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (paused) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
elapsedRef.current += TICK_MS / 1000;
|
||||
const ratio = Math.min(elapsedRef.current / durationS, 1);
|
||||
onProgress?.(ratio);
|
||||
|
||||
if (ratio >= 1) {
|
||||
clearInterval(interval);
|
||||
onEnded();
|
||||
}
|
||||
}, TICK_MS);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [paused, durationS, onEnded, onProgress, particle.id]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gradient-to-b from-black/40 via-black/20 to-black/40 p-8">
|
||||
<p
|
||||
className={cn(
|
||||
"max-w-2xl text-center leading-relaxed text-white",
|
||||
style.size,
|
||||
style.weight,
|
||||
)}
|
||||
>
|
||||
{particle.properties.content}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user