mosaicmap/components/timeline.tsx
2025-08-08 07:20:05 +08:00

183 lines
6.7 KiB
TypeScript

'use client'
import React, { useCallback, useEffect } from 'react'
import { Slider } from '@/components/ui/slider'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import {
Play,
Pause,
SkipBack,
SkipForward,
Clock,
Calendar
} from 'lucide-react'
import { format, addDays, subDays, startOfDay } from 'date-fns'
import { cn } from '@/lib/utils'
import { useTimeline } from '@/hooks/use-timeline'
interface TimelineProps {
className?: string
startDate?: Date
endDate?: Date
currentDate?: Date
onDateChange?: (date: Date) => void
onPlay?: () => void
onPause?: () => void
isPlaying?: boolean
speed?: 'slow' | 'normal' | 'fast'
onSpeedChange?: (speed: 'slow' | 'normal' | 'fast') => void
}
export function Timeline({
className,
startDate = subDays(new Date(), 30),
endDate = new Date(),
currentDate = new Date(),
onDateChange,
onPlay,
onPause,
isPlaying = false,
speed = 'normal',
onSpeedChange
}: TimelineProps) {
// 计算时间轴进度 (0-100)
const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24))
const currentDays = Math.ceil((currentDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24))
const progress = Math.max(0, Math.min(100, (currentDays / totalDays) * 100))
const timeline = useTimeline(
{
onDateChange(date) {
console.log(date)
}
}
)
const forward = useCallback(() => {
}, [timeline])
useEffect(() => {
const timer = setInterval(() => {
// setFrameIndex((prev) => (prev + 1) % 10); // 每隔3秒切换一帧
}, 60000);
return () => clearInterval(timer); // 清除定时器
}, []);
const handleSliderChange = useCallback((value: number[]) => {
const newProgress = value[0]
const newDays = Math.round((newProgress / 100) * totalDays)
const newDate = addDays(startDate, newDays)
onDateChange?.(newDate)
timeline.setTime(newDate)
}, [startDate, totalDays, onDateChange, timeline])
const handlePlayPause = useCallback(() => {
if (isPlaying) {
onPause?.()
} else {
onPlay?.()
}
}, [isPlaying, onPlay, onPause])
const handleSkipBack = useCallback(() => {
const newDate = subDays(currentDate, 1)
onDateChange?.(newDate)
timeline.setTime(newDate)
}, [currentDate, onDateChange, timeline])
const handleSkipForward = useCallback(() => {
const newDate = addDays(currentDate, 1)
onDateChange?.(newDate)
timeline.setTime(newDate)
}, [currentDate, onDateChange, timeline])
const speedOptions = [
{ value: 'slow', label: '慢速', interval: 2000 },
{ value: 'normal', label: '正常', interval: 1000 },
{ value: 'fast', label: '快速', interval: 500 }
]
return (
<Card className={cn("w-full max-w-4xl mx-auto", className)}>
<CardContent className="p-4">
<div className="flex flex-col gap-4">
{/* 时间显示 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Calendar className="w-4 h-4" />
<span>{format(startDate, 'yyyy-MM-dd')}</span>
</div>
<div className="flex items-center gap-2 text-sm font-medium">
<Clock className="w-4 h-4" />
<span>{format(currentDate, 'yyyy-MM-dd')}</span>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Calendar className="w-4 h-4" />
<span>{format(endDate, 'yyyy-MM-dd')}</span>
</div>
</div>
{/* 时间轴滑块 */}
<div className="flex items-center gap-4">
<Slider
value={[progress]}
onValueChange={handleSliderChange}
max={100}
step={1}
className="flex-1"
/>
</div>
{/* 控制按钮 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={handleSkipBack}
disabled={currentDate <= startDate}
>
<SkipBack className="w-4 h-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={handlePlayPause}
>
{isPlaying ? (
<Pause className="w-4 h-4" />
) : (
<Play className="w-4 h-4" />
)}
</Button>
<Button
variant="outline"
size="sm"
onClick={handleSkipForward}
disabled={currentDate >= endDate}
>
<SkipForward className="w-4 h-4" />
</Button>
</div>
{/* 速度控制 */}
<div className="flex items-center gap-1">
{speedOptions.map((option) => (
<Button
key={option.value}
variant={speed === option.value ? "default" : "outline"}
size="sm"
onClick={() => onSpeedChange?.(option.value as 'slow' | 'normal' | 'fast')}
className="text-xs px-2"
>
{option.label}
</Button>
))}
</div>
</div>
</div>
</CardContent>
</Card>
)
}