Spaces:
Sleeping
Sleeping
| import React, { useEffect } from 'react'; | |
| import { useGridStore } from '../../store/gridStore'; | |
| import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; | |
| import { Button } from '../ui/button'; | |
| import { Slider } from '../ui/slider'; | |
| import { | |
| Play, | |
| Pause, | |
| SkipBack, | |
| SkipForward, | |
| ChevronsLeft, | |
| ChevronsRight, | |
| Film, | |
| } from 'lucide-react'; | |
| export const PlaybackControls: React.FC = () => { | |
| const { | |
| steps, | |
| currentStep, | |
| isPlaying, | |
| playbackSpeed, | |
| play, | |
| pause, | |
| reset, | |
| nextStep, | |
| prevStep, | |
| setCurrentStep, | |
| setSpeed, | |
| } = useGridStore(); | |
| useEffect(() => { | |
| if (!isPlaying) return; | |
| const interval = setInterval(() => { | |
| nextStep(); | |
| }, playbackSpeed); | |
| return () => clearInterval(interval); | |
| }, [isPlaying, playbackSpeed, nextStep]); | |
| if (steps.length === 0) { | |
| return null; | |
| } | |
| const progress = ((currentStep + 1) / steps.length) * 100; | |
| return ( | |
| <Card> | |
| <CardHeader className="pb-3"> | |
| <CardTitle className="flex items-center gap-2 text-zinc-400 font-medium"> | |
| <Film className="w-4 h-4" /> | |
| Playback | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| {/* Progress bar */} | |
| <div> | |
| <div className="h-1 bg-zinc-800 rounded-full overflow-hidden"> | |
| <div | |
| className="h-full bg-zinc-400 transition-all duration-100" | |
| style={{ width: `${progress}%` }} | |
| /> | |
| </div> | |
| <div className="flex justify-between text-xs text-zinc-600 mt-1 font-mono"> | |
| <span>{currentStep + 1}</span> | |
| <span>{steps.length}</span> | |
| </div> | |
| </div> | |
| {/* Control buttons */} | |
| <div className="flex items-center justify-center gap-1"> | |
| <Button onClick={reset} variant="ghost" size="icon" className="h-8 w-8"> | |
| <ChevronsLeft className="w-4 h-4" /> | |
| </Button> | |
| <Button onClick={prevStep} variant="ghost" size="icon" className="h-8 w-8"> | |
| <SkipBack className="w-4 h-4" /> | |
| </Button> | |
| <Button | |
| onClick={isPlaying ? pause : play} | |
| variant={isPlaying ? 'secondary' : 'primary'} | |
| size="icon" | |
| className="h-9 w-9" | |
| > | |
| {isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />} | |
| </Button> | |
| <Button onClick={nextStep} variant="ghost" size="icon" className="h-8 w-8"> | |
| <SkipForward className="w-4 h-4" /> | |
| </Button> | |
| <Button onClick={() => setCurrentStep(steps.length - 1)} variant="ghost" size="icon" className="h-8 w-8"> | |
| <ChevronsRight className="w-4 h-4" /> | |
| </Button> | |
| </div> | |
| {/* Speed control */} | |
| <Slider | |
| label="Speed" | |
| showValue | |
| min={50} | |
| max={1000} | |
| step={50} | |
| value={playbackSpeed} | |
| onChange={(e) => setSpeed(parseInt(e.target.value))} | |
| /> | |
| {/* Current step info */} | |
| {steps[currentStep] && ( | |
| <div className="grid grid-cols-2 gap-2 text-xs"> | |
| <div className="bg-zinc-800 rounded p-2"> | |
| <span className="text-zinc-600">Position</span> | |
| <p className="text-zinc-300 font-mono"> | |
| ({steps[currentStep].currentNode.x}, {steps[currentStep].currentNode.y}) | |
| </p> | |
| </div> | |
| <div className="bg-zinc-800 rounded p-2"> | |
| <span className="text-zinc-600">Cost</span> | |
| <p className="text-zinc-300 font-mono"> | |
| {steps[currentStep].pathCost} | |
| </p> | |
| </div> | |
| <div className="bg-zinc-800 rounded p-2"> | |
| <span className="text-zinc-600">Frontier</span> | |
| <p className="text-zinc-300 font-mono"> | |
| {steps[currentStep].frontier.length} | |
| </p> | |
| </div> | |
| <div className="bg-zinc-800 rounded p-2"> | |
| <span className="text-zinc-600">Explored</span> | |
| <p className="text-zinc-300 font-mono"> | |
| {steps[currentStep].explored.length} | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| }; | |
| export default PlaybackControls; | |