/** * TimelineController - controls time for viewing historical positions */ import { useState, useEffect, useCallback, useRef } from 'react'; export interface TimelineState { currentDate: Date; isPlaying: boolean; speed: number; // days per second startDate: Date; endDate: Date; } interface TimelineControllerProps { onTimeChange: (date: Date) => void; minDate?: Date; maxDate?: Date; } export function TimelineController({ onTimeChange, minDate, maxDate }: TimelineControllerProps) { const startDate = minDate || new Date(Date.now() - 365 * 24 * 60 * 60 * 1000); // 1 year ago const endDate = maxDate || new Date(); const [currentDate, setCurrentDate] = useState(startDate); // Start from minDate instead of maxDate const [isPlaying, setIsPlaying] = useState(false); const [speed, setSpeed] = useState(30); // 30 days per second const animationFrameRef = useRef(); const lastUpdateRef = useRef(Date.now()); // Animation loop useEffect(() => { if (!isPlaying) { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } return; } const animate = () => { const now = Date.now(); const deltaSeconds = (now - lastUpdateRef.current) / 1000; lastUpdateRef.current = now; setCurrentDate((prev) => { const newDate = new Date(prev.getTime() + speed * deltaSeconds * 24 * 60 * 60 * 1000); // Loop back to start if we reach the end if (newDate > endDate) { return new Date(startDate); } return newDate; }); animationFrameRef.current = requestAnimationFrame(animate); }; lastUpdateRef.current = Date.now(); animationFrameRef.current = requestAnimationFrame(animate); return () => { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } }; }, [isPlaying, speed, startDate, endDate]); // Notify parent of time changes useEffect(() => { onTimeChange(currentDate); }, [currentDate, onTimeChange]); const handlePlayPause = useCallback(() => { setIsPlaying((prev) => !prev); }, []); const handleSpeedChange = useCallback((newSpeed: number) => { setSpeed(newSpeed); }, []); const handleSliderChange = useCallback((e: React.ChangeEvent) => { const value = parseFloat(e.target.value); const totalRange = endDate.getTime() - startDate.getTime(); const newDate = new Date(startDate.getTime() + (value / 100) * totalRange); setCurrentDate(newDate); setIsPlaying(false); }, [startDate, endDate]); const currentProgress = ((currentDate.getTime() - startDate.getTime()) / (endDate.getTime() - startDate.getTime())) * 100; return (
时间轴
{currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })}
{/* Progress bar */} {/* Controls */}
{/* Play/Pause button */} {/* Speed control */}
速度:
{/* Reset button */}
); }