mihirmithani commited on
Commit
4ce5238
·
1 Parent(s): 94f3ed9
src/app/page.tsx CHANGED
@@ -1,6 +1,6 @@
1
  'use client'
2
 
3
- import { useState, useCallback } from 'react'
4
  import { useAppStore, useImageStore, useUIStore } from '@/store'
5
  import { KernelSelector } from '@/components/kernel/KernelSelector'
6
  import { PlaybackControls } from '@/components/controls/PlaybackControls'
@@ -22,6 +22,7 @@ import {
22
  Settings,
23
  ChevronLeft,
24
  ChevronRight,
 
25
  } from 'lucide-react'
26
 
27
  export default function Home() {
@@ -42,7 +43,7 @@ export default function Home() {
42
  resetConvolution,
43
  } = useAppStore()
44
 
45
- const { runConvolution, pause, resume, stop, step, reset, isRunning, isPaused, canRun } = useConvolution()
46
  const { fileInputRef, openFileDialog, handleFileSelect } = useImageLoader()
47
  const { handleDragOver, handleDragLeave, handleDrop, isDragging } = useDragAndDrop((image, imageData) => {
48
  setOriginalImage(image, imageData)
@@ -52,11 +53,84 @@ export default function Home() {
52
  const [showComparison, setShowComparison] = useState(false)
53
  const [sidebarWidth, setSidebarWidth] = useState(320)
54
  const [inspectedPosition, setInspectedPosition] = useState<{ x: number; y: number } | null>(null)
 
55
 
56
  const handleInspectClick = useCallback((x: number, y: number) => {
57
  setInspectedPosition({ x, y })
58
  }, [])
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  const handleImageLoad = useCallback((data: { image: HTMLImageElement; imageData: ImageData }) => {
61
  setOriginalImage(data.image, data.imageData)
62
  resetConvolution()
@@ -400,6 +474,72 @@ export default function Home() {
400
  </div>
401
  </div>
402
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  </div>
404
  )
405
  }
 
1
  'use client'
2
 
3
+ import { useState, useCallback, useEffect } from 'react'
4
  import { useAppStore, useImageStore, useUIStore } from '@/store'
5
  import { KernelSelector } from '@/components/kernel/KernelSelector'
6
  import { PlaybackControls } from '@/components/controls/PlaybackControls'
 
22
  Settings,
23
  ChevronLeft,
24
  ChevronRight,
25
+ Keyboard,
26
  } from 'lucide-react'
27
 
28
  export default function Home() {
 
43
  resetConvolution,
44
  } = useAppStore()
45
 
46
+ const { runConvolution, pause, resume, stop, step, reset, isRunning, isPaused, canRun, toggleStepMode } = useConvolution()
47
  const { fileInputRef, openFileDialog, handleFileSelect } = useImageLoader()
48
  const { handleDragOver, handleDragLeave, handleDrop, isDragging } = useDragAndDrop((image, imageData) => {
49
  setOriginalImage(image, imageData)
 
53
  const [showComparison, setShowComparison] = useState(false)
54
  const [sidebarWidth, setSidebarWidth] = useState(320)
55
  const [inspectedPosition, setInspectedPosition] = useState<{ x: number; y: number } | null>(null)
56
+ const [showShortcuts, setShowShortcuts] = useState(false)
57
 
58
  const handleInspectClick = useCallback((x: number, y: number) => {
59
  setInspectedPosition({ x, y })
60
  }, [])
61
 
62
+ // Keyboard shortcuts
63
+ useEffect(() => {
64
+ const handleKeyDown = (e: KeyboardEvent) => {
65
+ // Don't trigger shortcuts when typing in inputs
66
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
67
+ return
68
+ }
69
+
70
+ switch (e.code) {
71
+ case 'Space':
72
+ e.preventDefault()
73
+ if (isRunning) pause()
74
+ else if (canRun) runConvolution()
75
+ else if (isPaused) resume()
76
+ break
77
+ case 'ArrowRight':
78
+ e.preventDefault()
79
+ step()
80
+ break
81
+ case 'ArrowLeft':
82
+ e.preventDefault()
83
+ // Step back not implemented yet
84
+ break
85
+ case 'Equal':
86
+ case 'NumpadAdd':
87
+ if (e.ctrlKey || e.metaKey) {
88
+ e.preventDefault()
89
+ const newZoom = Math.min(20, settings.zoomLevel * 1.2)
90
+ useAppStore.getState().setZoomLevel(newZoom)
91
+ }
92
+ break
93
+ case 'Minus':
94
+ case 'NumpadSubtract':
95
+ if (e.ctrlKey || e.metaKey) {
96
+ e.preventDefault()
97
+ const newZoom = Math.max(0.1, settings.zoomLevel / 1.2)
98
+ useAppStore.getState().setZoomLevel(newZoom)
99
+ }
100
+ break
101
+ case 'Digit0':
102
+ if (e.ctrlKey || e.metaKey) {
103
+ e.preventDefault()
104
+ useAppStore.getState().setZoomLevel(1)
105
+ useAppStore.getState().setPanOffset({ x: 0, y: 0 })
106
+ }
107
+ break
108
+ case 'KeyR':
109
+ if (e.ctrlKey || e.metaKey) {
110
+ e.preventDefault()
111
+ reset()
112
+ resetConvolution()
113
+ }
114
+ break
115
+ case 'KeyS':
116
+ if (e.ctrlKey || e.metaKey) {
117
+ e.preventDefault()
118
+ toggleStepMode(!settings.stepMode)
119
+ }
120
+ break
121
+ case 'Slash':
122
+ if (e.shiftKey) {
123
+ e.preventDefault()
124
+ setShowShortcuts(!showShortcuts)
125
+ }
126
+ break
127
+ }
128
+ }
129
+
130
+ window.addEventListener('keydown', handleKeyDown)
131
+ return () => window.removeEventListener('keydown', handleKeyDown)
132
+ }, [isRunning, isPaused, canRun, settings.zoomLevel, settings.stepMode, runConvolution, pause, resume, step, reset, resetConvolution, toggleStepMode])
133
+
134
  const handleImageLoad = useCallback((data: { image: HTMLImageElement; imageData: ImageData }) => {
135
  setOriginalImage(data.image, data.imageData)
136
  resetConvolution()
 
474
  </div>
475
  </div>
476
  )}
477
+
478
+ {/* Shortcuts Modal */}
479
+ {showShortcuts && (
480
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
481
+ <div className="bg-white dark:bg-gray-800 rounded-lg max-w-md w-full mx-4 max-h-[80vh] overflow-y-auto">
482
+ <div className="p-6">
483
+ <div className="flex items-center justify-between mb-4">
484
+ <h2 className="text-xl font-bold flex items-center gap-2">
485
+ <Keyboard className="h-5 w-5" />
486
+ Keyboard Shortcuts
487
+ </h2>
488
+ <Button variant="ghost" size="icon" onClick={() => setShowShortcuts(false)}>
489
+ <ChevronLeft className="h-5 w-5" />
490
+ </Button>
491
+ </div>
492
+ <div className="space-y-3 text-sm text-gray-600 dark:text-gray-400">
493
+ <div className="p-3 bg-gray-50 dark:bg-gray-800 rounded">
494
+ <h3 className="font-medium mb-2">Playback</h3>
495
+ <dl className="grid grid-cols-2 gap-2 text-xs">
496
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Space</dt>
497
+ <dd>Play / Pause / Resume</dd>
498
+ <dt className="font-mono text-blue-600 dark:text-blue-400">→</dt>
499
+ <dd>Step Forward</dd>
500
+ <dt className="font-mono text-blue-600 dark:text-blue-400">←</dt>
501
+ <dd>Step Backward (future)</dd>
502
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Ctrl+S</dt>
503
+ <dd>Toggle Step Mode</dd>
504
+ </dl>
505
+ </div>
506
+ <div className="p-3 bg-gray-50 dark:bg-gray-800 rounded">
507
+ <h3 className="font-medium mb-2">Zoom & Pan</h3>
508
+ <dl className="grid grid-cols-2 gap-2 text-xs">
509
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Ctrl++ / =</dt>
510
+ <dd>Zoom In</dd>
511
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Ctrl+-</dt>
512
+ <dd>Zoom Out</dd>
513
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Ctrl+0</dt>
514
+ <dd>Reset Zoom & Pan</dd>
515
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Mouse Wheel</dt>
516
+ <dd>Zoom at Cursor</dd>
517
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Drag</dt>
518
+ <dd>Pan Image</dd>
519
+ </dl>
520
+ </div>
521
+ <div className="p-3 bg-gray-50 dark:bg-gray-800 rounded">
522
+ <h3 className="font-medium mb-2">General</h3>
523
+ <dl className="grid grid-cols-2 gap-2 text-xs">
524
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Ctrl+R</dt>
525
+ <dd>Reset All</dd>
526
+ <dt className="font-mono text-blue-600 dark:text-blue-400">Shift+/</dt>
527
+ <dd>Show This Help</dd>
528
+ </dl>
529
+ </div>
530
+ <div className="p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded">
531
+ <p className="text-xs text-blue-800 dark:text-blue-300">
532
+ <strong>Tip:</strong> Shortcuts don't work when typing in input fields. Click canvas to focus.
533
+ </p>
534
+ </div>
535
+ </div>
536
+ <div className="mt-6 flex justify-end">
537
+ <Button onClick={() => setShowShortcuts(false)}>Got it</Button>
538
+ </div>
539
+ </div>
540
+ </div>
541
+ </div>
542
+ )}
543
  </div>
544
  )
545
  }
src/components/kernel/KernelSelector.tsx CHANGED
@@ -176,6 +176,20 @@ export function KernelSelector() {
176
  return { sum, hasNonZero, isNormalized: customKernelNormalize }
177
  }, [customKernelMatrix, customKernelNormalize])
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  return (
180
  <Card className="w-full max-w-2xl">
181
  <div className="flex items-center justify-between p-4 border-b dark:border-gray-700">
@@ -295,7 +309,7 @@ export function KernelSelector() {
295
  Array.from({ length: customKernelSize }, (_, col) => (
296
  <input
297
  key={`${row}-${col}`}
298
- ref={(el) => { if (matrixInputRefs.current[row]) matrixInputRefs.current[row][col] = el }}
299
  type="number"
300
  step="0.1"
301
  value={customKernelMatrix[row]?.[col] ?? 0}
 
176
  return { sum, hasNonZero, isNormalized: customKernelNormalize }
177
  }, [customKernelMatrix, customKernelNormalize])
178
 
179
+ const filteredKernels = PREDEFINED_KERNELS.filter(
180
+ (k) =>
181
+ k.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
182
+ k.category.toLowerCase().includes(searchQuery.toLowerCase()) ||
183
+ k.description.toLowerCase().includes(searchQuery.toLowerCase())
184
+ )
185
+
186
+ const kernelsByCategory = KERNEL_CATEGORIES.map((cat) => ({
187
+ ...cat,
188
+ kernels: getKernelsByCategory(cat.value).filter((k) =>
189
+ k.name.toLowerCase().includes(searchQuery.toLowerCase())
190
+ ),
191
+ }))
192
+
193
  return (
194
  <Card className="w-full max-w-2xl">
195
  <div className="flex items-center justify-between p-4 border-b dark:border-gray-700">
 
309
  Array.from({ length: customKernelSize }, (_, col) => (
310
  <input
311
  key={`${row}-${col}`}
312
+ ref={(el) => { if (el && matrixInputRefs.current[row]) matrixInputRefs.current[row][col] = el }}
313
  type="number"
314
  step="0.1"
315
  value={customKernelMatrix[row]?.[col] ?? 0}