From 8fe01bf7ebc646cdd7ec05a83849a4996774d8bb Mon Sep 17 00:00:00 2001 From: Saloni Tarone Date: Sat, 4 Oct 2025 17:58:21 +0530 Subject: [PATCH] Update All Signed-off-by: Saloni Tarone --- .../src/components/Sorting/ControlPanel.jsx | 240 +++++++--- .../src/components/Sorting/SortingCanvas.jsx | 94 ++-- .../src/contexts/AlgorithmSettingsContext.js | 71 +++ frontend/src/pages/SortingVisualizer.jsx | 436 ++++-------------- frontend/src/utils/soundEffects.js | 89 ++++ 5 files changed, 499 insertions(+), 431 deletions(-) create mode 100644 frontend/src/contexts/AlgorithmSettingsContext.js create mode 100644 frontend/src/utils/soundEffects.js diff --git a/frontend/src/components/Sorting/ControlPanel.jsx b/frontend/src/components/Sorting/ControlPanel.jsx index 9adda68..2dc0cb8 100644 --- a/frontend/src/components/Sorting/ControlPanel.jsx +++ b/frontend/src/components/Sorting/ControlPanel.jsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { Play, Pause, RotateCcw, SkipBack, SkipForward } from 'lucide-react'; +import React, { useCallback, useEffect } from 'react'; +import { Play, Pause, RotateCcw, SkipBack, SkipForward, Settings, Volume2, VolumeX, RefreshCw, SplitSquareHorizontal } from 'lucide-react'; +import { useAlgorithmSettings } from '../../contexts/AlgorithmSettingsContext'; const ControlPanel = ({ isPlaying, @@ -11,8 +12,11 @@ const ControlPanel = ({ totalSteps, isLoading, onStepForward, - onStepBackward + onStepBackward, + onCompareMode, + onThemeChange, }) => { + const { settings, updateSettings, resetSettings } = useAlgorithmSettings(); const getSpeedLabel = (speed) => { if (speed < 300) return 'Very Fast'; @@ -28,70 +32,178 @@ const ControlPanel = ({ return (
{/* Main Controls */} -
- - - - - - - +
+
+ + + + + + + +
+ +
+ + + + + +
- {/* Enhanced Speed Control - Fixed Direction */} -
-
- - - {speedPercentage}% - + {/* Settings Panel */} +
+ {/* Speed Control */} +
+
+ +
+ {[0.1, 0.5, 1, 2, 5, 10].map((preset) => ( + + ))} +
+
+ onSpeedChange(parseFloat(e.target.value))} + className={`w-full h-3 rounded-lg appearance-none cursor-pointer ${ + settings.theme === 'neon' ? 'bg-gray-800' : 'bg-gray-200' + }`} + style={{ + background: `linear-gradient(to right, ${ + settings.theme === 'neon' + ? '#a855f7' + : settings.theme === 'minimal' + ? '#374151' + : '#3b82f6' + } 0%, ${ + settings.theme === 'neon' + ? '#a855f7' + : settings.theme === 'minimal' + ? '#374151' + : '#3b82f6' + } ${(speed / 10) * 100}%, ${ + settings.theme === 'neon' ? '#1f2937' : '#e5e7eb' + } ${(speed / 10) * 100}%, ${ + settings.theme === 'neon' ? '#1f2937' : '#e5e7eb' + } 100%)` + }} + />
- onSpeedChange(1200 - parseInt(e.target.value))} - className="w-full h-3 bg-gray-200 rounded-lg appearance-none cursor-pointer" - style={{ - background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${speedPercentage}%, #e5e7eb ${speedPercentage}%, #e5e7eb 100%)` - }} - /> -
- Slow - Fast + + {/* Theme Selection */} +
+ +
+ {['classic', 'neon', 'minimal'].map((theme) => ( + + ))} +
diff --git a/frontend/src/components/Sorting/SortingCanvas.jsx b/frontend/src/components/Sorting/SortingCanvas.jsx index 16436c7..5dd2a32 100644 --- a/frontend/src/components/Sorting/SortingCanvas.jsx +++ b/frontend/src/components/Sorting/SortingCanvas.jsx @@ -1,23 +1,68 @@ import React, { useEffect, useRef, forwardRef, useCallback } from 'react'; import { useTheme } from '../../contexts/ThemeContext'; +const themeColors = { + classic: { + background: (isDark) => isDark ? '#1f2937' : '#f8fafc', + bar: (isDark) => isDark ? '#6b7280' : '#e5e7eb', + comparing: '#ef4444', + swapping: '#f59e0b', + sorted: '#10b981', + text: (isDark) => isDark ? '#ffffff' : '#000000', + glow: '#3b82f6' + }, + neon: { + background: () => '#0f172a', + bar: () => '#312e81', + comparing: '#f0abfc', + swapping: '#f472b6', + sorted: '#22d3ee', + text: () => '#ffffff', + glow: '#a855f7' + }, + minimal: { + background: (isDark) => isDark ? '#18181b' : '#fafafa', + bar: (isDark) => isDark ? '#3f3f46' : '#d4d4d8', + comparing: '#525252', + swapping: '#737373', + sorted: '#404040', + text: (isDark) => isDark ? '#ffffff' : '#000000', + glow: '#71717a' + } +}; + const SortingCanvas = forwardRef(({ - data, - algorithm, + array, + comparing = [], + swapping = [], + sorted = [], + theme = 'classic', compact = false, - selectedAlgorithm, - enhanced = false }, ref) => { const { isDark } = useTheme(); const canvasRef = useRef(null); const animationFrameRef = useRef(null); const lastRenderTime = useRef(0); + const getColor = useCallback((index) => { + const colors = themeColors[theme] || themeColors.classic; + + if (sorted.includes(index)) { + return colors.sorted; + } + if (swapping.includes(index)) { + return colors.swapping; + } + if (comparing.includes(index)) { + return colors.comparing; + } + return colors.bar(isDark); + }, [theme, isDark, comparing, swapping, sorted]); + const drawCanvas = useCallback(() => { const canvas = canvasRef.current; - if (!canvas || !data || !data.array) return; + if (!canvas || !array) return; - // Prevent excessive rendering const now = Date.now(); if (now - lastRenderTime.current < 16) return; // Limit to ~60fps lastRenderTime.current = now; @@ -26,55 +71,52 @@ const SortingCanvas = forwardRef(({ const ctx = canvas.getContext('2d'); const { width, height } = canvas; - // Clear canvas - ctx.fillStyle = isDark ? '#1f2937' : '#f8fafc'; + // Clear canvas with theme background + const colors = themeColors[theme] || themeColors.classic; + ctx.fillStyle = colors.background(isDark); ctx.fillRect(0, 0, width, height); - // Validate array data - if (!Array.isArray(data.array) || data.array.length === 0) return; + if (!Array.isArray(array) || array.length === 0) return; - // Draw bars - const maxValue = Math.max(...data.array); + const maxValue = Math.max(...array); if (maxValue <= 0) return; - const barWidth = width / data.array.length; + const barWidth = width / array.length; const maxBarHeight = height - 40; - data.array.forEach((value, index) => { + // Draw bars with glow effect for neon theme + array.forEach((value, index) => { if (typeof value !== 'number' || value < 0) return; const barHeight = (value / maxValue) * maxBarHeight; const x = index * barWidth; const y = height - barHeight - 20; - // Determine bar color - let barColor = isDark ? '#6b7280' : '#e5e7eb'; - - if (data.highlighted?.includes(index)) { - barColor = '#f59e0b'; // Orange for highlighted - } else if (data.comparing?.includes(index)) { - barColor = '#ef4444'; // Red for comparing + if (theme === 'neon') { + ctx.shadowColor = colors.glow; + ctx.shadowBlur = 15; + } else { + ctx.shadowBlur = 0; } // Draw bar - ctx.fillStyle = barColor; + ctx.fillStyle = getColor(index); ctx.fillRect(x + 2, y, barWidth - 4, barHeight); - // Draw value label - ctx.fillStyle = isDark ? '#ffffff' : '#000000'; + // Draw value label with theme color + ctx.fillStyle = colors.text(isDark); ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(value.toString(), x + barWidth / 2, height - 5); }); - // Update ref for canvas export if (ref) { ref.current = canvas; } } catch (error) { console.error('Canvas drawing error:', error); } - }, [data, algorithm, isDark, enhanced, ref]); + }, [array, comparing, swapping, sorted, theme, isDark, getColor, ref]); useEffect(() => { // Cancel any pending animation frame diff --git a/frontend/src/contexts/AlgorithmSettingsContext.js b/frontend/src/contexts/AlgorithmSettingsContext.js new file mode 100644 index 0000000..757e73a --- /dev/null +++ b/frontend/src/contexts/AlgorithmSettingsContext.js @@ -0,0 +1,71 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +const defaultSettings = { + speed: 1, // 0.1x to 10x + stepMode: false, + theme: 'classic', // 'classic' | 'neon' | 'minimal' + soundEffects: false, + showComplexity: true, + compareMode: false, +}; + +const AlgorithmSettingsContext = createContext(null); + +export const useAlgorithmSettings = () => { + const context = useContext(AlgorithmSettingsContext); + if (!context) { + throw new Error('useAlgorithmSettings must be used within an AlgorithmSettingsProvider'); + } + return context; +}; + +export const AlgorithmSettingsProvider = ({ children }) => { + const [settings, setSettings] = useState(() => { + // Load settings from localStorage on initial render + const savedSettings = localStorage.getItem('algorithmSettings'); + return savedSettings ? JSON.parse(savedSettings) : defaultSettings; + }); + + // Save settings to localStorage whenever they change + useEffect(() => { + localStorage.setItem('algorithmSettings', JSON.stringify(settings)); + }, [settings]); + + const updateSettings = (newSettings) => { + setSettings(prev => ({ ...prev, ...newSettings })); + }; + + const resetSettings = () => { + setSettings(defaultSettings); + }; + + return ( + + {children} + + ); +}; + +export const themes = { + classic: { + primary: 'from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700', + secondary: 'bg-gray-100 text-gray-700 hover:bg-gray-200', + bar: 'bg-blue-500', + text: 'text-gray-700', + background: 'bg-white', + }, + neon: { + primary: 'from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600', + secondary: 'bg-gray-900 text-purple-400 hover:bg-gray-800', + bar: 'bg-purple-500', + text: 'text-purple-200', + background: 'bg-gray-900', + }, + minimal: { + primary: 'from-gray-700 to-gray-800 hover:from-gray-800 hover:to-gray-900', + secondary: 'bg-gray-100 text-gray-600 hover:bg-gray-200', + bar: 'bg-gray-700', + text: 'text-gray-600', + background: 'bg-gray-50', + }, +}; diff --git a/frontend/src/pages/SortingVisualizer.jsx b/frontend/src/pages/SortingVisualizer.jsx index 2d05a55..35d39b4 100644 --- a/frontend/src/pages/SortingVisualizer.jsx +++ b/frontend/src/pages/SortingVisualizer.jsx @@ -2,14 +2,16 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Zap, Settings, BookOpen, Download } from 'lucide-react'; import toast from 'react-hot-toast'; import { useTheme } from '../contexts/ThemeContext'; +import { AlgorithmSettingsProvider, useAlgorithmSettings } from '../contexts/AlgorithmSettingsContext'; import SortingCanvas from '../components/Sorting/SortingCanvas'; import ArrayInput from '../components/Sorting/ArrayInput'; import ControlPanel from '../components/Sorting/ControlPanel'; import ComplexityDisplay from '../components/Sorting/ComplexityDisplay'; import { sortingService } from '../services/api'; -const SortingVisualizer = () => { +const SortingVisualizerContent = () => { const { isDark, classes, getThemedGradient } = useTheme(); + const { settings } = useAlgorithmSettings(); const [array, setArray] = useState([64, 34, 25, 12, 22, 11, 90]); const [algorithm, setAlgorithm] = useState('bubble'); const [isPlaying, setIsPlaying] = useState(false); @@ -26,6 +28,7 @@ const SortingVisualizer = () => { const particleCanvasRef = useRef(null); const timeoutCountRef = useRef(0); const lastTimeoutCheck = useRef(Date.now()); + const audioRef = useRef(new Audio('/sounds/swap.mp3')); const algorithms = [ { @@ -346,6 +349,31 @@ const SortingVisualizer = () => { }); }; + // Convert speed setting (0.1x - 10x) to delay in ms (1000ms - 100ms) + const getDelay = useCallback(() => { + return Math.round(1000 / settings.speed); + }, [settings.speed]); + + // Handle sound effects + const playSwapSound = useCallback(() => { + if (settings.soundEffects && audioRef.current) { + audioRef.current.currentTime = 0; + audioRef.current.play().catch(() => {}); + } + }, [settings.soundEffects]); + + useEffect(() => { + // Update speed when settings change + setSpeed(getDelay()); + }, [settings.speed, getDelay]); + + // Play step sound when moving forward + useEffect(() => { + if (currentStep > 0 && settings.soundEffects) { + playSwapSound(); + } + }, [currentStep, settings.soundEffects, playSwapSound]); + useEffect(() => { if (isPlaying && currentStep < steps.length - 1) { const timer = setTimeout(() => { @@ -367,356 +395,82 @@ const SortingVisualizer = () => { }, [isPlaying, currentStep, steps.length, speed]); return ( -
- {/* Conditional Particle Background */} - {shouldShowParticles && ( - - )} - - {/* Timeout Warning */} - {timeoutDetected && ( -
- ⚠️ Performance issue detected. Some visual effects disabled. -
- )} - -
-
- {/* Left Panel - Enhanced Array & Controls */} -
-
-
-

- - Configuration & Controls -

- - {/* Add algorithm selector here */} -
- - -
-
- -
- {/* Enhanced Array Section */} -
-

- - Array Configuration -

- - {/* Array Size Slider */} -
- - setArraySize(parseInt(e.target.value))} - className="w-full h-3 bg-gray-200 rounded-lg appearance-none cursor-pointer" - style={{ - background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${((arraySize - 5) / 10) * 100}%, #e5e7eb ${((arraySize - 5) / 10) * 100}%, #e5e7eb 100%)` - }} - /> -
- - -
- - {/* Enhanced Controls Section */} -
-

- - Playback Controls -

- -
- - {/* Download Button */} - -
-
+
+
+ {/* Main content */} +
+ {/* Algorithm selection and array input */} +
+ +
- {/* Right Panel - Enhanced Visualization */} -
-
- {/* Visualization Header */} -
-
-
-

- {selectedAlgorithm?.name} Visualization -

-
- -
- Step {currentStep + 1} of {steps.length || 1} -
-
- - Ops: {currentStepData.operations_count || 0} -
-
-
-
- {selectedAlgorithm?.complexity} -
-
-
- -
- {/* Enhanced Visualization Canvas */} - -
-
- - {/* Enhanced Step Explanation with Typewriter Effect */} -
-
-
-

- - Step Explanation -

- -
-
- -
-
-

- Current Operation: -

-

- {currentStepData.operation} -

-
- -
-

- What's Happening: -

-

- {typewriterText} - | -

-
- - {selectedAlgorithm && ( -
-

- Algorithm Info: -

-

- {selectedAlgorithm.explanation} -

-
- )} - - {/* Detailed Log */} - {showDetailedLog && ( -
-
- Step History: -
-
- {steps.slice(Math.max(0, currentStep - 5), currentStep + 1).map((step, index) => ( -
- {step.operation} -
- ))} -
-
- )} -
-
+ {/* Control panel with new settings */} +
+ settings.updateSettings({ speed: newSpeed })} + speed={settings.speed} + currentStep={currentStep} + totalSteps={steps.length} + isLoading={isLoading} + onStepForward={stepForward} + onStepBackward={stepBackward} + onCompareMode={() => settings.updateSettings({ compareMode: !settings.compareMode })} + onThemeChange={(theme) => settings.updateSettings({ theme })} + />
-
- {/* Enhanced Bottom Panel - Analysis & Metrics */} -
-
-
-

- Complexity Analysis -

-
-
- + + {showParticles && ( + -
-
- -
-
-

- Performance Metrics -

-
-
-
-
-
- {array.length} -
-
- Array Size -
-
-
-
- {steps.length} -
-
- Total Steps -
-
-
-
- {currentStepData.operations_count || 0} -
-
- Operations -
-
-
-
- {steps.length > 0 ? Math.round((currentStep / (steps.length - 1)) * 100) : 0}% -
-
- Progress -
-
-
-
+ )}
- - {/* Custom CSS for animations */} -