Skip to content

Commit 6672199

Browse files
feat: enhance OpeningDrillSidebar with reset functionality and integrate PlayerInfo component
1 parent 5f2e0f1 commit 6672199

7 files changed

Lines changed: 307 additions & 134 deletions

File tree

src/components/Board/MovesContainer.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,23 @@ interface AnalysisProps {
1212
highlightIndices?: number[]
1313
termination?: Termination
1414
type: 'analysis'
15+
showAnnotations?: boolean
1516
}
1617

1718
interface TuringProps {
1819
game: TuringGame
1920
highlightIndices?: number[]
2021
termination?: Termination
2122
type: 'turing'
23+
showAnnotations?: boolean
2224
}
2325

2426
interface PlayProps {
2527
game: BaseGame
2628
highlightIndices?: number[]
2729
termination?: Termination
2830
type: 'play'
31+
showAnnotations?: boolean
2932
}
3033

3134
type Props = AnalysisProps | TuringProps | PlayProps
@@ -103,7 +106,13 @@ function UnlikelyGoodMoveIcon() {
103106
}
104107

105108
export const MovesContainer: React.FC<Props> = (props) => {
106-
const { game, highlightIndices, termination, type } = props
109+
const {
110+
game,
111+
highlightIndices,
112+
termination,
113+
type,
114+
showAnnotations = true,
115+
} = props
107116
const { isMobile } = useContext(WindowSizeContext)
108117

109118
const baseController = useBaseTreeController(type)
@@ -209,8 +218,6 @@ export const MovesContainer: React.FC<Props> = (props) => {
209218
return pairs
210219
}, [mainLineNodes, isMobile])
211220

212-
const showAnnotations = type === 'analysis'
213-
214221
if (isMobile) {
215222
return (
216223
<div className="w-full overflow-x-auto px-2">

src/components/Core/PlayerInfo.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
interface PlayerInfoProps {
2+
name: string
3+
color: string
4+
rating?: number
5+
termination?: string
6+
}
7+
8+
export const PlayerInfo: React.FC<PlayerInfoProps> = ({
9+
name,
10+
rating,
11+
color,
12+
termination,
13+
}) => {
14+
return (
15+
<div className="flex h-10 w-full items-center justify-between bg-background-1 px-4">
16+
<div className="flex items-center gap-1.5">
17+
<div
18+
className={`h-2.5 w-2.5 rounded-full ${color === 'white' ? 'bg-white' : 'border bg-black'}`}
19+
/>
20+
<p>
21+
{name ?? 'Unknown'} {rating ? `(${rating})` : null}
22+
</p>
23+
</div>
24+
{termination === color ? (
25+
<p className="text-engine-3">1</p>
26+
) : termination !== 'none' ? (
27+
<p className="text-human-3">0</p>
28+
) : termination === undefined ? (
29+
<></>
30+
) : (
31+
<p>1/2</p>
32+
)}
33+
</div>
34+
)
35+
}

src/components/Core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './Markdown'
66
export * from './ThemeButton'
77
export * from './ErrorBoundary'
88
export * from './AuthenticatedWrapper'
9+
export * from './PlayerInfo'

src/components/Openings/OpeningDrillSidebar.tsx

Lines changed: 93 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React from 'react'
2+
import Image from 'next/image'
3+
import { Tooltip } from 'react-tooltip'
24
import { OpeningSelection, GameNode, GameTree } from 'src/types'
35
import { GameInfo } from '../Misc/GameInfo'
46
import { MovesContainer } from '../Board/MovesContainer'
@@ -9,6 +11,7 @@ interface Props {
911
currentSelectionIndex: number
1012
onSwitchSelection: (index: number) => void
1113
onResetCurrent: () => void
14+
onResetOpening: (selectionId: string) => void
1215
gameTree: GameTree
1316
currentNode: GameNode
1417
goToNode: (node: GameNode) => void
@@ -18,13 +21,15 @@ interface Props {
1821
plyCount: number
1922
orientation: 'white' | 'black'
2023
setOrientation: (orientation: 'white' | 'black') => void
24+
analysisEnabled: boolean
2125
}
2226

2327
export const OpeningDrillSidebar: React.FC<Props> = ({
2428
selections,
2529
currentSelectionIndex,
2630
onSwitchSelection,
2731
onResetCurrent,
32+
onResetOpening,
2833
gameTree,
2934
currentNode,
3035
goToNode,
@@ -34,6 +39,7 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
3439
plyCount,
3540
orientation,
3641
setOrientation,
42+
analysisEnabled,
3743
}) => {
3844
const currentSelection = selections[currentSelectionIndex]
3945

@@ -53,36 +59,25 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
5359

5460
return (
5561
<div className="flex h-[85vh] w-72 min-w-60 max-w-72 flex-col gap-2 overflow-hidden 2xl:min-w-72">
56-
<GameInfo title="Opening Drill" icon="school" type="analysis">
62+
<GameInfo title="Drill Openings" icon="school" type="analysis">
5763
<div className="space-y-2">
58-
<p className="text-sm text-secondary">
59-
Current Opening:{' '}
60-
<span className="text-primary">
61-
{currentSelection?.opening.name}
62-
</span>
63-
</p>
64-
{currentSelection?.variation && (
64+
<div className="min-h-[30px]">
6565
<p className="text-sm text-secondary">
66-
Variation:{' '}
66+
Current Opening:{' '}
6767
<span className="text-primary">
68-
{currentSelection.variation.name}
68+
{currentSelection?.opening.name}
6969
</span>
7070
</p>
71-
)}
72-
<div className="flex items-center gap-2 text-sm">
73-
<span
74-
className={`inline-flex items-center gap-1 ${
75-
currentSelection?.playerColor === 'white'
76-
? 'text-white'
77-
: 'text-gray-400'
78-
}`}
79-
>
80-
<span className="material-symbols-outlined text-xs">chess</span>
81-
Playing as {currentSelection?.playerColor}
82-
</span>
83-
<span className="text-human-3">
84-
vs {currentSelection?.maiaVersion.replace('maia_kdd_', 'Maia ')}
85-
</span>
71+
<div className="min-h-[20px]">
72+
{currentSelection?.variation && (
73+
<p className="text-sm text-secondary">
74+
Variation:{' '}
75+
<span className="text-primary">
76+
{currentSelection.variation.name}
77+
</span>
78+
</p>
79+
)}
80+
</div>
8681
</div>
8782
</div>
8883
</GameInfo>
@@ -94,73 +89,104 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
9489
</h3>
9590
<button
9691
onClick={onResetCurrent}
97-
className="text-xs text-secondary transition-colors hover:text-human-4"
98-
title="Reset current opening"
92+
className="text-xs text-secondary transition-colors hover:text-primary"
93+
data-tooltip-id="reset-all-tooltip"
9994
>
100-
<span className="material-symbols-outlined text-sm">refresh</span>
95+
<span className="material-symbols-outlined text-base">refresh</span>
10196
</button>
97+
<Tooltip
98+
id="reset-all-tooltip"
99+
content="Reset All Openings"
100+
place="top"
101+
delayShow={300}
102+
className="z-50 !bg-background-2 !px-2 !py-1 !text-xs"
103+
/>
102104
</div>
103105

104106
<div className="flex-1 overflow-y-auto">
105107
{selections.map((selection, index) => (
106108
<div
107109
key={selection.id}
108-
role="button"
109-
tabIndex={0}
110-
className={`cursor-pointer border-b border-white/5 p-3 transition-colors ${
110+
className={`flex cursor-pointer items-center justify-between border-b border-white/5 p-3 transition-colors ${
111111
index === currentSelectionIndex
112112
? 'bg-human-2/20'
113113
: 'hover:bg-human-2/10'
114114
}`}
115-
onClick={() => onSwitchSelection(index)}
116-
onKeyDown={(e) => {
117-
if (e.key === 'Enter' || e.key === ' ') {
118-
onSwitchSelection(index)
119-
}
120-
}}
121115
>
122-
<div className="flex items-start justify-between">
123-
<div className="min-w-0 flex-1">
124-
<h4 className="truncate text-sm font-medium">
125-
{selection.opening.name}
126-
</h4>
127-
{selection.variation && (
128-
<p className="truncate text-xs text-secondary">
129-
{selection.variation.name}
130-
</p>
131-
)}
132-
<div className="mt-1 flex items-center gap-2">
133-
<span
134-
className={`inline-flex items-center gap-1 text-xs ${
116+
<div
117+
role="button"
118+
tabIndex={0}
119+
className="min-w-0 flex-1"
120+
onClick={() => onSwitchSelection(index)}
121+
onKeyDown={(e) => {
122+
if (e.key === 'Enter' || e.key === ' ') {
123+
onSwitchSelection(index)
124+
}
125+
}}
126+
>
127+
<div className="flex items-center gap-3">
128+
<div className="relative h-5 w-5 flex-shrink-0">
129+
<Image
130+
src={
135131
selection.playerColor === 'white'
136-
? 'text-white'
137-
: 'text-gray-400'
138-
}`}
139-
>
140-
<span className="material-symbols-outlined text-xs">
141-
chess
142-
</span>
143-
{selection.playerColor}
144-
</span>
145-
<span className="text-xs text-human-3">
146-
{selection.maiaVersion.replace('maia_kdd_', 'Maia ')}
132+
? '/assets/pieces/white king.svg'
133+
: '/assets/pieces/black king.svg'
134+
}
135+
fill={true}
136+
alt={`${selection.playerColor} king`}
137+
/>
138+
</div>
139+
<div className="flex min-w-0 flex-1 flex-col">
140+
<span className="truncate text-sm font-medium text-primary">
141+
{selection.opening.name}
147142
</span>
143+
<div className="flex items-center gap-1 text-xs text-secondary">
144+
{selection.variation && (
145+
<>
146+
<span className="truncate">
147+
{selection.variation.name}
148+
</span>
149+
<span></span>
150+
</>
151+
)}
152+
<span>
153+
v. Maia {selection.maiaVersion.replace('maia_kdd_', '')}
154+
</span>
155+
</div>
148156
</div>
149157
</div>
150-
{index === currentSelectionIndex && (
151-
<span className="material-symbols-outlined ml-2 text-sm text-human-3">
152-
play_arrow
153-
</span>
154-
)}
155158
</div>
159+
<button
160+
onClick={(e) => {
161+
e.stopPropagation()
162+
onResetOpening(selection.id)
163+
}}
164+
className="ml-2 text-secondary transition-colors hover:text-primary"
165+
data-tooltip-id={`reset-opening-${selection.id}`}
166+
>
167+
<span className="material-symbols-outlined text-sm">
168+
refresh
169+
</span>
170+
</button>
171+
<Tooltip
172+
id={`reset-opening-${selection.id}`}
173+
content="Reset Opening"
174+
place="top"
175+
delayShow={300}
176+
className="z-50 !bg-background-2 !px-2 !py-1 !text-xs"
177+
/>
156178
</div>
157179
))}
158180
</div>
159181
</div>
160182

161183
<div className="flex h-1/2 w-full flex-1 flex-col gap-2">
162184
<div className="flex h-full flex-col overflow-y-scroll">
163-
<MovesContainer game={baseGame} type="analysis" />
185+
<MovesContainer
186+
game={baseGame}
187+
type="analysis"
188+
showAnnotations={analysisEnabled}
189+
/>
164190
<BoardController
165191
gameTree={gameTree}
166192
orientation={orientation}

src/hooks/useOpeningDrillController/useOpeningDrillController.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,50 @@ export const useOpeningDrillController = (selections: OpeningSelection[]) => {
401401
// The useEffect will handle syncing the controller with the new tree
402402
}, [currentSelection])
403403

404+
// Reset specific opening to starting position
405+
const resetOpening = useCallback(
406+
(selectionId: string) => {
407+
const selection = selections.find((s) => s.id === selectionId)
408+
if (!selection) return
409+
410+
const startingFen =
411+
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' // Always start from initial position
412+
const gameTree = new GameTree(startingFen)
413+
414+
// Parse the PGN to populate the tree with opening moves
415+
const pgn = selection.variation
416+
? selection.variation.pgn
417+
: selection.opening.pgn
418+
const endNode = parsePgnToTree(pgn, gameTree)
419+
420+
const resetGame: OpeningDrillGame = {
421+
id: selection.id,
422+
selection: selection,
423+
moves: [],
424+
tree: gameTree,
425+
currentFen: endNode?.fen || startingFen,
426+
toPlay: endNode
427+
? new Chess(endNode.fen).turn() === 'w'
428+
? 'white'
429+
: 'black'
430+
: 'white',
431+
openingEndNode: endNode,
432+
}
433+
434+
// Update the drill games state
435+
setDrillGames((prev) => ({
436+
...prev,
437+
[selection.id]: resetGame,
438+
}))
439+
440+
// If this is the current selection, sync the controller
441+
if (selection.id === currentSelection?.id) {
442+
// The useEffect will handle syncing the controller with the new tree
443+
}
444+
},
445+
[selections, currentSelection],
446+
)
447+
404448
return {
405449
// Game state
406450
currentSelection,
@@ -428,6 +472,7 @@ export const useOpeningDrillController = (selections: OpeningSelection[]) => {
428472
makePlayerMove,
429473
switchToSelection,
430474
resetCurrentGame,
475+
resetOpening,
431476

432477
// Analysis
433478
analysisEnabled,

0 commit comments

Comments
 (0)