import { useState, useEffect, useRef, useCallback } from 'react'; import { Play, Pause, SkipBack, SkipForward } from 'lucide-react'; interface TimelinePlayerProps { startDate: string; endDate: string; currentDate: string; onDateChange: (date: string) => void; isPlaying?: boolean; speed?: number; onSpeedChange?: (speed: number) => void; onPlayPause?: (playing: boolean) => void; } const SPEEDS = [0.5, 1, 2, 5, 10]; export function TimelinePlayer({ startDate, endDate, currentDate, onDateChange, isPlaying = false, speed = 1, onSpeedChange, onPlayPause, }: TimelinePlayerProps) { const [playing, setPlaying] = useState(isPlaying); const timerRef = useRef | null>(null); const generateDateRange = useCallback((start: string, end: string) => { const dates: string[] = []; const current = new Date(start); const final = new Date(end); while (current <= final) { dates.push(current.toISOString().split('T')[0]); current.setDate(current.getDate() + 1); } return dates; }, []); const dateRange = generateDateRange(startDate, endDate); const currentIndex = dateRange.indexOf(currentDate); const progress = ((currentIndex + 1) / dateRange.length) * 100; const play = useCallback(() => { setPlaying(true); onPlayPause?.(true); }, [onPlayPause]); const pause = useCallback(() => { setPlaying(false); onPlayPause?.(false); }, [onPlayPause]); const togglePlay = () => { if (playing) { pause(); } else { play(); } }; const goToNext = useCallback(() => { const nextIndex = Math.min(currentIndex + 1, dateRange.length - 1); onDateChange(dateRange[nextIndex]); }, [currentIndex, dateRange, onDateChange]); const goToStart = () => { onDateChange(dateRange[0]); }; useEffect(() => { if (playing) { const interval = 1000 / speed; timerRef.current = setInterval(() => { goToNext(); }, interval); return () => { if (timerRef.current) { clearInterval(timerRef.current); } }; } }, [playing, speed, goToNext]); useEffect(() => { if (currentIndex >= dateRange.length - 1) { pause(); } }, [currentIndex, dateRange.length, pause]); const handleSliderChange = (e: React.ChangeEvent) => { const index = Math.round((Number(e.target.value) / 100) * (dateRange.length - 1)); onDateChange(dateRange[index]); }; const handleSpeedChange = () => { const currentIndex = SPEEDS.indexOf(speed); const nextIndex = (currentIndex + 1) % SPEEDS.length; onSpeedChange?.(SPEEDS[nextIndex]); }; const formatSpeed = (s: number) => { return s >= 1 ? `${s}x` : `${s.toFixed(1)}x`; }; const formatDate = (dateStr: string) => { const date = new Date(dateStr); const today = new Date(); const isToday = date.toDateString() === today.toDateString(); if (isToday) { return `今天 ${date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`; } return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', }); }; return (
{/* Date display */}
{formatDate(currentDate)}
第 {currentIndex + 1} / {dateRange.length} 天
{/* Vertical slider */}
{new Date(startDate).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' })} {new Date(endDate).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' })}
{/* Transport controls */}
{/* Speed */}
); }