Skip to content

Commit 8b15f0f

Browse files
committed
feat: add full screen mode
1 parent 5b82995 commit 8b15f0f

3 files changed

Lines changed: 240 additions & 92 deletions

File tree

src/App.jsx

Lines changed: 172 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { useState, useEffect, lazy, Suspense } from 'react';
8+
import { motion, AnimatePresence } from 'framer-motion';
89
import Header from './components/Header';
910
import Footer from './components/Footer';
1011
import ArrayVisualizer from './components/ArrayVisualizer';
@@ -17,6 +18,7 @@ const PythonCodePanel = lazy(() => import('./components/PythonCodePanel'));
1718
const ComplexityPanel = lazy(() => import('./components/ComplexityPanel'));
1819
import { useSortingVisualization } from './hooks/useSortingVisualization';
1920
import { usePathfindingVisualization } from './hooks/usePathfindingVisualization';
21+
import { useFullScreen } from './hooks/useFullScreen';
2022
import { generateRandomArray } from './utils/arrayHelpers';
2123
import { createEmptyGrid } from './utils/gridHelpers';
2224
import { algorithms } from './algorithms';
@@ -43,6 +45,8 @@ function App() {
4345
const [mode, setMode] = useState(VISUALIZATION_MODES.MANUAL);
4446
const [isPythonPanelOpen, setIsPythonPanelOpen] = useState(false);
4547

48+
const { isFullScreen, toggleFullScreen } = useFullScreen();
49+
4650
const sortingVisualization = useSortingVisualization(array, speed, mode);
4751
const pathfindingVisualization = usePathfindingVisualization(
4852
gridSize,
@@ -139,104 +143,180 @@ function App() {
139143
>
140144
Skip to main content
141145
</a>
142-
<Header />
143-
<main className="flex-1 pt-20 p-6 pb-32" role="main" id="main-content">
144-
<div className="max-w-7xl mx-auto">
145-
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
146-
{/* Settings Panel */}
147-
<aside
148-
className="lg:col-span-1"
149-
role="complementary"
150-
aria-label="Algorithm settings"
151-
>
152-
<SettingsPanel
153-
algorithmType={algorithmType}
154-
onAlgorithmTypeChange={handleAlgorithmTypeChange}
155-
selectedAlgorithm={
156-
algorithmType === ALGORITHM_TYPES.SORTING
157-
? selectedAlgorithm
158-
: selectedPathfindingAlgorithm
159-
}
160-
onAlgorithmChange={handleAlgorithmChange}
161-
speed={speed}
162-
onSpeedChange={setSpeed}
163-
arraySize={arraySize}
164-
onArraySizeChange={handleArraySizeChange}
165-
gridSize={gridSize}
166-
onGridSizeChange={handleGridSizeChange}
167-
isPlaying={visualization.isPlaying}
168-
mode={mode}
169-
onModeChange={setMode}
170-
/>
171-
</aside>
172146

173-
{/* Visualization Area */}
174-
<section
175-
className="lg:col-span-3 space-y-6"
176-
role="region"
177-
aria-label="Algorithm visualization"
147+
<AnimatePresence mode="wait">
148+
{isFullScreen ? (
149+
<motion.div
150+
key="fullscreen"
151+
initial={{ opacity: 0 }}
152+
animate={{ opacity: 1 }}
153+
exit={{ opacity: 0 }}
154+
transition={{ duration: 0.3 }}
155+
className="w-screen h-screen bg-gray-900 flex flex-col p-4"
156+
role="main"
157+
id="main-content"
158+
>
159+
{/* Full Screen Visualizer */}
160+
<div className="flex-1 mb-4">
161+
{algorithmType === ALGORITHM_TYPES.SORTING ? (
162+
<ArrayVisualizer
163+
array={sortingVisualization.array}
164+
states={sortingVisualization.states}
165+
description={sortingVisualization.description}
166+
isComplete={sortingVisualization.isComplete}
167+
algorithm={selectedAlgorithm}
168+
onStepForward={sortingVisualization.stepForward}
169+
onStepBackward={sortingVisualization.stepBackward}
170+
mode={mode}
171+
/>
172+
) : (
173+
<GridVisualizer
174+
states={pathfindingVisualization.states}
175+
description={pathfindingVisualization.description}
176+
isComplete={pathfindingVisualization.isComplete}
177+
algorithm={selectedPathfindingAlgorithm}
178+
gridSize={gridSize}
179+
onStepForward={pathfindingVisualization.stepForward}
180+
onStepBackward={pathfindingVisualization.stepBackward}
181+
mode={mode}
182+
/>
183+
)}
184+
</div>
185+
186+
{/* Full Screen Control Panel */}
187+
<ControlPanel
188+
isPlaying={visualization.isPlaying}
189+
isComplete={visualization.isComplete}
190+
mode={mode}
191+
onPlay={visualization.play}
192+
onPause={visualization.pause}
193+
onReset={visualization.reset}
194+
onStepForward={visualization.stepForward}
195+
onStepBackward={visualization.stepBackward}
196+
currentStep={visualization.currentStep}
197+
totalSteps={visualization.totalSteps}
198+
onGenerateArray={handleGenerateArray}
199+
algorithmType={algorithmType}
200+
isFullScreen={isFullScreen}
201+
onToggleFullScreen={toggleFullScreen}
202+
/>
203+
</motion.div>
204+
) : (
205+
<motion.div
206+
key="normal"
207+
initial={{ opacity: 0 }}
208+
animate={{ opacity: 1 }}
209+
exit={{ opacity: 0 }}
210+
transition={{ duration: 0.3 }}
211+
className="flex flex-col min-h-screen"
212+
>
213+
<Header />
214+
<main
215+
className="flex-1 pt-20 p-6 pb-32"
216+
role="main"
217+
id="main-content"
178218
>
179-
{/* Visualizer */}
180-
<div
181-
className="h-[650px]"
182-
role="img"
183-
aria-label={`${algorithmType === ALGORITHM_TYPES.SORTING ? selectedAlgorithm : selectedPathfindingAlgorithm} algorithm visualization`}
184-
>
185-
{algorithmType === ALGORITHM_TYPES.SORTING ? (
186-
<ArrayVisualizer
187-
array={sortingVisualization.array}
188-
states={sortingVisualization.states}
189-
description={sortingVisualization.description}
190-
isComplete={sortingVisualization.isComplete}
191-
algorithm={selectedAlgorithm}
192-
onStepForward={sortingVisualization.stepForward}
193-
onStepBackward={sortingVisualization.stepBackward}
194-
mode={mode}
195-
/>
196-
) : (
197-
<GridVisualizer
198-
states={pathfindingVisualization.states}
199-
description={pathfindingVisualization.description}
200-
isComplete={pathfindingVisualization.isComplete}
201-
algorithm={selectedPathfindingAlgorithm}
202-
gridSize={gridSize}
203-
onStepForward={pathfindingVisualization.stepForward}
204-
onStepBackward={pathfindingVisualization.stepBackward}
205-
mode={mode}
206-
/>
207-
)}
219+
<div className="max-w-7xl mx-auto">
220+
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
221+
{/* Settings Panel */}
222+
<aside
223+
className="lg:col-span-1"
224+
role="complementary"
225+
aria-label="Algorithm settings"
226+
>
227+
<SettingsPanel
228+
algorithmType={algorithmType}
229+
onAlgorithmTypeChange={handleAlgorithmTypeChange}
230+
selectedAlgorithm={
231+
algorithmType === ALGORITHM_TYPES.SORTING
232+
? selectedAlgorithm
233+
: selectedPathfindingAlgorithm
234+
}
235+
onAlgorithmChange={handleAlgorithmChange}
236+
speed={speed}
237+
onSpeedChange={setSpeed}
238+
arraySize={arraySize}
239+
onArraySizeChange={handleArraySizeChange}
240+
gridSize={gridSize}
241+
onGridSizeChange={handleGridSizeChange}
242+
isPlaying={visualization.isPlaying}
243+
mode={mode}
244+
onModeChange={setMode}
245+
/>
246+
</aside>
247+
248+
{/* Visualization Area */}
249+
<section
250+
className="lg:col-span-3 space-y-6"
251+
role="region"
252+
aria-label="Algorithm visualization"
253+
>
254+
{/* Visualizer */}
255+
<div
256+
className="h-[650px]"
257+
role="img"
258+
aria-label={`${algorithmType === ALGORITHM_TYPES.SORTING ? selectedAlgorithm : selectedPathfindingAlgorithm} algorithm visualization`}
259+
>
260+
{algorithmType === ALGORITHM_TYPES.SORTING ? (
261+
<ArrayVisualizer
262+
array={sortingVisualization.array}
263+
states={sortingVisualization.states}
264+
description={sortingVisualization.description}
265+
isComplete={sortingVisualization.isComplete}
266+
algorithm={selectedAlgorithm}
267+
onStepForward={sortingVisualization.stepForward}
268+
onStepBackward={sortingVisualization.stepBackward}
269+
mode={mode}
270+
/>
271+
) : (
272+
<GridVisualizer
273+
states={pathfindingVisualization.states}
274+
description={pathfindingVisualization.description}
275+
isComplete={pathfindingVisualization.isComplete}
276+
algorithm={selectedPathfindingAlgorithm}
277+
gridSize={gridSize}
278+
onStepForward={pathfindingVisualization.stepForward}
279+
onStepBackward={pathfindingVisualization.stepBackward}
280+
mode={mode}
281+
/>
282+
)}
283+
</div>
284+
285+
{/* Control Panel */}
286+
<ControlPanel
287+
isPlaying={visualization.isPlaying}
288+
isComplete={visualization.isComplete}
289+
mode={mode}
290+
onPlay={visualization.play}
291+
onPause={visualization.pause}
292+
onReset={visualization.reset}
293+
onStepForward={visualization.stepForward}
294+
onStepBackward={visualization.stepBackward}
295+
currentStep={visualization.currentStep}
296+
totalSteps={visualization.totalSteps}
297+
onGenerateArray={handleGenerateArray}
298+
algorithmType={algorithmType}
299+
isFullScreen={isFullScreen}
300+
onToggleFullScreen={toggleFullScreen}
301+
/>
302+
</section>
303+
</div>
208304
</div>
305+
</main>
306+
<Footer />
209307

210-
{/* Control Panel */}
211-
<ControlPanel
212-
isPlaying={visualization.isPlaying}
213-
isComplete={visualization.isComplete}
214-
mode={mode}
215-
onPlay={visualization.play}
216-
onPause={visualization.pause}
217-
onReset={visualization.reset}
218-
onStepForward={visualization.stepForward}
219-
onStepBackward={visualization.stepBackward}
220-
currentStep={visualization.currentStep}
221-
totalSteps={visualization.totalSteps}
222-
onGenerateArray={handleGenerateArray}
223-
algorithmType={algorithmType}
308+
{/* Floating Action Button - Only show when panel is closed */}
309+
{!isPythonPanelOpen && (
310+
<FloatingActionButton
311+
onClick={() => setIsPythonPanelOpen(true)}
312+
disabled={!selectedAlgorithm}
224313
/>
225-
</section>
226-
</div>
227-
</div>
228-
</main>
229-
<Footer />
230-
231-
{/* Floating Action Button - Only show when panel is closed */}
232-
{!isPythonPanelOpen && (
233-
<FloatingActionButton
234-
onClick={() => setIsPythonPanelOpen(true)}
235-
disabled={!selectedAlgorithm}
236-
/>
237-
)}
314+
)}
315+
</motion.div>
316+
)}
317+
</AnimatePresence>
238318

239-
{/* Python Code Panel */}
319+
{/* Python Code Panel - Always available */}
240320
<Suspense
241321
fallback={
242322
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">

src/components/ControlPanel.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
SkipBack,
1313
SkipForward,
1414
Shuffle,
15+
Maximize,
16+
Minimize,
1517
} from 'lucide-react';
1618

1719
/**
@@ -31,6 +33,8 @@ import {
3133
* @param {number} totalSteps - Total number of steps
3234
* @param {Function} onGenerateArray - Handler for generating new random start/end points
3335
* @param {string} algorithmType - Current algorithm type ('sorting' or 'pathfinding')
36+
* @param {boolean} isFullScreen - Whether full-screen mode is active
37+
* @param {Function} onToggleFullScreen - Handler for toggling full-screen mode
3438
*/
3539
function ControlPanel({
3640
isPlaying,
@@ -45,6 +49,8 @@ function ControlPanel({
4549
totalSteps,
4650
onGenerateArray,
4751
algorithmType,
52+
isFullScreen,
53+
onToggleFullScreen,
4854
}) {
4955
const buttonBaseClasses =
5056
'p-3 min-w-[44px] min-h-[44px] rounded-lg transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:scale-100 touch-manipulation';
@@ -128,6 +134,20 @@ function ControlPanel({
128134
<Shuffle size={20} aria-hidden="true" />
129135
</button>
130136
)}
137+
138+
{/* Full Screen Toggle */}
139+
<button
140+
onClick={onToggleFullScreen}
141+
className={`${buttonBaseClasses} bg-purple-500 hover:bg-purple-600 text-white`}
142+
title={isFullScreen ? 'Exit Full Screen (Esc)' : 'Go Full Screen (F)'}
143+
aria-label={isFullScreen ? 'Exit Full Screen' : 'Go Full Screen'}
144+
>
145+
{isFullScreen ? (
146+
<Minimize size={20} aria-hidden="true" />
147+
) : (
148+
<Maximize size={20} aria-hidden="true" />
149+
)}
150+
</button>
131151
</div>
132152

133153
{/* Progress Bar */}

src/hooks/useFullScreen.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright (c) 2025 Ayoub Abidi
3+
* Licensed under Elastic License 2.0 OR Commercial
4+
* See LICENSE for details.
5+
*/
6+
7+
import { useState, useEffect, useCallback } from 'react';
8+
9+
export function useFullScreen() {
10+
const [isFullScreen, setIsFullScreen] = useState(() => {
11+
return localStorage.getItem('flowModeEnabled') === 'true';
12+
});
13+
14+
const toggleFullScreen = useCallback(() => {
15+
setIsFullScreen(prev => {
16+
const newValue = !prev;
17+
localStorage.setItem('flowModeEnabled', newValue.toString());
18+
return newValue;
19+
});
20+
}, []);
21+
22+
const exitFullScreen = useCallback(() => {
23+
setIsFullScreen(false);
24+
localStorage.setItem('flowModeEnabled', 'false');
25+
}, []);
26+
27+
// Keyboard shortcuts
28+
useEffect(() => {
29+
const handleKeyDown = event => {
30+
if (event.key === 'f' || event.key === 'F') {
31+
event.preventDefault();
32+
toggleFullScreen();
33+
} else if (event.key === 'Escape') {
34+
event.preventDefault();
35+
exitFullScreen();
36+
}
37+
};
38+
39+
window.addEventListener('keydown', handleKeyDown);
40+
return () => window.removeEventListener('keydown', handleKeyDown);
41+
}, [toggleFullScreen, exitFullScreen]);
42+
43+
return {
44+
isFullScreen,
45+
toggleFullScreen,
46+
exitFullScreen,
47+
};
48+
}

0 commit comments

Comments
 (0)