feat: build interactive documentary page with audio player and chapter navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
74
apps/web/src/components/documentary/DocumentaryLayout.tsx
Normal file
74
apps/web/src/components/documentary/DocumentaryLayout.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import type { Documentary, Chapter } from "@afterlife/shared";
|
||||
import { ChapterNav } from "./ChapterNav";
|
||||
import { ChapterContent } from "./ChapterContent";
|
||||
import { AudioPlayer } from "./AudioPlayer";
|
||||
import { ReadingProgress } from "./ReadingProgress";
|
||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||
|
||||
interface DocumentaryLayoutProps {
|
||||
documentary: Documentary;
|
||||
}
|
||||
|
||||
export function DocumentaryLayout({ documentary }: DocumentaryLayoutProps) {
|
||||
const chapters = [...documentary.chapters].sort((a, b) => a.order - b.order);
|
||||
const [activeChapter, setActiveChapter] = useState<Chapter>(chapters[0]);
|
||||
|
||||
const audio = useAudioPlayer();
|
||||
|
||||
useEffect(() => {
|
||||
const audioTracks = chapters
|
||||
.filter((ch) => ch.audioFile)
|
||||
.map((ch) => ({
|
||||
id: ch.id,
|
||||
title: ch.title,
|
||||
url: ch.audioFile!.url,
|
||||
duration: ch.audioDuration ?? 0,
|
||||
}));
|
||||
audio.setTracks(audioTracks);
|
||||
if (audioTracks.length > 0) {
|
||||
audio.loadTrack(0);
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
function handleSelectChapter(chapterId: number, index: number) {
|
||||
const chapter = chapters.find((c) => c.id === chapterId);
|
||||
if (chapter) {
|
||||
setActiveChapter(chapter);
|
||||
const trackIndex = audio.tracks.findIndex((t) => t.id === chapterId);
|
||||
if (trackIndex !== -1) {
|
||||
audio.goToTrack(trackIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReadingProgress />
|
||||
<div className="max-w-7xl mx-auto px-4 py-12 flex gap-8">
|
||||
<ChapterNav
|
||||
chapters={chapters}
|
||||
activeChapterId={activeChapter.id}
|
||||
onSelectChapter={handleSelectChapter}
|
||||
/>
|
||||
<div className="flex-1 pb-24">
|
||||
<ChapterContent chapter={activeChapter} />
|
||||
</div>
|
||||
</div>
|
||||
<AudioPlayer
|
||||
trackTitle={audio.currentTrack?.title ?? null}
|
||||
isPlaying={audio.isPlaying}
|
||||
progress={audio.progress}
|
||||
duration={audio.duration}
|
||||
playbackRate={audio.playbackRate}
|
||||
continuousMode={audio.continuousMode}
|
||||
onToggle={audio.toggle}
|
||||
onSeek={audio.seek}
|
||||
onChangeRate={audio.changeRate}
|
||||
onToggleContinuous={() => audio.setContinuousMode(!audio.continuousMode)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user