1- import React , { useContext } from 'react'
1+ import React , { useContext , useState } from 'react'
22import { motion } from 'framer-motion'
33
44import { BlunderMeterResult , ColorSanMapping } from 'src/types'
55import { WindowSizeContext } from 'src/contexts'
6+ import { MoveTooltip } from './MoveTooltip'
67
78interface Props {
89 data : BlunderMeterResult
910 colorSanMapping : ColorSanMapping
1011 hover : ( move ?: string ) => void
1112 makeMove : ( move : string ) => void
13+ moveEvaluation ?: {
14+ maia ?: { policy : { [ key : string ] : number } }
15+ stockfish ?: {
16+ cp_vec : { [ key : string ] : number }
17+ winrate_vec ?: { [ key : string ] : number }
18+ winrate_loss_vec ?: { [ key : string ] : number }
19+ }
20+ } | null
1221}
1322
1423export const BlunderMeter : React . FC < Props > = ( {
1524 data,
1625 hover,
1726 makeMove,
1827 colorSanMapping,
28+ moveEvaluation,
1929} : Props ) => {
2030 const { isMobile } = useContext ( WindowSizeContext )
2131
@@ -25,13 +35,15 @@ export const BlunderMeter: React.FC<Props> = ({
2535 hover = { hover }
2636 makeMove = { makeMove }
2737 colorSanMapping = { colorSanMapping }
38+ moveEvaluation = { moveEvaluation }
2839 />
2940 ) : (
3041 < DesktopBlunderMeter
3142 data = { data }
3243 hover = { hover }
3344 makeMove = { makeMove }
3445 colorSanMapping = { colorSanMapping }
46+ moveEvaluation = { moveEvaluation }
3547 />
3648 )
3749}
@@ -41,6 +53,7 @@ const DesktopBlunderMeter: React.FC<Props> = ({
4153 hover,
4254 makeMove,
4355 colorSanMapping,
56+ moveEvaluation,
4457} : Props ) => {
4558 return (
4659 < div className = "flex h-64 max-h-full w-full flex-col gap-2 overflow-hidden rounded bg-background-1/60 p-3 md:h-full md:w-auto md:min-w-[40%] md:max-w-[40%]" >
@@ -56,6 +69,7 @@ const DesktopBlunderMeter: React.FC<Props> = ({
5669 bgColor = "bg-[#1a9850] rounded-t"
5770 probability = { data . goodMoves . probability }
5871 colorSanMapping = { colorSanMapping }
72+ moveEvaluation = { moveEvaluation }
5973 />
6074 < Meter
6175 hover = { hover }
@@ -66,6 +80,7 @@ const DesktopBlunderMeter: React.FC<Props> = ({
6680 textColor = "text-[#fee08b]"
6781 probability = { data . okMoves . probability }
6882 colorSanMapping = { colorSanMapping }
83+ moveEvaluation = { moveEvaluation }
6984 />
7085 < Meter
7186 hover = { hover }
@@ -76,6 +91,7 @@ const DesktopBlunderMeter: React.FC<Props> = ({
7691 moves = { data . blunderMoves . moves }
7792 probability = { data . blunderMoves . probability }
7893 colorSanMapping = { colorSanMapping }
94+ moveEvaluation = { moveEvaluation }
7995 />
8096 </ div >
8197 </ div >
@@ -88,6 +104,7 @@ const MobileBlunderMeter: React.FC<Props> = ({
88104 hover,
89105 makeMove,
90106 colorSanMapping,
107+ moveEvaluation,
91108} : Props ) => {
92109 return (
93110 < div className = "flex w-full flex-col gap-2 overflow-hidden rounded bg-background-1/60 p-3" >
@@ -135,6 +152,7 @@ const MobileBlunderMeter: React.FC<Props> = ({
135152 hover = { hover }
136153 makeMove = { makeMove }
137154 colorSanMapping = { colorSanMapping }
155+ moveEvaluation = { moveEvaluation }
138156 />
139157 < MovesList
140158 title = "Meh Moves"
@@ -143,6 +161,7 @@ const MobileBlunderMeter: React.FC<Props> = ({
143161 hover = { hover }
144162 makeMove = { makeMove }
145163 colorSanMapping = { colorSanMapping }
164+ moveEvaluation = { moveEvaluation }
146165 />
147166 < MovesList
148167 title = "Blunders"
@@ -151,6 +170,7 @@ const MobileBlunderMeter: React.FC<Props> = ({
151170 hover = { hover }
152171 makeMove = { makeMove }
153172 colorSanMapping = { colorSanMapping }
173+ moveEvaluation = { moveEvaluation }
154174 />
155175 </ div >
156176 </ div >
@@ -165,18 +185,45 @@ function MovesList({
165185 hover,
166186 makeMove,
167187 colorSanMapping,
188+ moveEvaluation,
168189} : {
169190 title : string
170191 textColor : string
171192 moves : { move : string ; probability : number } [ ]
172193 hover : ( move ?: string ) => void
173194 makeMove : ( move : string ) => void
174195 colorSanMapping : ColorSanMapping
196+ moveEvaluation ?: {
197+ maia ?: { policy : { [ key : string ] : number } }
198+ stockfish ?: {
199+ cp_vec : { [ key : string ] : number }
200+ winrate_vec ?: { [ key : string ] : number }
201+ winrate_loss_vec ?: { [ key : string ] : number }
202+ }
203+ }
175204} ) {
205+ const [ tooltipData , setTooltipData ] = useState < {
206+ move : string
207+ position : { x : number ; y : number }
208+ } | null > ( null )
209+
176210 const filteredMoves = ( ) => {
177211 return moves . slice ( 0 , 6 ) . filter ( ( move ) => move . probability >= 8 )
178212 }
179213
214+ const handleMouseEnter = ( move : string , event : React . MouseEvent ) => {
215+ hover ( move )
216+ setTooltipData ( {
217+ move,
218+ position : { x : event . clientX , y : event . clientY } ,
219+ } )
220+ }
221+
222+ const handleMouseLeave = ( ) => {
223+ hover ( )
224+ setTooltipData ( null )
225+ }
226+
180227 return (
181228 < div className = "flex flex-col" >
182229 < p className = { `text-sm font-medium ${ textColor } ` } > { title } </ p >
@@ -185,15 +232,32 @@ function MovesList({
185232 < button
186233 key = { move . move }
187234 className = "text-left hover:underline"
188- onMouseLeave = { ( ) => hover ( ) }
189- onMouseEnter = { ( ) => hover ( move . move ) }
235+ onMouseLeave = { handleMouseLeave }
236+ onMouseEnter = { ( e ) => handleMouseEnter ( move . move , e ) }
190237 onClick = { ( ) => makeMove ( move . move ) }
191238 >
192239 { colorSanMapping [ move . move ] ?. san || move . move } (
193240 { Math . round ( move . probability ) } %)
194241 </ button >
195242 ) ) }
196243 </ div >
244+
245+ { /* Tooltip */ }
246+ { tooltipData && moveEvaluation && (
247+ < MoveTooltip
248+ move = { tooltipData . move }
249+ colorSanMapping = { colorSanMapping }
250+ maiaProb = { moveEvaluation . maia ?. policy [ tooltipData . move ] }
251+ stockfishCp = { moveEvaluation . stockfish ?. cp_vec [ tooltipData . move ] }
252+ stockfishWinrate = {
253+ moveEvaluation . stockfish ?. winrate_vec ?. [ tooltipData . move ]
254+ }
255+ stockfishLoss = {
256+ moveEvaluation . stockfish ?. winrate_loss_vec ?. [ tooltipData . move ]
257+ }
258+ position = { tooltipData . position }
259+ />
260+ ) }
197261 </ div >
198262 )
199263}
@@ -245,6 +309,7 @@ function Meter({
245309 textColor,
246310 probability,
247311 colorSanMapping,
312+ moveEvaluation,
248313} : {
249314 title : string
250315 textColor : string
@@ -254,11 +319,37 @@ function Meter({
254319 makeMove : ( move : string ) => void
255320 colorSanMapping : ColorSanMapping
256321 moves : { move : string ; probability : number } [ ]
322+ moveEvaluation ?: {
323+ maia ?: { policy : { [ key : string ] : number } }
324+ stockfish ?: {
325+ cp_vec : { [ key : string ] : number }
326+ winrate_vec ?: { [ key : string ] : number }
327+ winrate_loss_vec ?: { [ key : string ] : number }
328+ }
329+ }
257330} ) {
331+ const [ tooltipData , setTooltipData ] = useState < {
332+ move : string
333+ position : { x : number ; y : number }
334+ } | null > ( null )
335+
258336 const filteredMoves = ( ) => {
259337 return moves . slice ( 0 , 6 ) . filter ( ( move ) => move . probability >= 8 )
260338 }
261339
340+ const handleMouseEnter = ( move : string , event : React . MouseEvent ) => {
341+ hover ( move )
342+ setTooltipData ( {
343+ move,
344+ position : { x : event . clientX , y : event . clientY } ,
345+ } )
346+ }
347+
348+ const handleMouseLeave = ( ) => {
349+ hover ( )
350+ setTooltipData ( null )
351+ }
352+
262353 return (
263354 < motion . div
264355 className = "flex min-h-6 w-full flex-row items-start justify-start gap-2 overflow-hidden"
@@ -282,8 +373,8 @@ function Meter({
282373 < button
283374 key = { move . move }
284375 className = "text-left hover:underline"
285- onMouseLeave = { ( ) => hover ( ) }
286- onMouseEnter = { ( ) => hover ( move . move ) }
376+ onMouseLeave = { handleMouseLeave }
377+ onMouseEnter = { ( e ) => handleMouseEnter ( move . move , e ) }
287378 onClick = { ( ) => makeMove ( move . move ) }
288379 >
289380 { colorSanMapping [ move . move ] ?. san || move . move } (
@@ -292,6 +383,23 @@ function Meter({
292383 ) ) }
293384 </ div >
294385 </ div >
386+
387+ { /* Tooltip */ }
388+ { tooltipData && moveEvaluation && (
389+ < MoveTooltip
390+ move = { tooltipData . move }
391+ colorSanMapping = { colorSanMapping }
392+ maiaProb = { moveEvaluation . maia ?. policy [ tooltipData . move ] }
393+ stockfishCp = { moveEvaluation . stockfish ?. cp_vec [ tooltipData . move ] }
394+ stockfishWinrate = {
395+ moveEvaluation . stockfish ?. winrate_vec ?. [ tooltipData . move ]
396+ }
397+ stockfishLoss = {
398+ moveEvaluation . stockfish ?. winrate_loss_vec ?. [ tooltipData . move ]
399+ }
400+ position = { tooltipData . position }
401+ />
402+ ) }
295403 </ motion . div >
296404 )
297405}
0 commit comments