Skip to content

Commit f458371

Browse files
chore: add initial iteration of openings modal
1 parent f0e10bc commit f458371

9 files changed

Lines changed: 1225 additions & 80 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React from 'react'
2+
import { Highlight, MoveMap, BlunderMeter } from '../Analysis'
3+
import { useAnalysisController } from 'src/hooks'
4+
import { AnalyzedGame } from 'src/types'
5+
import { GameTree } from 'src/types/base/tree'
6+
import { Chess } from 'chess.ts'
7+
8+
// Create a mock analyzed game for when no real game is available
9+
const createMockAnalyzedGame = (): AnalyzedGame => ({
10+
id: 'mock',
11+
blackPlayer: { name: 'Player', rating: undefined },
12+
whitePlayer: { name: 'Player', rating: undefined },
13+
moves: [],
14+
availableMoves: [],
15+
gameType: 'play',
16+
termination: {
17+
result: '*',
18+
winner: 'none',
19+
condition: 'Normal',
20+
},
21+
maiaEvaluations: [],
22+
stockfishEvaluations: [],
23+
tree: new GameTree(new Chess().fen()),
24+
type: 'play',
25+
})
26+
27+
interface Props {
28+
analyzedGame: AnalyzedGame | null
29+
analysisEnabled: boolean
30+
onToggleAnalysis: () => void
31+
}
32+
33+
export const OpeningDrillAnalysis: React.FC<Props> = ({
34+
analyzedGame,
35+
analysisEnabled,
36+
onToggleAnalysis,
37+
}) => {
38+
// Always call the hook but use mock data when disabled
39+
const analysisController = useAnalysisController(
40+
analyzedGame || createMockAnalyzedGame(),
41+
'white',
42+
)
43+
const mockHover = () => {
44+
// No-op for disabled analysis
45+
}
46+
47+
const mockMakeMove = () => {
48+
// No-op for disabled analysis
49+
}
50+
51+
const mockSetHoverArrow = () => {
52+
// No-op for disabled analysis
53+
}
54+
55+
return (
56+
<div className="flex h-[calc(55vh+4.5rem)] w-full flex-col gap-2">
57+
{/* Analysis Toggle */}
58+
<div className="flex items-center justify-between rounded bg-background-1 px-4 py-2">
59+
<div className="flex items-center gap-2">
60+
<span className="material-symbols-outlined text-xl">analytics</span>
61+
<h3 className="font-semibold">Analysis</h3>
62+
</div>
63+
<button
64+
onClick={onToggleAnalysis}
65+
className={`flex items-center gap-2 rounded px-3 py-1 text-sm transition-colors ${
66+
analysisEnabled
67+
? 'bg-human-4 text-white hover:bg-human-4/80'
68+
: 'bg-background-2 text-secondary hover:bg-background-3'
69+
}`}
70+
>
71+
<span className="material-symbols-outlined text-sm">
72+
{analysisEnabled ? 'visibility' : 'visibility_off'}
73+
</span>
74+
{analysisEnabled ? 'Enabled' : 'Disabled'}
75+
</button>
76+
</div>
77+
78+
{/* Top Row - Highlight Component */}
79+
<div className="relative">
80+
<div className="flex h-[calc((55vh+4.5rem)/2)]">
81+
<Highlight
82+
hover={mockHover}
83+
makeMove={mockMakeMove}
84+
currentMaiaModel="maia_kdd_1500"
85+
recommendations={analysisController.moveRecommendations}
86+
moveEvaluation={
87+
analysisController.moveEvaluation || {
88+
maia: undefined,
89+
stockfish: undefined,
90+
}
91+
}
92+
movesByRating={analysisController.movesByRating}
93+
colorSanMapping={analysisController.colorSanMapping}
94+
boardDescription={
95+
analysisEnabled
96+
? 'This position offers multiple strategic options. Consider central control and piece development.'
97+
: 'Analysis is disabled. Enable analysis to see detailed move evaluations and recommendations.'
98+
}
99+
/>
100+
</div>
101+
{!analysisEnabled && (
102+
<div className="absolute inset-0 z-10 flex items-center justify-center overflow-hidden rounded bg-background-1/80 backdrop-blur-sm">
103+
<div className="rounded bg-background-2/90 p-4 text-center shadow-lg">
104+
<span className="material-symbols-outlined mb-2 text-3xl text-human-3">
105+
lock
106+
</span>
107+
<p className="font-medium text-primary">Analysis Disabled</p>
108+
<p className="text-sm text-secondary">
109+
Enable analysis to see move evaluations
110+
</p>
111+
</div>
112+
</div>
113+
)}
114+
</div>
115+
116+
{/* Bottom Row - Move Map and Blunder Meter */}
117+
<div className="relative">
118+
<div className="flex h-[calc((55vh+4.5rem)/2)] flex-row gap-2">
119+
<div className="flex h-full w-full flex-col">
120+
<MoveMap
121+
moveMap={analysisController.moveMap}
122+
colorSanMapping={analysisController.colorSanMapping}
123+
setHoverArrow={mockSetHoverArrow}
124+
/>
125+
</div>
126+
<BlunderMeter
127+
hover={mockHover}
128+
makeMove={mockMakeMove}
129+
data={analysisController.blunderMeter}
130+
colorSanMapping={analysisController.colorSanMapping}
131+
/>
132+
</div>
133+
{!analysisEnabled && (
134+
<div className="absolute inset-0 z-10 flex items-center justify-center overflow-hidden rounded bg-background-1/80 backdrop-blur-sm">
135+
<div className="rounded bg-background-2/90 p-4 text-center shadow-lg">
136+
<span className="material-symbols-outlined mb-2 text-3xl text-human-3">
137+
lock
138+
</span>
139+
<p className="font-medium text-primary">Analysis Disabled</p>
140+
<p className="text-sm text-secondary">
141+
Enable analysis to see position evaluation
142+
</p>
143+
</div>
144+
</div>
145+
)}
146+
</div>
147+
</div>
148+
)
149+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import React from 'react'
2+
import { OpeningSelection } from 'src/types'
3+
import { GameInfo } from '../Misc/GameInfo'
4+
5+
interface Props {
6+
selections: OpeningSelection[]
7+
currentSelectionIndex: number
8+
onSwitchSelection: (index: number) => void
9+
onResetCurrent: () => void
10+
}
11+
12+
export const OpeningDrillSidebar: React.FC<Props> = ({
13+
selections,
14+
currentSelectionIndex,
15+
onSwitchSelection,
16+
onResetCurrent,
17+
}) => {
18+
const currentSelection = selections[currentSelectionIndex]
19+
20+
return (
21+
<div className="flex h-[85vh] w-72 min-w-60 max-w-72 flex-col gap-2 overflow-hidden 2xl:min-w-72">
22+
<GameInfo title="Opening Drill" icon="school" type="analysis">
23+
<div className="space-y-2">
24+
<p className="text-sm text-secondary">
25+
Current Opening:{' '}
26+
<span className="text-primary">
27+
{currentSelection?.opening.name}
28+
</span>
29+
</p>
30+
{currentSelection?.variation && (
31+
<p className="text-sm text-secondary">
32+
Variation:{' '}
33+
<span className="text-primary">
34+
{currentSelection.variation.name}
35+
</span>
36+
</p>
37+
)}
38+
<div className="flex items-center gap-2 text-sm">
39+
<span
40+
className={`inline-flex items-center gap-1 ${
41+
currentSelection?.playerColor === 'white'
42+
? 'text-white'
43+
: 'text-gray-400'
44+
}`}
45+
>
46+
<span className="material-symbols-outlined text-xs">chess</span>
47+
Playing as {currentSelection?.playerColor}
48+
</span>
49+
<span className="text-human-3">
50+
vs {currentSelection?.maiaVersion.replace('maia_kdd_', 'Maia ')}
51+
</span>
52+
</div>
53+
</div>
54+
</GameInfo>
55+
56+
<div className="flex flex-col overflow-hidden rounded bg-background-1">
57+
<div className="flex items-center justify-between border-b border-white/10 p-3">
58+
<h3 className="font-semibold">
59+
Selected Openings ({selections.length})
60+
</h3>
61+
<button
62+
onClick={onResetCurrent}
63+
className="text-xs text-secondary transition-colors hover:text-human-4"
64+
title="Reset current opening"
65+
>
66+
<span className="material-symbols-outlined text-sm">refresh</span>
67+
</button>
68+
</div>
69+
70+
<div className="flex-1 overflow-y-auto">
71+
{selections.map((selection, index) => (
72+
<div
73+
key={selection.id}
74+
role="button"
75+
tabIndex={0}
76+
className={`cursor-pointer border-b border-white/5 p-3 transition-colors ${
77+
index === currentSelectionIndex
78+
? 'bg-human-2/20'
79+
: 'hover:bg-human-2/10'
80+
}`}
81+
onClick={() => onSwitchSelection(index)}
82+
onKeyDown={(e) => {
83+
if (e.key === 'Enter' || e.key === ' ') {
84+
onSwitchSelection(index)
85+
}
86+
}}
87+
>
88+
<div className="flex items-start justify-between">
89+
<div className="min-w-0 flex-1">
90+
<h4 className="truncate text-sm font-medium">
91+
{selection.opening.name}
92+
</h4>
93+
{selection.variation && (
94+
<p className="truncate text-xs text-secondary">
95+
{selection.variation.name}
96+
</p>
97+
)}
98+
<div className="mt-1 flex items-center gap-2">
99+
<span
100+
className={`inline-flex items-center gap-1 text-xs ${
101+
selection.playerColor === 'white'
102+
? 'text-white'
103+
: 'text-gray-400'
104+
}`}
105+
>
106+
<span className="material-symbols-outlined text-xs">
107+
chess
108+
</span>
109+
{selection.playerColor}
110+
</span>
111+
<span className="text-xs text-human-3">
112+
{selection.maiaVersion.replace('maia_kdd_', 'Maia ')}
113+
</span>
114+
</div>
115+
</div>
116+
{index === currentSelectionIndex && (
117+
<span className="material-symbols-outlined ml-2 text-sm text-human-3">
118+
play_arrow
119+
</span>
120+
)}
121+
</div>
122+
</div>
123+
))}
124+
</div>
125+
</div>
126+
</div>
127+
)
128+
}

0 commit comments

Comments
 (0)