diff --git a/frontend/src/app/test/page.tsx b/frontend/src/app/test/page.tsx index d3d67a0..5062838 100644 --- a/frontend/src/app/test/page.tsx +++ b/frontend/src/app/test/page.tsx @@ -1,8 +1,15 @@ 'use client'; import { Editor } from '@/components/Editor'; +import { Hotkeys } from '@/components/pages/race/Hotkeys/Hotkeys'; +import { ExecutedCommand } from '@vimracing/shared'; +import { useState } from 'react'; export default function TestPage() { + const [executedCommands, setExecutedCommands] = useState( + [] + ); + const [keysCount, setKeysCount] = useState(0); return (
+ setKeysCount((prev) => prev + 1)} + />
); } diff --git a/frontend/src/components/pages/race/Hotkeys/Hotkeys.tsx b/frontend/src/components/pages/race/Hotkeys/Hotkeys.tsx index 9b0216b..40e947c 100644 --- a/frontend/src/components/pages/race/Hotkeys/Hotkeys.tsx +++ b/frontend/src/components/pages/race/Hotkeys/Hotkeys.tsx @@ -8,26 +8,29 @@ import { ExecutedCommand } from '@vimracing/shared'; interface IHotkeysProps { setExecutedCommands?: (ExecutedCommands: ExecutedCommand[]) => void; executedCommands?: ExecutedCommand[]; + keysCount?: number; + onKeyPressed?: (key: string) => void; } export const Hotkeys: React.FC = ({ setExecutedCommands, - executedCommands = [] + executedCommands = [], + keysCount, + onKeyPressed }) => { const [currentCommand, setCurrentCommand] = useState(''); const [partialCommandExecuted, setPartialCommandExecuted] = useState(false); - const [keysCount, setKeysCount] = useState(0); const lastPressedKey = useRef(''); useEffect(() => { const onVimKey = (event: any) => { lastPressedKey.current = event.detail; - setKeysCount((prev) => prev + 1); + onKeyPressed?.(event.detail); }; window.addEventListener('vimracing-key', onVimKey); return () => { window.removeEventListener('vimracing-key', onVimKey); }; - }, []); + }, [onKeyPressed]); useEffect(() => { const onVimCommandDone = (event: any) => { @@ -146,7 +149,7 @@ export const Hotkeys: React.FC = ({ })} - + {!!keysCount && } ); }; @@ -166,6 +169,7 @@ const KeysCounter: React.FC<{ count: number }> = ({ count }) => { }, [animate, count, scope]); if (!count) return null; + return (
diff --git a/frontend/src/components/pages/race/RaceState.tsx b/frontend/src/components/pages/race/RaceState.tsx index c652707..6e9405c 100644 --- a/frontend/src/components/pages/race/RaceState.tsx +++ b/frontend/src/components/pages/race/RaceState.tsx @@ -1,19 +1,25 @@ import { Editor, isTextEqual } from '@/components/Editor'; -import { useState, useCallback, useMemo } from 'react'; +import { useState, useCallback, useMemo, useEffect } from 'react'; import { Players } from './Players'; -import { ExecutedCommand, Player, RaceStatus } from '@vimracing/shared'; +import { + RaceDocs, + ExecutedCommand, + Player, + RaceStatus +} from '@vimracing/shared'; import { Hotkeys } from './Hotkeys/Hotkeys'; import { Recap } from './Recap'; import { Timer } from '@/components/Timer'; import { getPostfixedPlace } from '@/utils/postfix'; -import { RaceDocs } from '@vimracing/shared'; interface RaceStateProps { raceDocs: RaceDocs; onDocChange: ( newDoc: string[], documentIndex: number, - executedCommands?: ExecutedCommand[] + executedCommands?: ExecutedCommand[], + secondsSinceStart?: number, + keysCount?: number ) => void; players?: Player[]; currentPlayer?: Player; @@ -32,41 +38,60 @@ export const RaceState: React.FC = ({ const [docExecutedCommands, setDocExecutedCommands] = useState< ExecutedCommand[] >([]); + const [keysCount, setKeysCount] = useState(0); const [documentIndex, setDocumentIndex] = useState( currentPlayer?.raceData?.currentDocIndex ?? 0 ); + const [currentDocumentDoc, setCurrentDocumentDoc] = useState( + raceDocs[documentIndex].start + ); + const [hasDocumentChange, setHasDocumentChange] = useState(false); const isDocFinished = (current: string[], target: string[]) => { return isTextEqual(current, target); }; - const onCurrentDocumentChange = useCallback( - (newDoc: string[]) => { - const isEditingCurrentDoc = !isDocFinished( - newDoc, - raceDocs[documentIndex].target - ); - if (isEditingCurrentDoc) { - onDocChange(newDoc, documentIndex); - return; - } + const onCurrentDocumentChange = useCallback((newDoc: string[]) => { + setCurrentDocumentDoc(newDoc); + setHasDocumentChange(true); + }, []); - // current doc finished, move to next doc - const isLastDocument = documentIndex === raceDocs.length - 1; - setDocumentIndex(documentIndex + 1); - setDocExecutedCommands((prev) => { - console.log({ prev, documentIndex }); + useEffect(() => { + if (!hasDocumentChange) return; + setHasDocumentChange(false); + const isEditingCurrentDoc = !isDocFinished( + currentDocumentDoc, + raceDocs[documentIndex].target + ); - onDocChange( - newDoc, - isLastDocument ? documentIndex : documentIndex + 1, - prev - ); - return []; - }); - }, - [documentIndex, onDocChange, raceDocs] - ); + if (isEditingCurrentDoc) { + onDocChange(currentDocumentDoc, documentIndex); + return; + } + + // current doc finished, move to next doc + const isLastDocument = documentIndex === raceDocs.length - 1; + setDocumentIndex(documentIndex + 1); + setDocExecutedCommands([]); + setKeysCount(0); + + onDocChange( + currentDocumentDoc, + isLastDocument ? documentIndex : documentIndex + 1, + docExecutedCommands, + raceTimer, + keysCount + ); + }, [ + currentDocumentDoc, + docExecutedCommands, + documentIndex, + hasDocumentChange, + keysCount, + onDocChange, + raceDocs, + raceTimer + ]); const isCurrentPlayerFinished = useMemo( () => @@ -132,6 +157,8 @@ export const RaceState: React.FC = ({ setExecutedCommands={setDocExecutedCommands} executedCommands={docExecutedCommands} key={documentIndex} + keysCount={keysCount} + onKeyPressed={() => setKeysCount(keysCount + 1)} /> )} diff --git a/frontend/src/components/pages/race/Recap.tsx b/frontend/src/components/pages/race/Recap.tsx index 152584b..f70d4fb 100644 --- a/frontend/src/components/pages/race/Recap.tsx +++ b/frontend/src/components/pages/race/Recap.tsx @@ -78,8 +78,8 @@ export const Recap: React.FC = ({ raceDocs, players }) => { const finishedPlayers = players.filter( (player) => - player.raceData?.executedCommands?.length && - player.raceData?.executedCommands?.length > 0 + player.raceData?.completedDocs?.length && + player.raceData?.completedDocs?.length > 0 ); return ( @@ -95,7 +95,10 @@ export const Recap: React.FC = ({ raceDocs, players }) => { )} {finishedPlayers.map((player) => { - if (!player.raceData?.executedCommands?.[currentRecapTaskIndex]) + if ( + !player.raceData?.completedDocs?.[currentRecapTaskIndex] + ?.executedCommands + ) return null; return (
@@ -104,7 +107,8 @@ export const Recap: React.FC = ({ raceDocs, players }) => {
diff --git a/frontend/src/hooks/useRace.ts b/frontend/src/hooks/useRace.ts index 4d62d20..9d556bc 100644 --- a/frontend/src/hooks/useRace.ts +++ b/frontend/src/hooks/useRace.ts @@ -181,14 +181,24 @@ export const useRace = (raceId: string) => { ( newDocument: string[], documentIndex: number, - executedCommands?: ExecutedCommand[] + executedCommands?: ExecutedCommand[], + secondsSinceStart?: number, + keysCount?: number ) => { const payload: FrontendDocumentChangeEvent = { event: FrontendEventType.DOCUMENT_CHANGE, payload: { docIndex: documentIndex, newDocument, - executedCommands + ...(executedCommands && + secondsSinceStart && + keysCount !== undefined && { + sharedDocPayload: { + executedCommands, + seconds: secondsSinceStart, + keysCount + } + }) } }; socketConnection.current?.send(JSON.stringify(payload));