feat: list streams and story-mode catchup

This commit is contained in:
talksik
2026-02-21 09:46:04 -08:00
parent b5f90709de
commit 0cd74c0a8a
33 changed files with 2304 additions and 41 deletions
@@ -0,0 +1,58 @@
import { useEffect, useRef } from "react";
import type { StreamParticle } from "@/api/types";
import { apiClient } from "@/api/client";
import { useAppStore } from "@/stores/app-store";
import { usePlaybackStore } from "@/stores/playback-store";
import { MediaParticleView } from "./media-particle-view";
import { TextParticleView } from "./text-particle-view";
import { FallbackParticleView } from "./fallback-particle-view";
interface ParticleRendererProps {
particle: StreamParticle;
onNext: () => void;
onPrev: () => void;
}
export function ParticleRenderer({
particle,
onNext,
onPrev,
}: ParticleRendererProps) {
const markParticlesSeen = useAppStore((s) => s.markParticlesSeen);
const markedRef = useRef<string | null>(null);
useEffect(() => {
if (!particle.seen && markedRef.current !== particle.id) {
markedRef.current = particle.id;
markParticlesSeen([particle.id]);
apiClient.markSeen(particle.id).catch(() => {});
}
}, [particle.id, particle.seen, markParticlesSeen]);
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
if (x < 0.3) onPrev();
else if (x > 0.7) onNext();
};
const renderContent = () => {
switch (particle.type) {
case "media":
return <MediaParticleView particle={particle} onEnded={onNext} />;
case "text":
return <TextParticleView particle={particle} />;
default:
return <FallbackParticleView particle={particle} />;
}
};
return (
<div
className="relative flex h-full w-full cursor-pointer items-center justify-center"
onClick={handleClick}
>
{renderContent()}
</div>
);
}