File size: 5,647 Bytes
80bcac8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | 'use client'
import { useAppStore } from '@/store'
import { useConvolution } from '@/hooks/useConvolution'
import { Button, Slider, Card, CardContent } from '@/components/ui'
import { cn } from '@/utils/helpers'
import { Play, Pause, RotateCcw, StepForward, ZoomIn, ZoomOut, Maximize } from 'lucide-react'
export function PlaybackControls() {
const {
convolution,
settings,
startConvolution,
pauseConvolution,
resumeConvolution,
stopConvolution,
updateConvolutionStep,
updateConvolutionProgress,
completeConvolution,
setConvolutionError,
resetConvolution,
setAnimationSpeed,
setZoomLevel,
setPanOffset,
toggleOverlay,
setPadding,
setComparisonMode,
setStepMode,
} = useAppStore()
const { runConvolution, pause, resume, stop, step, reset, isRunning, isPaused, canRun, toggleStepMode } = useConvolution()
const { status, progress, elapsedTime } = convolution
const { animationSpeed, zoomLevel, stepMode } = settings
const isCompleted = status === 'completed'
const isActive = isRunning || isPaused || isCompleted
const formatTime = (ms: number) => {
if (ms < 1000) return `${ms}ms`
return `${(ms / 1000).toFixed(1)}s`
}
return (
<Card className="w-full">
<CardContent className="p-4 space-y-4">
{/* Main Controls */}
<div className="flex items-center gap-2 flex-wrap">
<Button
variant={isRunning ? 'secondary' : 'primary'}
size="lg"
onClick={isRunning ? pause : runConvolution}
disabled={!canRun}
className="min-w-[100px]"
>
{isRunning ? (
<>
<Pause className="h-4 w-4" />
Pause
</>
) : (
<>
<Play className="h-4 w-4" />
{isPaused ? 'Resume' : 'Play'}
</>
)}
</Button>
<Button variant="outline" size="lg" onClick={step} disabled={isRunning || isCompleted}>
<StepForward className="h-4 w-4" />
Step
</Button>
<Button variant="outline" size="lg" onClick={stop} disabled={status === 'idle'}>
<RotateCcw className="h-4 w-4" />
Stop
</Button>
<Button variant="outline" size="lg" onClick={reset} disabled={status === 'idle'}>
<RotateCcw className="h-4 w-4" />
Reset
</Button>
</div>
{/* Speed Control */}
<div>
<label className="flex items-center justify-between text-sm mb-1">
<span>Animation Speed</span>
<span className="font-mono text-blue-600">{animationSpeed.toFixed(1)}x</span>
</label>
<Slider
min={0.1}
max={10}
step={0.1}
value={animationSpeed}
onValueChange={setAnimationSpeed}
showValue
valueFormat={(v: number) => `${v.toFixed(1)}x`}
/>
</div>
{/* Zoom Controls */}
<div>
<label className="flex items-center justify-between text-sm mb-1">
<span>Zoom</span>
<span className="font-mono text-blue-600">{Math.round(zoomLevel * 100)}%</span>
</label>
<Slider
min={0.1}
max={20}
step={0.1}
value={zoomLevel}
onValueChange={setZoomLevel}
showValue
valueFormat={(v: number) => `${Math.round(v * 100)}%`}
/>
<div className="flex gap-2 mt-2">
<Button variant="outline" size="sm" onClick={() => setZoomLevel(Math.min(20, zoomLevel * 1.2))}>
<ZoomIn className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={() => setZoomLevel(Math.max(0.1, zoomLevel / 1.2))}>
<ZoomOut className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={() => { setZoomLevel(1); setPanOffset({ x: 0, y: 0 }) }}>
<Maximize className="h-4 w-4" />
Fit
</Button>
</div>
</div>
{/* Progress */}
{isActive && (
<div className="space-y-2 pt-2 border-t dark:border-gray-700">
<div className="flex items-center justify-between text-sm">
<span>Progress</span>
<span className="font-mono">{Math.round(progress * 100)}%</span>
</div>
<div className="h-2 bg-gray-200 rounded-full overflow-hidden dark:bg-gray-700">
<div
className="h-full bg-blue-600 transition-all duration-300"
style={{ width: `${progress * 100}%` }}
/>
</div>
<div className="flex justify-between text-xs text-gray-500">
<span>Elapsed: {formatTime(elapsedTime)}</span>
<span>{status === 'running' ? 'Running' : status === 'paused' ? 'Paused' : 'Completed'}</span>
</div>
</div>
)}
{/* Step Mode Toggle */}
<div className="flex items-center justify-between pt-2 border-t dark:border-gray-700">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={stepMode}
onChange={(e) => toggleStepMode(e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm">Step Mode (manual advance)</span>
</label>
</div>
</CardContent>
</Card>
)
} |