diff --git a/src/components/Analysis/AnalysisGameList.tsx b/src/components/Analysis/AnalysisGameList.tsx index 7fc6e984..5c9007ba 100644 --- a/src/components/Analysis/AnalysisGameList.tsx +++ b/src/components/Analysis/AnalysisGameList.tsx @@ -681,17 +681,11 @@ export const AnalysisGameList: React.FC = ({ getCurrentGames().length === 0 && !loading && (
- - star - -

+

{selected === 'favorites' - ? 'Hit the star to favorite games...' - : 'Play more games...'} + ? ' ⭐ Hit the star to favourite games...' + : 'Play more games... ^. .^₎⟆'}

-

₍^. .^₎⟆

)} diff --git a/src/components/Analysis/ConfigurableScreens.tsx b/src/components/Analysis/ConfigurableScreens.tsx index 85776931..1447afd6 100644 --- a/src/components/Analysis/ConfigurableScreens.tsx +++ b/src/components/Analysis/ConfigurableScreens.tsx @@ -1,8 +1,14 @@ import { motion } from 'framer-motion' import React, { useState } from 'react' import { ConfigureAnalysis } from 'src/components/Analysis/ConfigureAnalysis' +import { LearnFromMistakes } from 'src/components/Analysis/LearnFromMistakes' import { ExportGame } from 'src/components/Common/ExportGame' -import { AnalyzedGame, GameNode } from 'src/types' +import { + AnalyzedGame, + GameNode, + LearnFromMistakesState, + MistakePosition, +} from 'src/types' interface Props { currentMaiaModel: string @@ -13,12 +19,26 @@ interface Props { currentNode: GameNode onDeleteCustomGame?: () => void onAnalyzeEntireGame?: () => void + onLearnFromMistakes?: () => void isAnalysisInProgress?: boolean + isLearnFromMistakesActive?: boolean autoSave?: { hasUnsavedChanges: boolean isSaving: boolean status: 'saving' | 'unsaved' | 'saved' } + // Learn from mistakes props + learnFromMistakesState?: LearnFromMistakesState + learnFromMistakesCurrentInfo?: { + mistake: MistakePosition + progress: string + isLastMistake: boolean + } | null + onShowSolution?: () => void + onNextMistake?: () => void + onStopLearnFromMistakes?: () => void + onSelectPlayer?: (color: 'white' | 'black') => void + lastMoveResult?: 'correct' | 'incorrect' | 'not-learning' } export const ConfigurableScreens: React.FC = ({ @@ -30,8 +50,17 @@ export const ConfigurableScreens: React.FC = ({ currentNode, onDeleteCustomGame, onAnalyzeEntireGame, + onLearnFromMistakes, isAnalysisInProgress, + isLearnFromMistakesActive, autoSave, + learnFromMistakesState, + learnFromMistakesCurrentInfo, + onShowSolution, + onNextMistake, + onStopLearnFromMistakes, + onSelectPlayer, + lastMoveResult, }) => { const screens = [ { @@ -46,6 +75,50 @@ export const ConfigurableScreens: React.FC = ({ const [screen, setScreen] = useState(screens[0]) + // If learn from mistakes is active, show only the learning interface + if ( + isLearnFromMistakesActive && + learnFromMistakesState && + (learnFromMistakesCurrentInfo || learnFromMistakesState.showPlayerSelection) + ) { + return ( +
+
+ { + /* no-op */ + }) + } + onNext={ + onNextMistake || + (() => { + /* no-op */ + }) + } + onStop={ + onStopLearnFromMistakes || + (() => { + /* no-op */ + }) + } + onSelectPlayer={ + onSelectPlayer || + (() => { + /* no-op */ + }) + } + lastMoveResult={lastMoveResult || 'not-learning'} + /> +
+
+ ) + } + + // Normal state with configure/export tabs return (
@@ -87,7 +160,9 @@ export const ConfigurableScreens: React.FC = ({ game={game} onDeleteCustomGame={onDeleteCustomGame} onAnalyzeEntireGame={onAnalyzeEntireGame} + onLearnFromMistakes={onLearnFromMistakes} isAnalysisInProgress={isAnalysisInProgress} + isLearnFromMistakesActive={isLearnFromMistakesActive} autoSave={autoSave} /> ) : screen.id === 'export' ? ( diff --git a/src/components/Analysis/ConfigureAnalysis.tsx b/src/components/Analysis/ConfigureAnalysis.tsx index fe7b6e88..601137bb 100644 --- a/src/components/Analysis/ConfigureAnalysis.tsx +++ b/src/components/Analysis/ConfigureAnalysis.tsx @@ -11,7 +11,9 @@ interface Props { game: AnalyzedGame onDeleteCustomGame?: () => void onAnalyzeEntireGame?: () => void + onLearnFromMistakes?: () => void isAnalysisInProgress?: boolean + isLearnFromMistakesActive?: boolean autoSave?: { hasUnsavedChanges: boolean isSaving: boolean @@ -27,7 +29,9 @@ export const ConfigureAnalysis: React.FC = ({ game, onDeleteCustomGame, onAnalyzeEntireGame, + onLearnFromMistakes, isAnalysisInProgress = false, + isLearnFromMistakesActive = false, autoSave, }: Props) => { const isCustomGame = game.type === 'custom-pgn' || game.type === 'custom-fen' @@ -55,8 +59,8 @@ export const ConfigureAnalysis: React.FC = ({ {onAnalyzeEntireGame && ( )} + {onLearnFromMistakes && ( + + )} {autoSave && game.type !== 'custom-pgn' && game.type !== 'custom-fen' && diff --git a/src/components/Analysis/LearnFromMistakes.tsx b/src/components/Analysis/LearnFromMistakes.tsx new file mode 100644 index 00000000..0c2cf64d --- /dev/null +++ b/src/components/Analysis/LearnFromMistakes.tsx @@ -0,0 +1,223 @@ +import React from 'react' +import Image from 'next/image' +import { LearnFromMistakesState, MistakePosition } from 'src/types/analysis' + +interface Props { + state: LearnFromMistakesState + currentInfo: { + mistake: MistakePosition + progress: string + isLastMistake: boolean + } | null + onShowSolution: () => void + onNext: () => void + onStop: () => void + onSelectPlayer: (color: 'white' | 'black') => void + lastMoveResult?: 'correct' | 'incorrect' | 'not-learning' +} + +export const LearnFromMistakes: React.FC = ({ + state, + currentInfo, + onShowSolution, + onNext, + onStop, + onSelectPlayer, + lastMoveResult, +}) => { + if (!state.isActive) { + return null + } + + // Show player selection dialog + if (state.showPlayerSelection) { + return ( +
+
+
+
+
+ + school + +

+ Learn from your mistakes +

+
+ +
+ +
+

+ Choose which player's mistakes you'd like to learn + from: +

+
+ + +
+
+
+
+
+ ) + } + + if (!currentInfo) { + return null + } + + const { mistake, progress, isLastMistake } = currentInfo + + const getMoveDisplay = () => { + const moveNumber = Math.ceil(mistake.moveIndex / 2) + const isWhiteMove = mistake.playerColor === 'white' + + if (isWhiteMove) { + return `${moveNumber}. ${mistake.san}` + } else { + return `${moveNumber}... ${mistake.san}` + } + } + + const getPromptText = () => { + const mistakeType = mistake.type === 'blunder' ? '??' : '?!' + const moveDisplay = getMoveDisplay() + const playerColorName = mistake.playerColor === 'white' ? 'White' : 'Black' + + return `${moveDisplay}${mistakeType} was played. Find a better move for ${playerColorName}.` + } + + const getFeedbackText = () => { + if (state.showSolution) { + if (lastMoveResult === 'correct') { + return `Correct! ${mistake.bestMoveSan} was the best move.` + } else { + return `The best move was ${mistake.bestMoveSan}.` + } + } + + if (lastMoveResult === 'incorrect') { + const playerColorName = + mistake.playerColor === 'white' ? 'White' : 'Black' + return `You can do better. Try another move for ${playerColorName}.` + } + + return null + } + + return ( +
+
+
+
+
+ + school + +

+ Learn from your mistakes +

+ ({progress}) +
+ +
+ + {/* Main prompt */} +
+

{getPromptText()}

+ {getFeedbackText() && ( +

+ {getFeedbackText()} +

+ )} +
+
+ + {/* Action buttons */} +
+ {!state.showSolution && lastMoveResult !== 'correct' ? ( + <> + + {!isLastMistake && ( + + )} + + ) : ( + + )} +
+
+
+ ) +} diff --git a/src/components/Analysis/index.ts b/src/components/Analysis/index.ts index 4f68a750..fc650a15 100644 --- a/src/components/Analysis/index.ts +++ b/src/components/Analysis/index.ts @@ -12,3 +12,4 @@ export * from './AnalysisNotification' export * from './AnalysisOverlay' export * from './InteractiveDescription' export * from './AnalysisSidebar' +export * from './LearnFromMistakes' diff --git a/src/components/Board/BoardController.tsx b/src/components/Board/BoardController.tsx index b2989a08..bc5c5781 100644 --- a/src/components/Board/BoardController.tsx +++ b/src/components/Board/BoardController.tsx @@ -17,6 +17,7 @@ interface Props { disableFlip?: boolean disablePrevious?: boolean disableKeyboardNavigation?: boolean + disableNavigation?: boolean } export const BoardController: React.FC = ({ @@ -33,6 +34,7 @@ export const BoardController: React.FC = ({ disableFlip = false, disablePrevious = false, disableKeyboardNavigation = false, + disableNavigation = false, }: Props) => { const { width } = useWindowSize() @@ -134,29 +136,29 @@ export const BoardController: React.FC = ({ {FlipIcon}