Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/Analysis/AnalysisGameList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,7 @@ export const AnalysisGameList: React.FC<AnalysisGameListProps> = ({
selectedGameElement as React.RefObject<HTMLButtonElement>
}
analysisTournamentList={analysisTournamentList}
onGameSelected={onGameSelected}
/>
))}
</>
Expand Down
15 changes: 13 additions & 2 deletions src/components/Analysis/Tournament.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Props = {
selectedGameElement: React.RefObject<HTMLButtonElement>
analysisTournamentList: Map<string, WorldChampionshipGameListEntry[]>
setCurrentMove?: Dispatch<SetStateAction<number>>
onGameSelected?: () => void
}

export const Tournament = ({
Expand All @@ -26,6 +27,7 @@ export const Tournament = ({
setLoadingIndex,
selectedGameElement,
analysisTournamentList,
onGameSelected,
}: Props) => {
const router = useRouter()
const games = analysisTournamentList.get(id)
Expand Down Expand Up @@ -59,10 +61,18 @@ export const Tournament = ({
className={`flex w-full flex-col ${openIndex === index ? 'block' : 'hidden'}`}
>
{games?.map((game, j) => {
const routeType = currentId?.[1]
const isTypedGameRoute =
routeType === 'lichess' ||
routeType === 'play' ||
routeType === 'hand' ||
routeType === 'brain' ||
routeType === 'custom' ||
routeType === 'stream'
const selected =
currentId && currentId[1] == 'tournament'
currentId && !isTypedGameRoute
? sectionId == currentId[0] &&
game.game_index == Number.parseInt(currentId[1])
game.game_index == Number.parseInt(currentId[1], 10)
: false
return (
<button
Expand All @@ -71,6 +81,7 @@ export const Tournament = ({
onClick={() => {
setLoadingIndex(j)
router.push(`/analysis/${sectionId}/${game.game_index}`)
onGameSelected?.()
}}
ref={selected && opened ? selectedGameElement : null}
>
Expand Down
14 changes: 10 additions & 4 deletions src/components/Board/BoardController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface Props {
disablePrevious?: boolean
disableKeyboardNavigation?: boolean
disableNavigation?: boolean
onNavigate?: () => void
embedded?: boolean
}

Expand All @@ -36,6 +37,7 @@ export const BoardController: React.FC<Props & { embedded?: boolean }> = ({
disablePrevious = false,
disableKeyboardNavigation = false,
disableNavigation = false,
onNavigate,
embedded = false,
}: Props) => {
const { width } = useWindowSize()
Expand All @@ -55,17 +57,20 @@ export const BoardController: React.FC<Props & { embedded?: boolean }> = ({
const getFirst = useCallback(() => {
goToRootNode()
setCurrentMove?.(null)
}, [goToRootNode, setCurrentMove])
onNavigate?.()
}, [goToRootNode, onNavigate, setCurrentMove])

const getPrevious = useCallback(() => {
goToPreviousNode()
setCurrentMove?.(null)
}, [goToPreviousNode, setCurrentMove])
onNavigate?.()
}, [goToPreviousNode, onNavigate, setCurrentMove])

const getNext = useCallback(() => {
goToNextNode()
setCurrentMove?.(null)
}, [goToNextNode, setCurrentMove])
onNavigate?.()
}, [goToNextNode, onNavigate, setCurrentMove])

const getLast = useCallback(() => {
if (!currentNode) return
Expand All @@ -79,7 +84,8 @@ export const BoardController: React.FC<Props & { embedded?: boolean }> = ({
}

setCurrentMove?.(null)
}, [currentNode, goToNode, setCurrentMove])
onNavigate?.()
}, [currentNode, goToNode, onNavigate, setCurrentMove])

useEffect(() => {
if (width <= 670 || disableKeyboardNavigation) return
Expand Down
11 changes: 11 additions & 0 deletions src/components/Board/MovesContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface Props {
startFromNode?: GameNode
restrictNavigationBefore?: GameNode
forceMobileLayout?: boolean
scrollToTopSignal?: number
}

const getMoveClassification = (node: GameNode | null) => {
Expand Down Expand Up @@ -52,6 +53,7 @@ export const MovesContainer: React.FC<
startFromNode,
restrictNavigationBefore,
forceMobileLayout,
scrollToTopSignal,
embedded = true,
heightClass = 'h-48',
} = props as Props & { embedded?: boolean; heightClass?: string }
Expand Down Expand Up @@ -169,6 +171,15 @@ export const MovesContainer: React.FC<
}
}, [controller.currentNode])

useEffect(() => {
if (!containerRef.current || scrollToTopSignal === undefined) return

containerRef.current.scrollTo({
top: 0,
left: 0,
})
}, [scrollToTopSignal])

const moves = useMemo(() => {
// When using startFromNode, we want to show moves AFTER that node
// When using default behavior, we want to skip the root node (slice(1))
Expand Down
172 changes: 103 additions & 69 deletions src/pages/analysis/[...id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,32 @@ const Analysis: React.FC<Props> = ({
const [showAnalysisConfigModal, setShowAnalysisConfigModal] = useState(false)
const [refreshTrigger, setRefreshTrigger] = useState(0)
const [analysisEnabled, setAnalysisEnabled] = useState(true)
const [
desktopMoveListScrollResetSignal,
setDesktopMoveListScrollResetSignal,
] = useState(0)
const [lastMoveResult, setLastMoveResult] = useState<
'correct' | 'incorrect' | 'not-learning'
>('not-learning')

const controller = useAnalysisController(analyzedGame)
const userGamePovOrientation = useMemo(() => {
if (!['play', 'hand', 'brain'].includes(analyzedGame.type)) {
return null
}

const whiteName = analyzedGame.whitePlayer.name.toLowerCase()
const blackName = analyzedGame.blackPlayer.name.toLowerCase()

if (whiteName.includes('maia')) return 'black'
if (blackName.includes('maia')) return 'white'

return null
}, [
analyzedGame.blackPlayer.name,
analyzedGame.type,
analyzedGame.whitePlayer.name,
])
const destinationBadges = useMemo(() => {
if (
!analysisEnabled ||
Expand Down Expand Up @@ -522,6 +543,11 @@ const Analysis: React.FC<Props> = ({
}
}, [analyzedGame])

useEffect(() => {
if (!userGamePovOrientation) return
controller.setOrientation(userGamePovOrientation)
}, [analyzedGame.tree, controller.setOrientation, userGamePovOrientation])

useEffect(() => {
setHoverArrow(null)
}, [controller.currentNode])
Expand Down Expand Up @@ -1194,6 +1220,12 @@ const Analysis: React.FC<Props> = ({
const [desktopLeftPanelTab, setDesktopLeftPanelTab] = useState<
(typeof desktopLeftPanelTabs)[number]['id']
>(desktopLeftPanelTabs[0].id)
const showDesktopMovesAfterNavigation = useCallback(() => {
if (desktopLeftPanelTab !== 'select-game') return

setDesktopLeftPanelTab('moves')
setDesktopMoveListScrollResetSignal((prev) => prev + 1)
}, [desktopLeftPanelTab])

useEffect(() => {
if (useMobileStyleAnalysisLayout || desktopLeftPanelTab !== 'select-game') {
Expand Down Expand Up @@ -1222,15 +1254,15 @@ const Analysis: React.FC<Props> = ({
event.preventDefault()
event.stopPropagation()
controller.goToPreviousNode()
setDesktopLeftPanelTab('moves')
showDesktopMovesAfterNavigation()
return
}

if (event.key === 'ArrowRight' && controller.currentNode?.mainChild) {
event.preventDefault()
event.stopPropagation()
controller.goToNextNode()
setDesktopLeftPanelTab('moves')
showDesktopMovesAfterNavigation()
}
}

Expand All @@ -1244,6 +1276,7 @@ const Analysis: React.FC<Props> = ({
controller.goToPreviousNode,
controller.learnFromMistakes.state.isActive,
desktopLeftPanelTab,
showDesktopMovesAfterNavigation,
useMobileStyleAnalysisLayout,
])

Expand Down Expand Up @@ -1457,74 +1490,75 @@ const Analysis: React.FC<Props> = ({
)
})}
</div>
<div className="relative flex min-h-0 flex-1 overflow-hidden">
<div
aria-hidden={desktopLeftPanelTab !== 'moves'}
className={`red-scrollbar absolute inset-0 flex h-full flex-col overflow-y-auto transition-opacity duration-150 ${
desktopLeftPanelTab === 'moves'
? 'visible opacity-100'
: 'pointer-events-none invisible opacity-0'
}`}
>
<MovesContainer
game={analyzedGame}
termination={analyzedGame.termination}
showAnnotations={true}
disableKeyboardNavigation={
controller.gameAnalysis.progress.isAnalyzing ||
controller.learnFromMistakes.state.isActive
}
disableMoveClicking={
controller.learnFromMistakes.state.isActive
}
embedded
heightClass="h-40"
/>
{/* No spacer here to keep controller tight to moves */}
<BoardController
gameTree={controller.gameTree}
orientation={controller.orientation}
setOrientation={controller.setOrientation}
currentNode={controller.currentNode}
plyCount={controller.plyCount}
goToNode={controller.goToNode}
goToNextNode={controller.goToNextNode}
goToPreviousNode={controller.goToPreviousNode}
goToRootNode={controller.goToRootNode}
disableKeyboardNavigation={
controller.gameAnalysis.progress.isAnalyzing ||
controller.learnFromMistakes.state.isActive
}
disableNavigation={
controller.learnFromMistakes.state.isActive
}
embedded
/>
</div>
<div
aria-hidden={desktopLeftPanelTab !== 'select-game'}
className={`absolute inset-0 flex min-h-0 flex-col transition-opacity duration-150 ${
desktopLeftPanelTab === 'select-game'
? 'visible opacity-100'
: 'pointer-events-none invisible opacity-0'
}`}
>
<AnalysisGameList
currentId={currentId}
loadNewWorldChampionshipGame={(newId, setCurrentMove) =>
getAndSetTournamentGame(newId, setCurrentMove)
}
loadNewLichessGame={(id, pgn, setCurrentMove) =>
getAndSetLichessGame(id, pgn, setCurrentMove)
}
loadNewMaiaGame={(id, type, setCurrentMove) =>
getAndSetUserGame(id, type, setCurrentMove)
}
onCustomAnalysis={() => setShowCustomModal(true)}
refreshTrigger={refreshTrigger}
embedded
/>
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
<div className="relative min-h-0 flex-1 overflow-hidden">
<div
aria-hidden={desktopLeftPanelTab !== 'moves'}
className={`absolute inset-0 flex h-full flex-col transition-opacity duration-150 ${
desktopLeftPanelTab === 'moves'
? 'visible opacity-100'
: 'pointer-events-none invisible opacity-0'
}`}
>
<MovesContainer
game={analyzedGame}
termination={analyzedGame.termination}
showAnnotations={true}
disableKeyboardNavigation={
controller.gameAnalysis.progress.isAnalyzing ||
controller.learnFromMistakes.state.isActive
}
disableMoveClicking={
controller.learnFromMistakes.state.isActive
}
scrollToTopSignal={desktopMoveListScrollResetSignal}
embedded
heightClass="h-40"
/>
</div>
<div
aria-hidden={desktopLeftPanelTab !== 'select-game'}
className={`absolute inset-0 flex min-h-0 flex-col transition-opacity duration-150 ${
desktopLeftPanelTab === 'select-game'
? 'visible opacity-100'
: 'pointer-events-none invisible opacity-0'
}`}
>
<AnalysisGameList
currentId={currentId}
loadNewWorldChampionshipGame={(newId, setCurrentMove) =>
getAndSetTournamentGame(newId, setCurrentMove)
}
loadNewLichessGame={(id, pgn, setCurrentMove) =>
getAndSetLichessGame(id, pgn, setCurrentMove)
}
loadNewMaiaGame={(id, type, setCurrentMove) =>
getAndSetUserGame(id, type, setCurrentMove)
}
onCustomAnalysis={() => setShowCustomModal(true)}
refreshTrigger={refreshTrigger}
embedded
/>
</div>
</div>
<BoardController
gameTree={controller.gameTree}
orientation={controller.orientation}
setOrientation={controller.setOrientation}
currentNode={controller.currentNode}
plyCount={controller.plyCount}
goToNode={controller.goToNode}
goToNextNode={controller.goToNextNode}
goToPreviousNode={controller.goToPreviousNode}
goToRootNode={controller.goToRootNode}
disableKeyboardNavigation={
controller.gameAnalysis.progress.isAnalyzing ||
controller.learnFromMistakes.state.isActive
}
disableNavigation={controller.learnFromMistakes.state.isActive}
onNavigate={showDesktopMovesAfterNavigation}
embedded
/>
</div>
</div>
</motion.div>
Expand Down
Loading