Skip to content

Commit

Permalink
feat: add analysis game list to v2 analysis page
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinjosethomas committed Jan 22, 2025
1 parent 26d8576 commit cff9625
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 6 deletions.
249 changes: 249 additions & 0 deletions src/components/Analysis/ClientAnalysisGameList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import {
useRef,
useMemo,
Dispatch,
useState,
useEffect,
useContext,
SetStateAction,
} from 'react'
import { motion } from 'framer-motion'

import { Tournament } from 'src/components'
import { AnalysisListContext, GameControllerContext } from 'src/contexts'

interface ClientAnalysisGameListProps {
currentId: string[] | null
currentMaiaModel: string
loadNewTournamentGame: (
newId: string[],
setCurrentMove: Dispatch<SetStateAction<number>>,
) => Promise<void>
loadNewLichessGames: (
id: string,
pgn: string,
setCurrentMove: Dispatch<SetStateAction<number>>,
currentMaiaModel: string,
) => Promise<void>
loadNewUserGames: (
id: string,
type: 'play' | 'hand' | 'brain',
setCurrentMove: Dispatch<SetStateAction<number>>,
currentMaiaModel: string,
) => Promise<void>
}

export const ClientAnalysisGameList: React.FC<ClientAnalysisGameListProps> = ({
currentId,
currentMaiaModel,
loadNewTournamentGame,
loadNewLichessGames,
loadNewUserGames,
}) => {
const controller = useContext(GameControllerContext)
const {
analysisPlayList,
analysisHandList,
analysisBrainList,
analysisLichessList,
analysisTournamentList,
} = useContext(AnalysisListContext)

const listKeys = useMemo(() => {
return analysisTournamentList
? Array.from(analysisTournamentList.keys()).sort((a, b) =>
b.split('---')[1].localeCompare(a.split('---')[1]),
)
: []
}, [analysisTournamentList])

const initialOpenIndex = useMemo(() => {
if (analysisTournamentList && currentId) {
return listKeys.map((m) => m.split('---')[0]).indexOf(currentId[0])
} else {
return null
}
}, [analysisTournamentList, currentId, listKeys])

const [selected, setSelected] = useState<
'tournament' | 'pgn' | 'play' | 'hand' | 'brain'
>('pgn')
const [loadingIndex, setLoadingIndex] = useState<number | null>(null)
const [openIndex, setOpenIndex] = useState<number | null>(initialOpenIndex)

const openElement = useRef<HTMLDivElement>(null)
const selectedGameElement = useRef<HTMLButtonElement>(null)

useEffect(() => {
if (selectedGameElement.current) {
selectedGameElement.current.scrollIntoView({
behavior: 'auto',
block: 'nearest',
inline: 'center',
})
} else if (openElement.current) {
openElement.current.scrollIntoView({
behavior: 'auto',
block: 'start',
inline: 'center',
})
}
}),
[selectedGameElement, openElement]

useEffect(() => {
setLoadingIndex(null)
}, [selected])

return analysisTournamentList ? (
<div className="flex flex-col items-start justify-start overflow-hidden rounded bg-background-1">
<div className="flex w-full flex-col">
<div className="grid select-none grid-cols-5 border-b-2 border-white border-opacity-10">
<Header
label="Play"
name="play"
selected={selected}
setSelected={setSelected}
/>
<Header
label="Hand"
name="hand"
selected={selected}
setSelected={setSelected}
/>
<Header
label="Brain"
name="brain"
selected={selected}
setSelected={setSelected}
/>
<Header
label="Lichess"
name="pgn"
selected={selected}
setSelected={setSelected}
/>
<Header
label="WC Matches"
name="tournament"
selected={selected}
setSelected={setSelected}
/>
</div>
<div className="red-scrollbar flex max-h-64 flex-col overflow-y-scroll md:max-h-[60vh]">
{selected === 'tournament' ? (
<>
{listKeys.map((id, i) => (
<Tournament
key={i}
id={id}
index={i}
openIndex={openIndex}
currentId={currentId}
openElement={openElement}
setOpenIndex={setOpenIndex}
loadingIndex={loadingIndex}
setLoadingIndex={setLoadingIndex}
selectedGameElement={selectedGameElement}
loadNewTournamentGame={loadNewTournamentGame}
analysisTournamentList={analysisTournamentList}
setCurrentMove={controller.setCurrentIndex}
/>
))}
</>
) : (
<>
{(selected === 'play'
? analysisPlayList
: selected === 'hand'
? analysisHandList
: selected === 'brain'
? analysisBrainList
: analysisLichessList
).map((game, index) => {
const selectedGame = currentId && currentId[0] === game.id
return (
<button
key={index}
onClick={async () => {
setLoadingIndex(index)
if (game.type === 'pgn') {
await loadNewLichessGames(
game.id,
game.pgn as string,
controller.setCurrentIndex,
currentMaiaModel,
)
} else {
await loadNewUserGames(
game.id,
game.type as 'play' | 'hand' | 'brain',
controller.setCurrentIndex,
currentMaiaModel,
)
}
setLoadingIndex(null)
}}
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'}`}
>
<div
className={`flex h-full w-10 items-center justify-center py-1 ${selectedGame ? 'bg-background-3' : 'bg-background-2 group-hover:bg-white/5'}`}
>
<p className="text-secondary">{index + 1}</p>
</div>
<div className="flex flex-1 items-center justify-between overflow-hidden py-1">
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-primary">
{game.label}
</p>
<p className="whitespace-nowrap text-secondary">
{game.result}
</p>
</div>
</button>
)
})}
</>
)}
</div>
</div>
</div>
) : null
}

function Header({
name,
label,
selected,
setSelected,
}: {
label: string
name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
selected: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
setSelected: (name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn') => void
}) {
return (
<button
onClick={() => setSelected(name)}
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`}
>
<div className="flex items-center justify-start gap-1">
<p
className={`text-sm transition duration-200 ${selected === name ? 'text-human-2' : 'text-primary'}`}
>
{label}
</p>
<i
className={`material-symbols-outlined text-base transition duration-200 ${selected === name ? 'text-human-2/80' : 'text-primary/80'}`}
>
keyboard_arrow_down
</i>
</div>
{selected === name && (
<motion.div
layoutId="underline"
className="absolute -bottom-0.5 h-0.5 w-full bg-human-2/80"
></motion.div>
)}
</button>
)
}
2 changes: 1 addition & 1 deletion src/components/Analysis/ConfigureAnalysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const ConfigureAnalysis: React.FC<Props> = ({
MAIA_MODELS,
}: Props) => {
return (
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2 p-4">
<div className="flex flex-col gap-0.5">
<p className="text-sm text-secondary">Analyze using:</p>
<select
Expand Down
10 changes: 8 additions & 2 deletions src/components/Analysis/UserGameList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const UserGameList = ({
}: Props) => {
return (
<div className="flex w-full flex-col">
<div className="grid select-none grid-cols-4 border-b-2 border-white border-opacity-10">
<div className="grid select-none grid-cols-5 border-b-2 border-white border-opacity-10">
<Header
label="Play"
name="play"
Expand All @@ -69,6 +69,12 @@ export const UserGameList = ({
selected={selected}
setSelected={setSelected}
/>
<Header
label="WC Matches"
name="tournament"
selected={selected}
setSelected={setSelected}
/>
</div>
<div className="red-scrollbar flex max-h-64 flex-col overflow-y-scroll md:max-h-[60vh]">
{(selected === 'play'
Expand Down Expand Up @@ -132,7 +138,7 @@ function Header({
setSelected,
}: {
label: string
name: 'play' | 'hand' | 'brain' | 'pgn'
name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
selected: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn'
setSelected: (name: 'tournament' | 'play' | 'hand' | 'brain' | 'pgn') => void
}) {
Expand Down
1 change: 1 addition & 0 deletions src/components/Analysis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './Tournament'
export * from './BlunderMeter'
export * from './UserGameList'
export * from './AnalysisGameList'
export * from './ClientAnalysisGameList'
export * from './HorizontalEvaluationBar'
export * from './PositionEvaluationContainer'
export * from './VerticalEvaluationBar'
Expand Down
19 changes: 16 additions & 3 deletions src/pages/analyze/[...id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
MoveRecommendations,
AuthenticatedWrapper,
VerticalEvaluationBar,
ClientAnalysisGameList,
HorizontalEvaluationBar,
} from 'src/components'
import { Color, PlayedGame } from 'src/types'
Expand Down Expand Up @@ -222,6 +223,10 @@ const Analysis: React.FC<Props> = ({
getAndSetUserGames,
}: Props) => {
const screens = [
{
id: 'select',
name: 'Select Game',
},
{
id: 'configure',
name: 'Configure',
Expand Down Expand Up @@ -432,16 +437,24 @@ const Analysis: React.FC<Props> = ({
)
})}
</div>
<div className="flex flex-col bg-backdrop/30 p-4">
{screen.id === 'configure' ? (
<div className="flex flex-col bg-backdrop/30">
{screen.id === 'select' ? (
<ClientAnalysisGameList
currentId={currentId}
currentMaiaModel={currentMaiaModel}
loadNewTournamentGame={getAndSetTournamentGame}
loadNewLichessGames={getAndSetLichessGames}
loadNewUserGames={getAndSetUserGames}
/>
) : screen.id === 'configure' ? (
<ConfigureAnalysis
currentMaiaModel={currentMaiaModel}
setCurrentMaiaModel={setCurrentMaiaModel}
launchContinue={launchContinue}
MAIA_MODELS={MAIA_MODELS}
/>
) : screen.id === 'export' ? (
<div className="flex flex-col">
<div className="flex flex-col p-4">
<ExportGame
game={analyzedGame as any as PlayedGame}

Check warning on line 459 in src/pages/analyze/[...id].tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
whitePlayer={analyzedGame.whitePlayer.name}
Expand Down

0 comments on commit cff9625

Please sign in to comment.