Cleanup
This commit is contained in:
parent
b4a104a860
commit
38f9b7a33a
1 changed files with 15 additions and 39 deletions
54
src/App.tsx
54
src/App.tsx
|
@ -31,20 +31,19 @@ import { clamp } from './utils/math';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// Core state
|
// Simulation state
|
||||||
const [targets, setTargets] = useState<Target[]>([]);
|
const [targets, setTargets] = useState<Target[]>([]);
|
||||||
const [currentTarget, setCurrentTarget] = useState<number | null>(null);
|
const [currentTarget, setCurrentTarget] = useState<number | null>(null);
|
||||||
const [zoomLevel, setZoomLevel] = useState<number>(CONSTANTS.ZOOM_LEVELS.default);
|
const [zoomLevel, setZoomLevel] = useState<number>(CONSTANTS.ZOOM_LEVELS.default);
|
||||||
const [stats, setStats] = useState<Stats>(createInitialStats());
|
const [stats, setStats] = useState<Stats>(createInitialStats());
|
||||||
|
|
||||||
// Study mode state
|
// Experiment mode configuration
|
||||||
const [mode, setMode] = useState<AppMode>('freestyle');
|
const [mode, setMode] = useState<AppMode>('freestyle');
|
||||||
const [currentPreset, setCurrentPreset] = useState<ZoomPreset>('A');
|
const [currentPreset, setCurrentPreset] = useState<ZoomPreset>('A');
|
||||||
const [presetsCompleted, setPresetsCompleted] = useState<ZoomPreset[]>([]);
|
const [presetsCompleted, setPresetsCompleted] = useState<ZoomPreset[]>([]);
|
||||||
const [targetCount, setTargetCount] = useState(0);
|
const [targetCount, setTargetCount] = useState(0);
|
||||||
const [studyComplete, setStudyComplete] = useState(false);
|
const [studyComplete, setStudyComplete] = useState(false);
|
||||||
|
|
||||||
// Load settings from localStorage or use defaults
|
|
||||||
const [settings, setSettings] = useState<AccelerationSettings>(() => {
|
const [settings, setSettings] = useState<AccelerationSettings>(() => {
|
||||||
const savedState = loadState();
|
const savedState = loadState();
|
||||||
const defaultSettings = createDefaultSettings();
|
const defaultSettings = createDefaultSettings();
|
||||||
|
@ -56,30 +55,28 @@ function App() {
|
||||||
return savedState?.rounds ?? [];
|
return savedState?.rounds ?? [];
|
||||||
});
|
});
|
||||||
|
|
||||||
// UI state
|
// UI visibility states
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const [showRounds, setShowRounds] = useState(false);
|
const [showRounds, setShowRounds] = useState(false);
|
||||||
const [showInstructions, setShowInstructions] = useState(false);
|
const [showInstructions, setShowInstructions] = useState(false);
|
||||||
|
const [showResizeWarning, setShowResizeWarning] = useState(false);
|
||||||
const [viewportOffset, setViewportOffset] = useState<ViewportOffset>({
|
const [viewportOffset, setViewportOffset] = useState<ViewportOffset>({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add state to track drag operations
|
// Interaction tracking refs
|
||||||
const isDragging = useRef(false);
|
const isDragging = useRef(false);
|
||||||
const dragStartTime = useRef(0);
|
const dragStartTime = useRef(0);
|
||||||
const dragStartPos = useRef({ x: 0, y: 0 });
|
const dragStartPos = useRef({ x: 0, y: 0 });
|
||||||
const DRAG_THRESHOLD = 5; // pixels
|
const DRAG_THRESHOLD = 5; // pixels
|
||||||
|
|
||||||
// Refs for handling zoom behavior
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const zoomSpeedSamples = useRef<number[]>([]);
|
const zoomSpeedSamples = useRef<number[]>([]);
|
||||||
const touchStartDistance = useRef<number>(0);
|
const touchStartDistance = useRef<number>(0);
|
||||||
const wasReset = useRef(false);
|
const wasReset = useRef(false);
|
||||||
|
|
||||||
// Add a new state for the resize warning popup
|
// Load saved state on initial mount
|
||||||
const [showResizeWarning, setShowResizeWarning] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedState = loadState();
|
const savedState = loadState();
|
||||||
if (savedState?.stats) {
|
if (savedState?.stats) {
|
||||||
|
@ -88,6 +85,7 @@ function App() {
|
||||||
setShowInstructions(!savedState?.rounds || savedState.rounds.length === 0);
|
setShowInstructions(!savedState?.rounds || savedState.rounds.length === 0);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Persist state when it changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
saveState({
|
saveState({
|
||||||
rounds,
|
rounds,
|
||||||
|
@ -96,6 +94,7 @@ function App() {
|
||||||
});
|
});
|
||||||
}, [rounds, stats, settings]);
|
}, [rounds, stats, settings]);
|
||||||
|
|
||||||
|
// Initialize the simulation on first load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeGame();
|
initializeGame();
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -115,7 +114,7 @@ function App() {
|
||||||
setStudyComplete(false);
|
setStudyComplete(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply preset when it changes
|
// Configure settings when preset changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPreset) {
|
if (currentPreset) {
|
||||||
setSettings(prev => ({
|
setSettings(prev => ({
|
||||||
|
@ -127,32 +126,29 @@ function App() {
|
||||||
}
|
}
|
||||||
}, [currentPreset]);
|
}, [currentPreset]);
|
||||||
|
|
||||||
// Handle moving to next preset in study mode
|
// Progress through presets in study mode
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode !== 'study') return;
|
if (mode !== 'study') return;
|
||||||
|
|
||||||
if (targetCount >= CONSTANTS.STUDY_TARGETS_PER_PRESET) {
|
if (targetCount >= CONSTANTS.STUDY_TARGETS_PER_PRESET) {
|
||||||
// Add current preset to completed list
|
|
||||||
if (!presetsCompleted.includes(currentPreset)) {
|
if (!presetsCompleted.includes(currentPreset)) {
|
||||||
setPresetsCompleted(prev => [...prev, currentPreset]);
|
setPresetsCompleted(prev => [...prev, currentPreset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Directly move to the next preset without showing break screen
|
|
||||||
const allPresets: ZoomPreset[] = ['A', 'B', 'C', 'D'];
|
const allPresets: ZoomPreset[] = ['A', 'B', 'C', 'D'];
|
||||||
const currentIndex = allPresets.indexOf(currentPreset);
|
const currentIndex = allPresets.indexOf(currentPreset);
|
||||||
|
|
||||||
if (currentIndex < allPresets.length - 1) {
|
if (currentIndex < allPresets.length - 1) {
|
||||||
// Move to next preset
|
|
||||||
const nextPreset = allPresets[currentIndex + 1];
|
const nextPreset = allPresets[currentIndex + 1];
|
||||||
setCurrentPreset(nextPreset);
|
setCurrentPreset(nextPreset);
|
||||||
setTargetCount(0);
|
setTargetCount(0);
|
||||||
} else {
|
} else {
|
||||||
// All presets completed
|
|
||||||
setStudyComplete(true);
|
setStudyComplete(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [targetCount, currentPreset, mode, presetsCompleted]);
|
}, [targetCount, currentPreset, mode, presetsCompleted]);
|
||||||
|
|
||||||
|
// Handle zoom wheel events and track timing data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
@ -171,15 +167,12 @@ function App() {
|
||||||
const scrollAmount = processWheelDelta(-event.deltaY);
|
const scrollAmount = processWheelDelta(-event.deltaY);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Determine zoom direction (positive scrollAmount = zoom in, negative = zoom out)
|
|
||||||
const direction = scrollAmount > 0 ? 'in' : 'out';
|
const direction = scrollAmount > 0 ? 'in' : 'out';
|
||||||
|
|
||||||
// Update zoom timing stats
|
|
||||||
setStats(previous => {
|
setStats(previous => {
|
||||||
// If we're already zooming in the same direction, add to the time
|
|
||||||
if (previous.isZooming && previous.zoomDirection === direction && previous.lastZoomTime) {
|
if (previous.isZooming && previous.zoomDirection === direction && previous.lastZoomTime) {
|
||||||
const timeDelta = now - previous.lastZoomTime;
|
const timeDelta = now - previous.lastZoomTime;
|
||||||
// Only count if it's a reasonable time (less than 500ms between events)
|
// Only accumulate time if events are close together (continuous zooming)
|
||||||
const shouldCount = timeDelta < 500;
|
const shouldCount = timeDelta < 500;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -191,7 +184,6 @@ function App() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise start new zoom tracking
|
|
||||||
return {
|
return {
|
||||||
...previous,
|
...previous,
|
||||||
isZooming: true,
|
isZooming: true,
|
||||||
|
@ -233,7 +225,6 @@ function App() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stop zoom timing when wheel events stop
|
|
||||||
const handleZoomEnd = () => {
|
const handleZoomEnd = () => {
|
||||||
setStats(previous => ({
|
setStats(previous => ({
|
||||||
...previous,
|
...previous,
|
||||||
|
@ -245,7 +236,7 @@ function App() {
|
||||||
|
|
||||||
container.addEventListener('wheel', handleWheelEvent, { passive: false });
|
container.addEventListener('wheel', handleWheelEvent, { passive: false });
|
||||||
|
|
||||||
// Add a listener to detect when zooming stops (after 300ms of no wheel events)
|
// Detect when zooming stops after a brief pause
|
||||||
let zoomEndTimer: number;
|
let zoomEndTimer: number;
|
||||||
container.addEventListener('wheel', () => {
|
container.addEventListener('wheel', () => {
|
||||||
clearTimeout(zoomEndTimer);
|
clearTimeout(zoomEndTimer);
|
||||||
|
@ -334,7 +325,6 @@ function App() {
|
||||||
if (distanceToTarget <= target.radius) {
|
if (distanceToTarget <= target.radius) {
|
||||||
const timeElapsed = Date.now() - stats.startTime;
|
const timeElapsed = Date.now() - stats.startTime;
|
||||||
|
|
||||||
// Create and save the round with zoom timing data
|
|
||||||
const newRound = createNewRound(
|
const newRound = createNewRound(
|
||||||
timeElapsed,
|
timeElapsed,
|
||||||
stats.misclicks,
|
stats.misclicks,
|
||||||
|
@ -348,7 +338,6 @@ function App() {
|
||||||
|
|
||||||
setRounds(previous => [...previous, newRound]);
|
setRounds(previous => [...previous, newRound]);
|
||||||
|
|
||||||
// Update stats and reset zoom timing data
|
|
||||||
setStats({
|
setStats({
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
misclicks: 0,
|
misclicks: 0,
|
||||||
|
@ -361,10 +350,8 @@ function App() {
|
||||||
zoomDirection: null
|
zoomDirection: null
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set next target
|
|
||||||
setCurrentTarget(getRandomTarget(currentTarget, CONSTANTS.TARGET_COUNT));
|
setCurrentTarget(getRandomTarget(currentTarget, CONSTANTS.TARGET_COUNT));
|
||||||
|
|
||||||
// If in study mode, increment target count
|
|
||||||
if (mode === 'study') {
|
if (mode === 'study') {
|
||||||
setTargetCount(prev => prev + 1);
|
setTargetCount(prev => prev + 1);
|
||||||
}
|
}
|
||||||
|
@ -384,24 +371,19 @@ function App() {
|
||||||
wasReset.current = true;
|
wasReset.current = true;
|
||||||
initializeGame();
|
initializeGame();
|
||||||
|
|
||||||
// Close any open panels
|
|
||||||
setShowSettings(false);
|
setShowSettings(false);
|
||||||
setShowRounds(false);
|
setShowRounds(false);
|
||||||
|
|
||||||
// Show the initial instructions screen
|
|
||||||
setShowInstructions(true);
|
setShowInstructions(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModeChange = (newMode: AppMode) => {
|
const handleModeChange = (newMode: AppMode) => {
|
||||||
// Reset data when changing modes
|
|
||||||
if (newMode !== mode) {
|
if (newMode !== mode) {
|
||||||
if (window.confirm('Changing modes will reset your current data. Continue?')) {
|
if (window.confirm('Changing modes will reset your current data. Continue?')) {
|
||||||
setMode(newMode);
|
setMode(newMode);
|
||||||
setRounds([]);
|
setRounds([]);
|
||||||
setStats(createInitialStats());
|
setStats(createInitialStats());
|
||||||
|
|
||||||
// Reset study mode stats when changing modes
|
|
||||||
if (newMode === 'study') {
|
if (newMode === 'study') {
|
||||||
setTargetCount(0);
|
setTargetCount(0);
|
||||||
setPresetsCompleted([]);
|
setPresetsCompleted([]);
|
||||||
|
@ -409,7 +391,6 @@ function App() {
|
||||||
setStudyComplete(false);
|
setStudyComplete(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the game
|
|
||||||
initializeGame();
|
initializeGame();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -417,15 +398,13 @@ function App() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate average time
|
|
||||||
const averageTime = calculateAverageTime(stats.times);
|
const averageTime = calculateAverageTime(stats.times);
|
||||||
|
|
||||||
// Add a resize handler to regenerate targets when window size changes
|
// Regenerate targets when window is resized
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let resizeTimer: number;
|
let resizeTimer: number;
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
// Don't regenerate if in study mode to avoid disrupting the test
|
|
||||||
if (mode !== 'study') {
|
if (mode !== 'study') {
|
||||||
clearTimeout(resizeTimer);
|
clearTimeout(resizeTimer);
|
||||||
|
|
||||||
|
@ -433,14 +412,12 @@ function App() {
|
||||||
const newTargets = generateTargets();
|
const newTargets = generateTargets();
|
||||||
setTargets(newTargets);
|
setTargets(newTargets);
|
||||||
|
|
||||||
// If we're targeting something, make sure it's still valid
|
|
||||||
if (currentTarget !== null) {
|
if (currentTarget !== null) {
|
||||||
setCurrentTarget(getRandomTarget(currentTarget, CONSTANTS.TARGET_COUNT));
|
setCurrentTarget(getRandomTarget(currentTarget, CONSTANTS.TARGET_COUNT));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show resize warning
|
|
||||||
setShowResizeWarning(true);
|
setShowResizeWarning(true);
|
||||||
}, 500); // Add a delay to detect when resizing is "done"
|
}, 500); // Delay to detect when resizing is complete
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -569,7 +546,6 @@ function App() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Resize warning popup */}
|
|
||||||
{showResizeWarning && (
|
{showResizeWarning && (
|
||||||
<div className="resize-warning">
|
<div className="resize-warning">
|
||||||
<div className="resize-warning-content">
|
<div className="resize-warning-content">
|
||||||
|
|
Loading…
Add table
Reference in a new issue