Skip to content

Commit cff9625

Browse files
feat: add analysis game list to v2 analysis page
1 parent 26d8576 commit cff9625

File tree

5 files changed

+275
-6
lines changed

5 files changed

+275
-6
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
import {
2+
useRef,
3+
useMemo,
4+
Dispatch,
5+
useState,
6+
useEffect,
7+
useContext,
8+
SetStateAction,
9+
} from 'react'
10+
import { motion } from 'framer-motion'
11+
12+
import { Tournament } from 'src/components'
13+
import { AnalysisListContext, GameControllerContext } from 'src/contexts'
14+
15+
interface ClientAnalysisGameListProps {
16+
currentId: string[] | null
17+
currentMaiaModel: string
18+
loadNewTournamentGame: (
19+
newId: string[],
20+
setCurrentMove: Dispatch<SetStateAction<number>>,
21+
) => Promise<void>
22+
loadNewLichessGames: (
23+
id: string,
24+
pgn: string,
25+
setCurrentMove: Dispatch<SetStateAction<number>>,
26+
currentMaiaModel: string,
27+
) => Promise<void>
28+
loadNewUserGames: (
29+
id: string,
30+
type: 'play' | 'hand' | 'brain',
31+
setCurrentMove: Dispatch<SetStateAction<number>>,
32+
currentMaiaModel: string,
33+
) => Promise<void>
34+
}
35+
36+
export const ClientAnalysisGameList: React.FC<ClientAnalysisGameListProps> = ({
37+
currentId,
38+
currentMaiaModel,
39+
loadNewTournamentGame,
40+
loadNewLichessGames,
41+
loadNewUserGames,
42+
}) => {
43+
const controller = useContext(GameControllerContext)
44+
const {
45+
analysisPlayList,
46+
analysisHandList,
47+
analysisBrainList,
48+
analysisLichessList,
49+
analysisTournamentList,
50+
} = useContext(AnalysisListContext)
51+
52+
const listKeys = useMemo(() => {
53+
return analysisTournamentList
54+
? Array.from(analysisTournamentList.keys()).sort((a, b) =>
55+
b.split('---')[1].localeCompare(a.split('---')[1]),
56+
)
57+
: []
58+
}, [analysisTournamentList])
59+
60+
const initialOpenIndex = useMemo(() => {
61+
if (analysisTournamentList && currentId) {
62+
return listKeys.map((m) => m.split('---')[0]).indexOf(currentId[0])
63+
} else {
64+
return null
65+
}
66+
}, [analysisTournamentList, currentId, listKeys])
67+
68+
const [selected, setSelected] = useState<
69+
'tournament' | 'pgn' | 'play' | 'hand' | 'brain'
70+
>('pgn')
71+
const [loadingIndex, setLoadingIndex] = useState<number | null>(null)
72+
const [openIndex, setOpenIndex] = useState<number | null>(initialOpenIndex)
73+
74+
const openElement = useRef<HTMLDivElement>(null)
75+
const selectedGameElement = useRef<HTMLButtonElement>(null)
76+
77+
useEffect(() => {
78+
if (selectedGameElement.current) {
79+
selectedGameElement.current.scrollIntoView({
80+
behavior: 'auto',
81+
block: 'nearest',
82+
inline: 'center',
83+
})
84+
} else if (openElement.current) {
85+
openElement.current.scrollIntoView({
86+
behavior: 'auto',
87+
block: 'start',
88+
inline: 'center',
89+
})
90+
}
91+
}),
92+
[selectedGameElement, openElement]
93+
94+
useEffect(() => {
95+
setLoadingIndex(null)
96+
}, [selected])
97+
98+
return analysisTournamentList ? (
99+
<div className="flex flex-col items-start justify-start overflow-hidden rounded bg-background-1">
100+
<div className="flex w-full flex-col">
101+
<div className="grid select-none grid-cols-5 border-b-2 border-white border-opacity-10">
102+
<Header
103+
label="Play"
104+
name="play"
105+
selected={selected}
106+
setSelected={setSelected}
107+
/>
108+
<Header
109+
label="Hand"
110+
name="hand"
111+
selected={selected}
112+
setSelected={setSelected}
113+
/>
114+
<Header
115+
label="Brain"
116+
name="brain"
117+
selected={selected}
118+
setSelected={setSelected}
119+
/>
120+
<Header
121+
label="Lichess"
122+
name="pgn"
123+
selected={selected}
124+
setSelected={setSelected}
125+
/>
126+
<Header
127+
label="WC Matches"
128+
name="tournament"
129+
selected={selected}
130+
setSelected={setSelected}
131+
/>
132+
</div>
133+
<div className="red-scrollbar flex max-h-64 flex-col overflow-y-scroll md:max-h-[60vh]">
134+
{selected === 'tournament' ? (
135+
<>
136+
{listKeys.map((id, i) => (
137+
<Tournament
138+
key={i}
139+
id={id}
140+
index={i}
141+
openIndex={openIndex}
142+
currentId={currentId}
143+
openElement={openElement}
144+
setOpenIndex={setOpenIndex}
145+
loadingIndex={loadingIndex}
146+
setLoadingIndex={setLoadingIndex}
147+
selectedGameElement={selectedGameElement}
148+
loadNewTournamentGame={loadNewTournamentGame}
149+
analysisTournamentList={analysisTournamentList}
150+
setCurrentMove={controller.setCurrentIndex}
151+
/>
152+
))}
153+
</>
154+
) : (
155+
<>
156+
{(selected === 'play'
157+
? analysisPlayList
158+
: selected === 'hand'
159+
? analysisHandList
160+
: selected === 'brain'
161+
? analysisBrainList
162+
: analysisLichessList
163+
).map((game, index) => {
164+
const selectedGame = currentId && currentId[0] === game.id
165+
return (
166+
<button
167+
key={index}
168+
onClick={async () => {
169+
setLoadingIndex(index)
170+
if (game.type === 'pgn') {
171+
await loadNewLichessGames(
172+
game.id,
173+
game.pgn as string,
174+
controller.setCurrentIndex,
175+
currentMaiaModel,
176+
)
177+
} else {
178+
await loadNewUserGames(
179+
game.id,
180+
game.type as 'play' | 'hand' | 'brain',
181+
controller.setCurrentIndex,
182+
currentMaiaModel,
183+
)
184+
}
185+
setLoadingIndex(null)
186+
}}
187+
className={`group flex w-full cursor-pointer items-center gap-2 pr-2 ${selectedGame ? 'bg-background-2 font-bold' : index % 2 === 0 ? 'bg-background-1/30 hover:bg-background-2' : 'bg-background-1/10 hover:bg-background-2'}`}
188+
>
189+
<div
190+
className={`flex h-full w-10 items-center justify-center py-1 ${selectedGame ? 'bg-background-3' : 'bg-background-2 group-hover:bg-white/5'}`}
191+
>
192+
<p className="text-secondary">{index + 1}</p>
193+
</div>
194+
<div className="flex flex-1 items-center justify-between overflow-hidden py-1">
195+
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-primary">
196+
{game.label}
197+
</p>
198+
<p className="whitespace-nowrap text-secondary">
199+
{game.result}
200+
</p>
201+
</div>
202+
</button>
203+
)
204+
})}
205+
</>
206+
)}
207+
</div>
208+
</div>
209+
</div>
210+
) : null
211+
}
212+
213+
function Header({
214+
name,
215+
label,
216+
selected,
217+
setSelected,
218+
}: {
219+
label: string
220+
name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
221+
selected: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
222+
setSelected: (name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn') => void
223+
}) {
224+
return (
225+
<button
226+
onClick={() => setSelected(name)}
227+
className={`relative flex items-center justify-center py-0.5 ${selected === name ? 'bg-human-4/30' : 'bg-background-1/80 hover:bg-background-2'} transition duration-200`}
228+
>
229+
<div className="flex items-center justify-start gap-1">
230+
<p
231+
className={`text-sm transition duration-200 ${selected === name ? 'text-human-2' : 'text-primary'}`}
232+
>
233+
{label}
234+
</p>
235+
<i
236+
className={`material-symbols-outlined text-base transition duration-200 ${selected === name ? 'text-human-2/80' : 'text-primary/80'}`}
237+
>
238+
keyboard_arrow_down
239+
</i>
240+
</div>
241+
{selected === name && (
242+
<motion.div
243+
layoutId="underline"
244+
className="absolute -bottom-0.5 h-0.5 w-full bg-human-2/80"
245+
></motion.div>
246+
)}
247+
</button>
248+
)
249+
}

src/components/Analysis/ConfigureAnalysis.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const ConfigureAnalysis: React.FC<Props> = ({
1717
MAIA_MODELS,
1818
}: Props) => {
1919
return (
20-
<div className="flex flex-col gap-2">
20+
<div className="flex flex-col gap-2 p-4">
2121
<div className="flex flex-col gap-0.5">
2222
<p className="text-sm text-secondary">Analyze using:</p>
2323
<select

src/components/Analysis/UserGameList.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const UserGameList = ({
4444
}: Props) => {
4545
return (
4646
<div className="flex w-full flex-col">
47-
<div className="grid select-none grid-cols-4 border-b-2 border-white border-opacity-10">
47+
<div className="grid select-none grid-cols-5 border-b-2 border-white border-opacity-10">
4848
<Header
4949
label="Play"
5050
name="play"
@@ -69,6 +69,12 @@ export const UserGameList = ({
6969
selected={selected}
7070
setSelected={setSelected}
7171
/>
72+
<Header
73+
label="WC Matches"
74+
name="tournament"
75+
selected={selected}
76+
setSelected={setSelected}
77+
/>
7278
</div>
7379
<div className="red-scrollbar flex max-h-64 flex-col overflow-y-scroll md:max-h-[60vh]">
7480
{(selected === 'play'
@@ -132,7 +138,7 @@ function Header({
132138
setSelected,
133139
}: {
134140
label: string
135-
name: 'play' | 'hand' | 'brain' | 'pgn'
141+
name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
136142
selected: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
137143
setSelected: (name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn') => void
138144
}) {

src/components/Analysis/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './Tournament'
55
export * from './BlunderMeter'
66
export * from './UserGameList'
77
export * from './AnalysisGameList'
8+
export * from './ClientAnalysisGameList'
89
export * from './HorizontalEvaluationBar'
910
export * from './PositionEvaluationContainer'
1011
export * from './VerticalEvaluationBar'

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
MoveRecommendations,
3636
AuthenticatedWrapper,
3737
VerticalEvaluationBar,
38+
ClientAnalysisGameList,
3839
HorizontalEvaluationBar,
3940
} from 'src/components'
4041
import { Color, PlayedGame } from 'src/types'
@@ -222,6 +223,10 @@ const Analysis: React.FC<Props> = ({
222223
getAndSetUserGames,
223224
}: Props) => {
224225
const screens = [
226+
{
227+
id: 'select',
228+
name: 'Select Game',
229+
},
225230
{
226231
id: 'configure',
227232
name: 'Configure',
@@ -432,16 +437,24 @@ const Analysis: React.FC<Props> = ({
432437
)
433438
})}
434439
</div>
435-
<div className="flex flex-col bg-backdrop/30 p-4">
436-
{screen.id === 'configure' ? (
440+
<div className="flex flex-col bg-backdrop/30">
441+
{screen.id === 'select' ? (
442+
<ClientAnalysisGameList
443+
currentId={currentId}
444+
currentMaiaModel={currentMaiaModel}
445+
loadNewTournamentGame={getAndSetTournamentGame}
446+
loadNewLichessGames={getAndSetLichessGames}
447+
loadNewUserGames={getAndSetUserGames}
448+
/>
449+
) : screen.id === 'configure' ? (
437450
<ConfigureAnalysis
438451
currentMaiaModel={currentMaiaModel}
439452
setCurrentMaiaModel={setCurrentMaiaModel}
440453
launchContinue={launchContinue}
441454
MAIA_MODELS={MAIA_MODELS}
442455
/>
443456
) : screen.id === 'export' ? (
444-
<div className="flex flex-col">
457+
<div className="flex flex-col p-4">
445458
<ExportGame
446459
game={analyzedGame as any as PlayedGame}
447460
whitePlayer={analyzedGame.whitePlayer.name}

0 commit comments

Comments
 (0)