diff --git a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx index a5a6c48b..5c5cad74 100644 --- a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx +++ b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx @@ -18,6 +18,7 @@ import { Icon, IconButton, Spacer, + Spinner, VStack, } from '../../../../../../../../infrastructures/useClient/chakra'; import type { Problem } from '../../../../../../../../problems/generateProblem'; @@ -33,7 +34,7 @@ const DY = [1, 0, -1, 0]; interface TurtleGraphicsProps { currentTraceItemIndex: number; focusTraceItemIndex: number; - handleClickSubmitButton: () => Promise; + handleSubmit: () => Promise; problem: Problem; } @@ -42,10 +43,11 @@ export interface TurtleGraphicsHandle { } export const BoardEditor = forwardRef( - ({ currentTraceItemIndex, focusTraceItemIndex, handleClickSubmitButton, problem }, ref) => { + ({ currentTraceItemIndex, focusTraceItemIndex, handleSubmit, problem }, ref) => { const [board, updateBoard] = useImmer([]); const [turtles, updateTurtles] = useImmer([]); const [selectedCell, setSelectedCell] = useState(); + const [isSubmitting, setIsSubmitting] = useState(false); const selectedTurtle = turtles.find((char) => char.x === selectedCell?.x && char.y === selectedCell?.y); const focusTraceItem = problem.traceItems[focusTraceItemIndex]; const currentTraceItem = problem.traceItems[currentTraceItemIndex]; @@ -152,13 +154,21 @@ export const BoardEditor = forwardRef }; const onCellRightClick = (x: number, y: number): void => { + if (isSubmitting) return; + onCellClick(x, y); updateCellColor('.', x, y); }; - const selectedPosition = selectedCell ? { x: selectedCell.x, y: selectedCell.y } : undefined; - - useShortcutKeys(handleClickSubmitButton); + const handleSubmitAndToggleSubmitting = useCallback(async () => { + setIsSubmitting(true); + try { + await handleSubmit(); + } finally { + setIsSubmitting(false); + } + }, [handleSubmit]); + useShortcutKeys(handleSubmitAndToggleSubmitting, isSubmitting); return ( @@ -166,7 +176,7 @@ export const BoardEditor = forwardRef cells.join('')).join('\n')} - focusedCell={selectedPosition} + focusedCell={selectedCell} turtles={turtles} onCellClick={onCellClick} onCellRightClick={onCellRightClick} @@ -181,10 +191,10 @@ export const BoardEditor = forwardRef 選択したマス - {selectedPosition ? ( + {selectedCell ? ( <> -
x = {selectedPosition.x}
-
y = {selectedPosition.y}
+
x = {selectedCell.x}
+
y = {selectedCell.y}
) : ( なし @@ -201,6 +211,7 @@ export const BoardEditor = forwardRef colorScheme="brand" gridColumnStart={2} gridRowStart={1} + isDisabled={isSubmitting} size="sm" variant="outline" onClick={() => handleMoveTurtle(true)} @@ -211,6 +222,7 @@ export const BoardEditor = forwardRef colorScheme="brand" gridColumnStart={2} gridRowStart={2} + isDisabled={isSubmitting} size="sm" variant="outline" onClick={() => handleMoveTurtle(false)} @@ -223,6 +235,7 @@ export const BoardEditor = forwardRef gridColumnStart={1} gridRowStart={2} icon={} + isDisabled={isSubmitting} size="sm" variant="outline" onClick={() => handleTurnTurtle(true)} @@ -233,6 +246,7 @@ export const BoardEditor = forwardRef gridColumnStart={3} gridRowStart={2} icon={} + isDisabled={isSubmitting} size="sm" variant="outline" onClick={() => handleTurnTurtle(false)} @@ -241,6 +255,7 @@ export const BoardEditor = forwardRef )} - {selectedPosition && ( + {selectedCell && ( 右クリックでマスを白色に戻せます。 )}
- @@ -301,15 +327,17 @@ function parseBoard(boardString: string): ColorChar[][] { .map((line) => [...line.trim()]) as ColorChar[][]; } -function useShortcutKeys(handleClickSubmitButton: () => Promise): void { +function useShortcutKeys(handleSubmit: () => Promise, isSubmitting: boolean): void { useEffect(() => { + if (isSubmitting) return; + const handleKeyDown = (event: KeyboardEvent): void => { if (event.key === 'Enter') { event.preventDefault(); - void handleClickSubmitButton(); + void handleSubmit(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [handleClickSubmitButton]); + }, [handleSubmit, isSubmitting]); } diff --git a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx index af794da9..50f679ef 100644 --- a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx +++ b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx @@ -70,7 +70,7 @@ export const ProblemBody: React.FC = (props) => { { enabled: false } ); - const handleClickSubmitButton = useCallback(async (): Promise => { + const handleSubmit = useCallback(async (): Promise => { if (isAlertOpen) return; const isCorrect = turtleGraphicsRef.current?.isCorrect() || false; @@ -78,7 +78,7 @@ export const ProblemBody: React.FC = (props) => { switch (problemType) { case 'executionResult': { if (isCorrect) { - void props.createSubmissionUpdatingProblemSession(true, true); + await props.createSubmissionUpdatingProblemSession(true, true); openAlertDialog( '正解', '一発正解です!この問題は完了です。問題一覧ページに戻りますので、次の問題に挑戦してください。', @@ -89,14 +89,14 @@ export const ProblemBody: React.FC = (props) => { } else { const response = await fetchIncorrectSubmissionsCount(); const incorrectCount = (response.data ?? 0) + 1; - void props.createSubmissionUpdatingProblemSession(false, false); + await props.createSubmissionUpdatingProblemSession(false, false); if (incorrectCount < MAX_CHALLENGE_COUNT) { openAlertDialog( '不正解', `不正解です。あと${MAX_CHALLENGE_COUNT - incorrectCount}回間違えたら、ステップ実行モードに移ります。一発正解を目指しましょう!` ); } else { - void props.updateProblemSession('step', 1); + await props.updateProblemSession('step', 1); openAlertDialog( '不正解', `不正解です。${MAX_CHALLENGE_COUNT}回間違えたので、ステップ実行モードに移ります。ステップごとに問題を解いてください。` @@ -106,7 +106,7 @@ export const ProblemBody: React.FC = (props) => { break; } case 'step': { - void props.createSubmissionUpdatingProblemSession( + await props.createSubmissionUpdatingProblemSession( isCorrect, isCorrect && currentTraceItemIndex === props.problem.traceItems.length - 1 ); @@ -120,7 +120,7 @@ export const ProblemBody: React.FC = (props) => { } ); } else { - void props.updateProblemSession('step', currentTraceItemIndex + 1); + await props.updateProblemSession('step', currentTraceItemIndex + 1); openAlertDialog('正解', '正解です。次のステップに進みます。'); setFocusTraceItemIndex(currentTraceItemIndex); } @@ -205,7 +205,7 @@ export const ProblemBody: React.FC = (props) => { ref={turtleGraphicsRef} currentTraceItemIndex={currentTraceItemIndex} focusTraceItemIndex={focusTraceItemIndex} - handleClickSubmitButton={handleClickSubmitButton} + handleSubmit={handleSubmit} problem={props.problem} />