Skip to content

Commit 2c0bf16

Browse files
Merge pull request #241 from CSSLab/codex/desktop-analysis-select-game-behavior
Refine desktop analysis game selection flow
2 parents a77aad0 + 0524b20 commit 2c0bf16

5 files changed

Lines changed: 138 additions & 75 deletions

File tree

src/components/Analysis/AnalysisGameList.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ export const AnalysisGameList: React.FC<AnalysisGameListProps> = ({
898898
selectedGameElement as React.RefObject<HTMLButtonElement>
899899
}
900900
analysisTournamentList={analysisTournamentList}
901+
onGameSelected={onGameSelected}
901902
/>
902903
))}
903904
</>

src/components/Analysis/Tournament.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Props = {
1313
selectedGameElement: React.RefObject<HTMLButtonElement>
1414
analysisTournamentList: Map<string, WorldChampionshipGameListEntry[]>
1515
setCurrentMove?: Dispatch<SetStateAction<number>>
16+
onGameSelected?: () => void
1617
}
1718

1819
export const Tournament = ({
@@ -26,6 +27,7 @@ export const Tournament = ({
2627
setLoadingIndex,
2728
selectedGameElement,
2829
analysisTournamentList,
30+
onGameSelected,
2931
}: Props) => {
3032
const router = useRouter()
3133
const games = analysisTournamentList.get(id)
@@ -59,10 +61,18 @@ export const Tournament = ({
5961
className={`flex w-full flex-col ${openIndex === index ? 'block' : 'hidden'}`}
6062
>
6163
{games?.map((game, j) => {
64+
const routeType = currentId?.[1]
65+
const isTypedGameRoute =
66+
routeType === 'lichess' ||
67+
routeType === 'play' ||
68+
routeType === 'hand' ||
69+
routeType === 'brain' ||
70+
routeType === 'custom' ||
71+
routeType === 'stream'
6272
const selected =
63-
currentId && currentId[1] == 'tournament'
73+
currentId && !isTypedGameRoute
6474
? sectionId == currentId[0] &&
65-
game.game_index == Number.parseInt(currentId[1])
75+
game.game_index == Number.parseInt(currentId[1], 10)
6676
: false
6777
return (
6878
<button
@@ -71,6 +81,7 @@ export const Tournament = ({
7181
onClick={() => {
7282
setLoadingIndex(j)
7383
router.push(`/analysis/${sectionId}/${game.game_index}`)
84+
onGameSelected?.()
7485
}}
7586
ref={selected && opened ? selectedGameElement : null}
7687
>

src/components/Board/BoardController.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface Props {
1818
disablePrevious?: boolean
1919
disableKeyboardNavigation?: boolean
2020
disableNavigation?: boolean
21+
onNavigate?: () => void
2122
embedded?: boolean
2223
}
2324

@@ -36,6 +37,7 @@ export const BoardController: React.FC<Props & { embedded?: boolean }> = ({
3637
disablePrevious = false,
3738
disableKeyboardNavigation = false,
3839
disableNavigation = false,
40+
onNavigate,
3941
embedded = false,
4042
}: Props) => {
4143
const { width } = useWindowSize()
@@ -55,17 +57,20 @@ export const BoardController: React.FC<Props & { embedded?: boolean }> = ({
5557
const getFirst = useCallback(() => {
5658
goToRootNode()
5759
setCurrentMove?.(null)
58-
}, [goToRootNode, setCurrentMove])
60+
onNavigate?.()
61+
}, [goToRootNode, onNavigate, setCurrentMove])
5962

6063
const getPrevious = useCallback(() => {
6164
goToPreviousNode()
6265
setCurrentMove?.(null)
63-
}, [goToPreviousNode, setCurrentMove])
66+
onNavigate?.()
67+
}, [goToPreviousNode, onNavigate, setCurrentMove])
6468

6569
const getNext = useCallback(() => {
6670
goToNextNode()
6771
setCurrentMove?.(null)
68-
}, [goToNextNode, setCurrentMove])
72+
onNavigate?.()
73+
}, [goToNextNode, onNavigate, setCurrentMove])
6974

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

8186
setCurrentMove?.(null)
82-
}, [currentNode, goToNode, setCurrentMove])
87+
onNavigate?.()
88+
}, [currentNode, goToNode, onNavigate, setCurrentMove])
8389

8490
useEffect(() => {
8591
if (width <= 670 || disableKeyboardNavigation) return

src/components/Board/MovesContainer.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface Props {
1717
startFromNode?: GameNode
1818
restrictNavigationBefore?: GameNode
1919
forceMobileLayout?: boolean
20+
scrollToTopSignal?: number
2021
}
2122

2223
const getMoveClassification = (node: GameNode | null) => {
@@ -52,6 +53,7 @@ export const MovesContainer: React.FC<
5253
startFromNode,
5354
restrictNavigationBefore,
5455
forceMobileLayout,
56+
scrollToTopSignal,
5557
embedded = true,
5658
heightClass = 'h-48',
5759
} = props as Props & { embedded?: boolean; heightClass?: string }
@@ -169,6 +171,15 @@ export const MovesContainer: React.FC<
169171
}
170172
}, [controller.currentNode])
171173

174+
useEffect(() => {
175+
if (!containerRef.current || scrollToTopSignal === undefined) return
176+
177+
containerRef.current.scrollTo({
178+
top: 0,
179+
left: 0,
180+
})
181+
}, [scrollToTopSignal])
182+
172183
const moves = useMemo(() => {
173184
// When using startFromNode, we want to show moves AFTER that node
174185
// When using default behavior, we want to skip the root node (slice(1))

src/pages/analysis/[...id].tsx

Lines changed: 103 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -483,11 +483,32 @@ const Analysis: React.FC<Props> = ({
483483
const [showAnalysisConfigModal, setShowAnalysisConfigModal] = useState(false)
484484
const [refreshTrigger, setRefreshTrigger] = useState(0)
485485
const [analysisEnabled, setAnalysisEnabled] = useState(true)
486+
const [
487+
desktopMoveListScrollResetSignal,
488+
setDesktopMoveListScrollResetSignal,
489+
] = useState(0)
486490
const [lastMoveResult, setLastMoveResult] = useState<
487491
'correct' | 'incorrect' | 'not-learning'
488492
>('not-learning')
489493

490494
const controller = useAnalysisController(analyzedGame)
495+
const userGamePovOrientation = useMemo(() => {
496+
if (!['play', 'hand', 'brain'].includes(analyzedGame.type)) {
497+
return null
498+
}
499+
500+
const whiteName = analyzedGame.whitePlayer.name.toLowerCase()
501+
const blackName = analyzedGame.blackPlayer.name.toLowerCase()
502+
503+
if (whiteName.includes('maia')) return 'black'
504+
if (blackName.includes('maia')) return 'white'
505+
506+
return null
507+
}, [
508+
analyzedGame.blackPlayer.name,
509+
analyzedGame.type,
510+
analyzedGame.whitePlayer.name,
511+
])
491512
const destinationBadges = useMemo(() => {
492513
if (
493514
!analysisEnabled ||
@@ -522,6 +543,11 @@ const Analysis: React.FC<Props> = ({
522543
}
523544
}, [analyzedGame])
524545

546+
useEffect(() => {
547+
if (!userGamePovOrientation) return
548+
controller.setOrientation(userGamePovOrientation)
549+
}, [analyzedGame.tree, controller.setOrientation, userGamePovOrientation])
550+
525551
useEffect(() => {
526552
setHoverArrow(null)
527553
}, [controller.currentNode])
@@ -1194,6 +1220,12 @@ const Analysis: React.FC<Props> = ({
11941220
const [desktopLeftPanelTab, setDesktopLeftPanelTab] = useState<
11951221
(typeof desktopLeftPanelTabs)[number]['id']
11961222
>(desktopLeftPanelTabs[0].id)
1223+
const showDesktopMovesAfterNavigation = useCallback(() => {
1224+
if (desktopLeftPanelTab !== 'select-game') return
1225+
1226+
setDesktopLeftPanelTab('moves')
1227+
setDesktopMoveListScrollResetSignal((prev) => prev + 1)
1228+
}, [desktopLeftPanelTab])
11971229

11981230
useEffect(() => {
11991231
if (useMobileStyleAnalysisLayout || desktopLeftPanelTab !== 'select-game') {
@@ -1222,15 +1254,15 @@ const Analysis: React.FC<Props> = ({
12221254
event.preventDefault()
12231255
event.stopPropagation()
12241256
controller.goToPreviousNode()
1225-
setDesktopLeftPanelTab('moves')
1257+
showDesktopMovesAfterNavigation()
12261258
return
12271259
}
12281260

12291261
if (event.key === 'ArrowRight' && controller.currentNode?.mainChild) {
12301262
event.preventDefault()
12311263
event.stopPropagation()
12321264
controller.goToNextNode()
1233-
setDesktopLeftPanelTab('moves')
1265+
showDesktopMovesAfterNavigation()
12341266
}
12351267
}
12361268

@@ -1244,6 +1276,7 @@ const Analysis: React.FC<Props> = ({
12441276
controller.goToPreviousNode,
12451277
controller.learnFromMistakes.state.isActive,
12461278
desktopLeftPanelTab,
1279+
showDesktopMovesAfterNavigation,
12471280
useMobileStyleAnalysisLayout,
12481281
])
12491282

@@ -1457,74 +1490,75 @@ const Analysis: React.FC<Props> = ({
14571490
)
14581491
})}
14591492
</div>
1460-
<div className="relative flex min-h-0 flex-1 overflow-hidden">
1461-
<div
1462-
aria-hidden={desktopLeftPanelTab !== 'moves'}
1463-
className={`red-scrollbar absolute inset-0 flex h-full flex-col overflow-y-auto transition-opacity duration-150 ${
1464-
desktopLeftPanelTab === 'moves'
1465-
? 'visible opacity-100'
1466-
: 'pointer-events-none invisible opacity-0'
1467-
}`}
1468-
>
1469-
<MovesContainer
1470-
game={analyzedGame}
1471-
termination={analyzedGame.termination}
1472-
showAnnotations={true}
1473-
disableKeyboardNavigation={
1474-
controller.gameAnalysis.progress.isAnalyzing ||
1475-
controller.learnFromMistakes.state.isActive
1476-
}
1477-
disableMoveClicking={
1478-
controller.learnFromMistakes.state.isActive
1479-
}
1480-
embedded
1481-
heightClass="h-40"
1482-
/>
1483-
{/* No spacer here to keep controller tight to moves */}
1484-
<BoardController
1485-
gameTree={controller.gameTree}
1486-
orientation={controller.orientation}
1487-
setOrientation={controller.setOrientation}
1488-
currentNode={controller.currentNode}
1489-
plyCount={controller.plyCount}
1490-
goToNode={controller.goToNode}
1491-
goToNextNode={controller.goToNextNode}
1492-
goToPreviousNode={controller.goToPreviousNode}
1493-
goToRootNode={controller.goToRootNode}
1494-
disableKeyboardNavigation={
1495-
controller.gameAnalysis.progress.isAnalyzing ||
1496-
controller.learnFromMistakes.state.isActive
1497-
}
1498-
disableNavigation={
1499-
controller.learnFromMistakes.state.isActive
1500-
}
1501-
embedded
1502-
/>
1503-
</div>
1504-
<div
1505-
aria-hidden={desktopLeftPanelTab !== 'select-game'}
1506-
className={`absolute inset-0 flex min-h-0 flex-col transition-opacity duration-150 ${
1507-
desktopLeftPanelTab === 'select-game'
1508-
? 'visible opacity-100'
1509-
: 'pointer-events-none invisible opacity-0'
1510-
}`}
1511-
>
1512-
<AnalysisGameList
1513-
currentId={currentId}
1514-
loadNewWorldChampionshipGame={(newId, setCurrentMove) =>
1515-
getAndSetTournamentGame(newId, setCurrentMove)
1516-
}
1517-
loadNewLichessGame={(id, pgn, setCurrentMove) =>
1518-
getAndSetLichessGame(id, pgn, setCurrentMove)
1519-
}
1520-
loadNewMaiaGame={(id, type, setCurrentMove) =>
1521-
getAndSetUserGame(id, type, setCurrentMove)
1522-
}
1523-
onCustomAnalysis={() => setShowCustomModal(true)}
1524-
refreshTrigger={refreshTrigger}
1525-
embedded
1526-
/>
1493+
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
1494+
<div className="relative min-h-0 flex-1 overflow-hidden">
1495+
<div
1496+
aria-hidden={desktopLeftPanelTab !== 'moves'}
1497+
className={`absolute inset-0 flex h-full flex-col transition-opacity duration-150 ${
1498+
desktopLeftPanelTab === 'moves'
1499+
? 'visible opacity-100'
1500+
: 'pointer-events-none invisible opacity-0'
1501+
}`}
1502+
>
1503+
<MovesContainer
1504+
game={analyzedGame}
1505+
termination={analyzedGame.termination}
1506+
showAnnotations={true}
1507+
disableKeyboardNavigation={
1508+
controller.gameAnalysis.progress.isAnalyzing ||
1509+
controller.learnFromMistakes.state.isActive
1510+
}
1511+
disableMoveClicking={
1512+
controller.learnFromMistakes.state.isActive
1513+
}
1514+
scrollToTopSignal={desktopMoveListScrollResetSignal}
1515+
embedded
1516+
heightClass="h-40"
1517+
/>
1518+
</div>
1519+
<div
1520+
aria-hidden={desktopLeftPanelTab !== 'select-game'}
1521+
className={`absolute inset-0 flex min-h-0 flex-col transition-opacity duration-150 ${
1522+
desktopLeftPanelTab === 'select-game'
1523+
? 'visible opacity-100'
1524+
: 'pointer-events-none invisible opacity-0'
1525+
}`}
1526+
>
1527+
<AnalysisGameList
1528+
currentId={currentId}
1529+
loadNewWorldChampionshipGame={(newId, setCurrentMove) =>
1530+
getAndSetTournamentGame(newId, setCurrentMove)
1531+
}
1532+
loadNewLichessGame={(id, pgn, setCurrentMove) =>
1533+
getAndSetLichessGame(id, pgn, setCurrentMove)
1534+
}
1535+
loadNewMaiaGame={(id, type, setCurrentMove) =>
1536+
getAndSetUserGame(id, type, setCurrentMove)
1537+
}
1538+
onCustomAnalysis={() => setShowCustomModal(true)}
1539+
refreshTrigger={refreshTrigger}
1540+
embedded
1541+
/>
1542+
</div>
15271543
</div>
1544+
<BoardController
1545+
gameTree={controller.gameTree}
1546+
orientation={controller.orientation}
1547+
setOrientation={controller.setOrientation}
1548+
currentNode={controller.currentNode}
1549+
plyCount={controller.plyCount}
1550+
goToNode={controller.goToNode}
1551+
goToNextNode={controller.goToNextNode}
1552+
goToPreviousNode={controller.goToPreviousNode}
1553+
goToRootNode={controller.goToRootNode}
1554+
disableKeyboardNavigation={
1555+
controller.gameAnalysis.progress.isAnalyzing ||
1556+
controller.learnFromMistakes.state.isActive
1557+
}
1558+
disableNavigation={controller.learnFromMistakes.state.isActive}
1559+
onNavigate={showDesktopMovesAfterNavigation}
1560+
embedded
1561+
/>
15281562
</div>
15291563
</div>
15301564
</motion.div>

0 commit comments

Comments
 (0)