starting new structure
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import type { BoardManager } from '../useBoard/types';
|
||||
import type { CursorManager, CursorMode, Coord, VimMotionSystem, GameKeyManager, KeyLogEntry } from './types';
|
||||
import { useVimMotions } from './useVimMotions';
|
||||
|
||||
export function useCursor(board: BoardManager): CursorManager & { keyManager: GameKeyManager } {
|
||||
const [position, setPosition] = useState<Coord>({ row: 0, col: 0 });
|
||||
const [mode, setMode] = useState<CursorMode>('normal');
|
||||
const [keyLog, setKeyLog] = useState<KeyLogEntry[]>([]);
|
||||
|
||||
const vimMotions = useVimMotions();
|
||||
|
||||
const addKeyToLog = useCallback((key: string) => {
|
||||
setKeyLog(prev => [...prev, { key, timestamp: Date.now() }]);
|
||||
}, []);
|
||||
|
||||
const move = useCallback((dRows: number, dCols: number, count: number) => {
|
||||
setPosition(prev => {
|
||||
const newRow = prev.row + (dRows * count);
|
||||
const newCol = prev.col + (dCols * count);
|
||||
|
||||
if (newRow < 0 || newRow > 8 || newCol < 0 || newCol > 8) {
|
||||
flashInvalid();
|
||||
return prev;
|
||||
}
|
||||
|
||||
return { row: newRow, col: newCol };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const moveLeft = useCallback((count = 1) => {
|
||||
move(0, -1, count);
|
||||
}, [move]);
|
||||
|
||||
const moveRight = useCallback((count = 1) => {
|
||||
move(0, 1, count);
|
||||
}, [move]);
|
||||
|
||||
const moveUp = useCallback((count = 1) => {
|
||||
move(-1, 0, count);
|
||||
}, [move]);
|
||||
|
||||
const moveDown = useCallback((count = 1) => {
|
||||
move(1, 0, count);
|
||||
}, [move]);
|
||||
|
||||
const moveToStart = useCallback(() => {
|
||||
setPosition(prev => ({ ...prev, col: 0 }));
|
||||
}, []);
|
||||
|
||||
const moveToEnd = useCallback(() => {
|
||||
setPosition(prev => ({ ...prev, col: 8 }));
|
||||
}, []);
|
||||
|
||||
const moveToTop = useCallback(() => {
|
||||
setPosition({ row: 0, col: 0 });
|
||||
}, []);
|
||||
|
||||
const moveToBottom = useCallback(() => {
|
||||
setPosition({ row: 8, col: 0 });
|
||||
}, []);
|
||||
|
||||
const repeatLastMotion = useCallback(() => {
|
||||
const lastMotion = vimMotions.repeatLastMotion();
|
||||
if (lastMotion) {
|
||||
move(lastMotion.dr, lastMotion.dc, lastMotion.steps);
|
||||
}
|
||||
}, [vimMotions, move]);
|
||||
|
||||
const resetCount = useCallback(() => {
|
||||
vimMotions.resetCount();
|
||||
}, [vimMotions]);
|
||||
|
||||
const getCount = useCallback(() => {
|
||||
return vimMotions.getCount().toString();
|
||||
}, [vimMotions]);
|
||||
|
||||
const hasCount = useCallback(() => {
|
||||
return vimMotions.hasCount();
|
||||
}, [vimMotions]);
|
||||
|
||||
const setCount = useCallback((digit: string) => {
|
||||
vimMotions.processKey(digit);
|
||||
}, [vimMotions]);
|
||||
|
||||
const setLastKey = useCallback((key: string) => {
|
||||
addKeyToLog(key);
|
||||
}, [addKeyToLog]);
|
||||
|
||||
const getLastKey = useCallback(() => {
|
||||
return keyLog[keyLog.length - 1]?.key || '';
|
||||
}, [keyLog]);
|
||||
|
||||
const flashInvalid = useCallback(() => {
|
||||
const container = board.containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const cellEl = container.querySelector(`.sudoku-cell[data-row="${position.row}"][data-col="${position.col}"]`) as HTMLElement;
|
||||
if (cellEl) {
|
||||
cellEl.classList.add('invalid-move');
|
||||
setTimeout(() => cellEl.classList.remove('invalid-move'), 180);
|
||||
}
|
||||
}, [board, position]);
|
||||
|
||||
const flashValid = useCallback(() => {
|
||||
const container = board.containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const cellEl = container.querySelector(`.sudoku-cell[data-row="${position.row}"][data-col="${position.col}"]`) as HTMLElement;
|
||||
if (cellEl) {
|
||||
cellEl.classList.add('valid-move');
|
||||
setTimeout(() => cellEl.classList.remove('valid-move'), 300);
|
||||
}
|
||||
}, [board, position]);
|
||||
|
||||
const flashLocked = useCallback(() => {
|
||||
const container = board.containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const cellEl = container.querySelector(`.sudoku-cell[data-row="${position.row}"][data-col="${position.col}"]`) as HTMLElement;
|
||||
if (cellEl) {
|
||||
cellEl.classList.add('locked-cell');
|
||||
setTimeout(() => cellEl.classList.remove('locked-cell'), 300);
|
||||
}
|
||||
}, [board, position]);
|
||||
|
||||
const highlightConflicts = useCallback((conflictIndices: number[]) => {
|
||||
const container = board.containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
conflictIndices.forEach((idx) => {
|
||||
const row = Math.floor(idx / 9);
|
||||
const col = idx % 9;
|
||||
const cellEl = container.querySelector(`.sudoku-cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
|
||||
if (cellEl) {
|
||||
cellEl.classList.add('conflict');
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
conflictIndices.forEach((idx) => {
|
||||
const row = Math.floor(idx / 9);
|
||||
const col = idx % 9;
|
||||
const cellEl = container.querySelector(`.sudoku-cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
|
||||
if (cellEl) {
|
||||
cellEl.classList.remove('conflict');
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
}, [board]);
|
||||
|
||||
const clearKeyLog = useCallback(() => {
|
||||
setKeyLog([]);
|
||||
}, []);
|
||||
|
||||
const getKeyLog = useCallback(() => {
|
||||
return [...keyLog];
|
||||
}, [keyLog]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const key = e.key;
|
||||
const result = vimMotions.processKey(key);
|
||||
|
||||
if (!result) return;
|
||||
|
||||
addKeyToLog(key);
|
||||
|
||||
switch (result.type) {
|
||||
case 'move':
|
||||
move(result.data.dr, result.data.dc, result.data.steps);
|
||||
break;
|
||||
|
||||
case 'anchor':
|
||||
switch (result.data) {
|
||||
case 'start':
|
||||
moveToStart();
|
||||
break;
|
||||
case 'end':
|
||||
moveToEnd();
|
||||
break;
|
||||
case 'top':
|
||||
moveToTop();
|
||||
break;
|
||||
case 'bottom':
|
||||
moveToBottom();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'repeat':
|
||||
if (result.data) {
|
||||
move(result.data.dr, result.data.dc, result.data.steps);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mode':
|
||||
setMode(result.data);
|
||||
break;
|
||||
|
||||
case 'digit':
|
||||
if (mode === 'edit') {
|
||||
const success = board.setCell(position.row, position.col, result.data);
|
||||
if (success) {
|
||||
flashValid();
|
||||
} else if (!board.canEditCell(position.row, position.col)) {
|
||||
flashLocked();
|
||||
} else {
|
||||
const conflicts = board.getConflicts(position.row, position.col);
|
||||
if (conflicts.length > 0) {
|
||||
highlightConflicts(conflicts);
|
||||
}
|
||||
flashInvalid();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'erase':
|
||||
if (mode === 'edit') {
|
||||
const success = board.setCell(position.row, position.col, '.');
|
||||
if (success) {
|
||||
flashValid();
|
||||
} else if (!board.canEditCell(position.row, position.col)) {
|
||||
flashLocked();
|
||||
} else {
|
||||
flashInvalid();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [vimMotions, addKeyToLog, move, moveToStart, moveToEnd, moveToTop, moveToBottom, mode, board, position, flashValid, flashLocked, flashInvalid, highlightConflicts]);
|
||||
|
||||
const cursorManager: CursorManager = {
|
||||
position: () => position,
|
||||
mode: () => mode,
|
||||
move,
|
||||
moveLeft,
|
||||
moveRight,
|
||||
moveUp,
|
||||
moveDown,
|
||||
moveToStart,
|
||||
moveToEnd,
|
||||
moveToTop,
|
||||
moveToBottom,
|
||||
repeatLastMotion,
|
||||
resetCount,
|
||||
getCount,
|
||||
hasCount,
|
||||
setCount,
|
||||
setLastKey,
|
||||
getLastKey,
|
||||
setMode,
|
||||
flashInvalid,
|
||||
flashValid,
|
||||
flashLocked,
|
||||
highlightConflicts
|
||||
};
|
||||
|
||||
const keyManager: GameKeyManager = {
|
||||
keyLog,
|
||||
clearKeyLog,
|
||||
getKeyLog,
|
||||
editMode: mode,
|
||||
setEditMode: setMode
|
||||
};
|
||||
|
||||
return {
|
||||
...cursorManager,
|
||||
keyManager
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user