Skip to content

Commit b89a333

Browse files
updt
1 parent 4a6a97b commit b89a333

File tree

2 files changed

+240
-2
lines changed

2 files changed

+240
-2
lines changed

src/components/panels/KeyboardShortcutsPanel.tsx

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Keyboard, X } from "lucide-react";
22
import React, { useEffect, useRef } from "react";
3-
import { useLocation } from "react-router-dom";
3+
import { useLocation, useNavigate } from "react-router-dom";
44

5+
import { useTheme } from "@/providers/ThemeProvider";
56
import { cn } from "@/utils";
67

78
interface KeyboardShortcutsPanelProps {
@@ -14,6 +15,8 @@ export function KeyboardShortcutsPanel({
1415
onClose,
1516
}: KeyboardShortcutsPanelProps) {
1617
const location = useLocation();
18+
const navigate = useNavigate();
19+
const { toggle: toggleTheme } = useTheme();
1720
const modalRef = useRef<HTMLDivElement | null>(null);
1821

1922
// Add click-outside functionality
@@ -44,6 +47,160 @@ export function KeyboardShortcutsPanel({
4447
};
4548
}, [isOpen, onClose]);
4649

50+
// Handle shortcut clicks
51+
const handleShortcutClick = (shortcutKey: string) => {
52+
// Close the panel first
53+
onClose();
54+
55+
// Determine the action based on the shortcut key
56+
const isVisualizerPage = location.pathname.includes("/viz/");
57+
58+
switch (shortcutKey) {
59+
// Navigation shortcuts
60+
case "H":
61+
navigate("/");
62+
break;
63+
case "V":
64+
if (!isVisualizerPage) {
65+
navigate("/viz/sorting/bubble-sort");
66+
}
67+
break;
68+
case "Esc":
69+
// Already closed the panel above
70+
break;
71+
72+
// Search shortcuts
73+
case "/":
74+
case "Ctrl+K": {
75+
const searchInput = document.querySelector(
76+
'input[type="search"], input[placeholder*="search" i]'
77+
) as HTMLInputElement;
78+
if (searchInput) {
79+
searchInput.focus();
80+
}
81+
break;
82+
}
83+
case "Enter": {
84+
// Trigger search with current query
85+
const searchBtn = document.querySelector(
86+
"[data-search-button]"
87+
) as HTMLButtonElement;
88+
if (searchBtn) {
89+
searchBtn.click();
90+
}
91+
break;
92+
}
93+
94+
// Theme and interface
95+
case "T":
96+
toggleTheme();
97+
break;
98+
case "F":
99+
if (document.fullscreenElement) {
100+
document.exitFullscreen();
101+
} else {
102+
document.documentElement.requestFullscreen();
103+
}
104+
break;
105+
case "?":
106+
// Already handled by closing the panel
107+
break;
108+
109+
// Visualizer controls (only work on visualizer pages)
110+
case "Space":
111+
if (isVisualizerPage) {
112+
document.dispatchEvent(new CustomEvent("algolens:play-pause"));
113+
}
114+
break;
115+
case "→":
116+
if (isVisualizerPage) {
117+
document.dispatchEvent(new CustomEvent("algolens:step-forward"));
118+
}
119+
break;
120+
case "←":
121+
if (isVisualizerPage) {
122+
document.dispatchEvent(new CustomEvent("algolens:step-backward"));
123+
}
124+
break;
125+
case "↑":
126+
if (isVisualizerPage) {
127+
document.dispatchEvent(new CustomEvent("algolens:speed-up"));
128+
}
129+
break;
130+
case "↓":
131+
if (isVisualizerPage) {
132+
document.dispatchEvent(new CustomEvent("algolens:speed-down"));
133+
}
134+
break;
135+
case "R":
136+
if (isVisualizerPage) {
137+
document.dispatchEvent(new CustomEvent("algolens:reset"));
138+
}
139+
break;
140+
case "Ctrl+Home":
141+
if (isVisualizerPage) {
142+
document.dispatchEvent(new CustomEvent("algolens:go-to-start"));
143+
}
144+
break;
145+
case "Ctrl+End":
146+
if (isVisualizerPage) {
147+
document.dispatchEvent(new CustomEvent("algolens:go-to-end"));
148+
}
149+
break;
150+
151+
// Speed presets
152+
case "1":
153+
if (isVisualizerPage) {
154+
document.dispatchEvent(
155+
new CustomEvent("algolens:set-speed", { detail: 0.25 })
156+
);
157+
}
158+
break;
159+
case "2":
160+
if (isVisualizerPage) {
161+
document.dispatchEvent(
162+
new CustomEvent("algolens:set-speed", { detail: 0.5 })
163+
);
164+
}
165+
break;
166+
case "3":
167+
if (isVisualizerPage) {
168+
document.dispatchEvent(
169+
new CustomEvent("algolens:set-speed", { detail: 1 })
170+
);
171+
}
172+
break;
173+
case "4":
174+
if (isVisualizerPage) {
175+
document.dispatchEvent(
176+
new CustomEvent("algolens:set-speed", { detail: 2 })
177+
);
178+
}
179+
break;
180+
case "5":
181+
if (isVisualizerPage) {
182+
document.dispatchEvent(
183+
new CustomEvent("algolens:set-speed", { detail: 4 })
184+
);
185+
}
186+
break;
187+
188+
// Filter management shortcuts (home page)
189+
case "Ctrl+Shift+C":
190+
document.dispatchEvent(new CustomEvent("algolens:clear-filters"));
191+
break;
192+
case "Ctrl+Shift+F":
193+
document.dispatchEvent(new CustomEvent("algolens:toggle-featured"));
194+
break;
195+
case "Ctrl+Shift+E":
196+
document.dispatchEvent(new CustomEvent("algolens:toggle-filter-bar"));
197+
break;
198+
199+
default:
200+
console.log(`Shortcut ${shortcutKey} not implemented yet`);
201+
}
202+
};
203+
47204
if (!isOpen) return null;
48205

49206
// Get context-aware shortcuts based on current page
@@ -251,14 +408,16 @@ export function KeyboardShortcutsPanel({
251408
) => (
252409
<div
253410
key={index}
411+
onClick={() => handleShortcutClick(shortcut.key)}
254412
className={cn(
255-
"group flex items-center justify-between rounded-xl p-4 transition-all duration-200",
413+
"group flex cursor-pointer items-center justify-between rounded-xl p-4 transition-all duration-200",
256414
"bg-gradient-to-r from-slate-50 to-slate-100/50 dark:from-slate-800/50 dark:to-slate-800/20",
257415
"border border-slate-200/60 dark:border-slate-700/60",
258416
"hover:from-primary-50 hover:to-secondary-50 dark:hover:from-primary-900/20 dark:hover:to-secondary-900/20",
259417
"hover:border-primary-200 dark:hover:border-primary-700",
260418
"hover:scale-[1.02] hover:shadow-md active:scale-[0.98]"
261419
)}
420+
title={`Click to execute: ${shortcut.description}`}
262421
>
263422
<div className="flex items-center gap-3">
264423
{shortcut.icon && (

src/pages/VisualizerPage.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,85 @@ export default function VisualizerPage() {
467467
return () => document.removeEventListener("keydown", handleKeyDown);
468468
}, [runner, navigate]);
469469

470+
// Handle custom algolens events from KeyboardProvider and clickable shortcuts
471+
useEffect(() => {
472+
const handlePlayPause = () => {
473+
if (runner.playing) {
474+
runner.pause();
475+
} else {
476+
runner.playForward();
477+
}
478+
};
479+
480+
const handleStepForward = () => {
481+
runner.stepNext();
482+
};
483+
484+
const handleStepBackward = () => {
485+
runner.stepPrev();
486+
};
487+
488+
const handleReset = () => {
489+
runner.toStart();
490+
runner.pause();
491+
};
492+
493+
const handleSpeedUp = () => {
494+
const newSpeed = Math.min(runner.speed * 1.5, 8);
495+
runner.setSpeed(newSpeed);
496+
};
497+
498+
const handleSpeedDown = () => {
499+
const newSpeed = Math.max(runner.speed / 1.5, 0.1);
500+
runner.setSpeed(newSpeed);
501+
};
502+
503+
const handleGoToStart = () => {
504+
runner.toStart();
505+
};
506+
507+
const handleGoToEnd = () => {
508+
runner.toEnd();
509+
};
510+
511+
const handleSetSpeed = (event: CustomEvent) => {
512+
const speed = event.detail;
513+
runner.setSpeed(speed);
514+
};
515+
516+
// Add event listeners
517+
document.addEventListener("algolens:play-pause", handlePlayPause);
518+
document.addEventListener("algolens:step-forward", handleStepForward);
519+
document.addEventListener("algolens:step-backward", handleStepBackward);
520+
document.addEventListener("algolens:reset", handleReset);
521+
document.addEventListener("algolens:speed-up", handleSpeedUp);
522+
document.addEventListener("algolens:speed-down", handleSpeedDown);
523+
document.addEventListener("algolens:go-to-start", handleGoToStart);
524+
document.addEventListener("algolens:go-to-end", handleGoToEnd);
525+
document.addEventListener(
526+
"algolens:set-speed",
527+
handleSetSpeed as EventListener
528+
);
529+
530+
return () => {
531+
document.removeEventListener("algolens:play-pause", handlePlayPause);
532+
document.removeEventListener("algolens:step-forward", handleStepForward);
533+
document.removeEventListener(
534+
"algolens:step-backward",
535+
handleStepBackward
536+
);
537+
document.removeEventListener("algolens:reset", handleReset);
538+
document.removeEventListener("algolens:speed-up", handleSpeedUp);
539+
document.removeEventListener("algolens:speed-down", handleSpeedDown);
540+
document.removeEventListener("algolens:go-to-start", handleGoToStart);
541+
document.removeEventListener("algolens:go-to-end", handleGoToEnd);
542+
document.removeEventListener(
543+
"algolens:set-speed",
544+
handleSetSpeed as EventListener
545+
);
546+
};
547+
}, [runner]);
548+
470549
// Show loading state while algorithm is being loaded
471550
if (isLoadingMeta) {
472551
return (

0 commit comments

Comments
 (0)