Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 351 additions & 0 deletions src/components/Analysis/AnalysisSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
import {
MoveMap,
Highlight,
BlunderMeter,
MovesByRating,
} from 'src/components/Analysis'
import { motion } from 'framer-motion'
import type { DrawShape } from 'chessground/draw'
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react'
import { useAnalysisController } from 'src/hooks/useAnalysisController'
import type { MaiaEvaluation, StockfishEvaluation } from 'src/types'

interface Props {
hover: (move?: string) => void
makeMove: (move: string) => void
setHoverArrow: Dispatch<SetStateAction<DrawShape | null>>
analysisEnabled: boolean
controller: ReturnType<typeof useAnalysisController>
handleToggleAnalysis: () => void
itemVariants?: {
hidden: {
opacity: number
y: number
}
visible: {
opacity: number
y: number
transition: {
duration: number
ease: number[]
type: string
}
}
exit: {
opacity: number
y: number
transition: {
duration: number
ease: number[]
type: string
}
}
}
}

export const AnalysisSidebar: React.FC<Props> = ({
hover,
makeMove,
controller,
setHoverArrow,
analysisEnabled,
handleToggleAnalysis,
itemVariants,
}) => {
// Mock handlers for when analysis is disabled
const emptyBlunderMeterData = useMemo(
() => ({
goodMoves: { moves: [], probability: 0 },
okMoves: { moves: [], probability: 0 },
blunderMoves: { moves: [], probability: 0 },
}),
[],
)

const emptyRecommendations = useMemo(
() => ({
maia: undefined,
stockfish: undefined,
}),
[],
)
const mockHover = useCallback(() => void 0, [])
const mockSetHoverArrow = useCallback(() => void 0, [])
const mockMakeMove = useCallback(() => void 0, [])

return (
<motion.div
id="analysis"
variants={itemVariants ?? {}}
className="desktop-right-column-container flex flex-col gap-2"
style={{ willChange: 'transform, opacity' }}
>
{/* Analysis Toggle Bar */}
<div className="flex h-10 min-h-10 items-center justify-between rounded bg-background-1 px-4">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-xl">analytics</span>
<h3 className="font-semibold">Analysis</h3>
</div>
<button
onClick={handleToggleAnalysis}
className={`flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors ${
analysisEnabled
? 'bg-human-4 text-white hover:bg-human-4/80'
: 'bg-background-2 text-secondary hover:bg-background-3'
}`}
>
<span className="material-symbols-outlined !text-xs">
{analysisEnabled ? 'visibility' : 'visibility_off'}
</span>
{analysisEnabled ? 'Visible' : 'Hidden'}
</button>
</div>

{/* Large screens : 2-row layout */}
<div className="hidden xl:flex xl:h-full xl:flex-col xl:gap-2">
{/* Combined Highlight + MovesByRating container */}
<div className="desktop-analysis-big-row-1-container relative flex gap-2">
<div className="flex h-full w-full overflow-hidden rounded border-[0.5px] border-white/40">
<div className="flex h-full w-auto min-w-[40%] max-w-[40%] border-r-[0.5px] border-white/40">
<Highlight
hover={analysisEnabled ? hover : mockHover}
makeMove={analysisEnabled ? makeMove : mockMakeMove}
currentMaiaModel={controller.currentMaiaModel}
setCurrentMaiaModel={controller.setCurrentMaiaModel}
recommendations={
analysisEnabled
? controller.moveRecommendations
: emptyRecommendations
}
moveEvaluation={
analysisEnabled
? (controller.moveEvaluation as {
maia?: MaiaEvaluation
stockfish?: StockfishEvaluation
})
: {
maia: undefined,
stockfish: undefined,
}
}
colorSanMapping={
analysisEnabled ? controller.colorSanMapping : {}
}
boardDescription={
analysisEnabled
? controller.boardDescription
: {
segments: [
{
type: 'text',
content:
'Analysis is disabled. Enable analysis to see detailed move evaluations and recommendations.',
},
],
}
}
currentNode={controller.currentNode}
/>
</div>
<div className="flex h-full w-full bg-background-1">
<MovesByRating
moves={analysisEnabled ? controller.movesByRating : undefined}
colorSanMapping={
analysisEnabled ? controller.colorSanMapping : {}
}
/>
</div>
</div>
{!analysisEnabled && (
<div className="absolute inset-0 z-10 flex items-center justify-center overflow-hidden rounded bg-background-1/80 backdrop-blur-sm">
<div className="rounded bg-background-2/90 p-4 text-center shadow-lg">
<span className="material-symbols-outlined mb-2 text-3xl text-human-3">
lock
</span>
<p className="font-medium text-primary">Analysis Disabled</p>
<p className="text-sm text-secondary">
Enable analysis to see move evaluations
</p>
</div>
</div>
)}
</div>

{/* MoveMap + BlunderMeter container */}
<div className="desktop-analysis-big-row-2-container relative flex flex-row gap-2">
<div className="flex h-full w-full flex-col">
<MoveMap
moveMap={analysisEnabled ? controller.moveMap : undefined}
colorSanMapping={
analysisEnabled ? controller.colorSanMapping : {}
}
setHoverArrow={
analysisEnabled ? setHoverArrow : mockSetHoverArrow
}
makeMove={analysisEnabled ? makeMove : mockMakeMove}
/>
</div>
<BlunderMeter
hover={analysisEnabled ? hover : mockHover}
makeMove={analysisEnabled ? makeMove : mockMakeMove}
data={
analysisEnabled ? controller.blunderMeter : emptyBlunderMeterData
}
colorSanMapping={analysisEnabled ? controller.colorSanMapping : {}}
moveEvaluation={
analysisEnabled ? controller.moveEvaluation : undefined
}
/>
{!analysisEnabled && (
<div className="absolute inset-0 z-10 flex items-center justify-center overflow-hidden rounded bg-background-1/80 backdrop-blur-sm">
<div className="rounded bg-background-2/90 p-4 text-center shadow-lg">
<span className="material-symbols-outlined mb-2 text-3xl text-human-3">
lock
</span>
<p className="font-medium text-primary">Analysis Disabled</p>
<p className="text-sm text-secondary">
Enable analysis to see position evaluation
</p>
</div>
</div>
)}
</div>
</div>

{/* Smaller screens: 3-row layout */}
<div className="flex h-full flex-col gap-2 xl:hidden">
{/* Row 1: Combined Highlight + BlunderMeter container */}
<div className="desktop-analysis-small-row-1-container relative flex overflow-hidden rounded border-[0.5px] border-white/40 bg-background-1">
<div className="flex h-full w-full border-r-[0.5px] border-white/40">
<Highlight
hover={analysisEnabled ? hover : mockHover}
makeMove={analysisEnabled ? makeMove : mockMakeMove}
currentMaiaModel={controller.currentMaiaModel}
setCurrentMaiaModel={controller.setCurrentMaiaModel}
recommendations={
analysisEnabled
? controller.moveRecommendations
: emptyRecommendations
}
moveEvaluation={
analysisEnabled
? (controller.moveEvaluation as {
maia?: MaiaEvaluation
stockfish?: StockfishEvaluation
})
: {
maia: undefined,
stockfish: undefined,
}
}
colorSanMapping={
analysisEnabled ? controller.colorSanMapping : {}
}
boardDescription={
analysisEnabled
? controller.boardDescription
: {
segments: [
{
type: 'text',
content:
'Analysis is disabled. Enable analysis to see detailed move evaluations and recommendations.',
},
],
}
}
currentNode={controller.currentNode}
/>
</div>
<div className="flex h-full w-auto min-w-[40%] max-w-[40%] bg-background-1 p-3">
<div className="h-full w-full">
<BlunderMeter
hover={analysisEnabled ? hover : mockHover}
makeMove={analysisEnabled ? makeMove : mockMakeMove}
data={
analysisEnabled
? controller.blunderMeter
: emptyBlunderMeterData
}
colorSanMapping={
analysisEnabled ? controller.colorSanMapping : {}
}
moveEvaluation={
analysisEnabled ? controller.moveEvaluation : undefined
}
showContainer={false}
/>
</div>
</div>
{!analysisEnabled && (
<div className="absolute inset-0 z-10 flex items-center justify-center overflow-hidden rounded bg-background-1/80 backdrop-blur-sm">
<div className="rounded bg-background-2/90 p-4 text-center shadow-lg">
<span className="material-symbols-outlined mb-2 text-3xl text-human-3">
lock
</span>
<p className="font-medium text-primary">Analysis Disabled</p>
<p className="text-sm text-secondary">
Enable analysis to see move evaluations
</p>
</div>
</div>
)}
</div>

{/* Row 2: MoveMap */}
<div className="desktop-analysis-small-row-2-container relative flex w-full">
<div className="h-full w-full">
<MoveMap
moveMap={analysisEnabled ? controller.moveMap : undefined}
colorSanMapping={
analysisEnabled ? controller.colorSanMapping : {}
}
setHoverArrow={
analysisEnabled ? setHoverArrow : mockSetHoverArrow
}
makeMove={analysisEnabled ? makeMove : mockMakeMove}
/>
</div>
{!analysisEnabled && (
<div className="absolute inset-0 z-10 flex items-center justify-center overflow-hidden rounded bg-background-1/80 backdrop-blur-sm">
<div className="rounded bg-background-2/90 p-4 text-center shadow-lg">
<span className="material-symbols-outlined mb-2 text-3xl text-human-3">
lock
</span>
<p className="font-medium text-primary">Analysis Disabled</p>
<p className="text-sm text-secondary">
Enable analysis to see position evaluation
</p>
</div>
</div>
)}
</div>

{/* Row 3: MovesByRating */}
<div className="desktop-analysis-small-row-3-container relative flex w-full">
<div className="h-full w-full">
<MovesByRating
moves={analysisEnabled ? controller.movesByRating : undefined}
colorSanMapping={
analysisEnabled ? controller.colorSanMapping : {}
}
/>
</div>
{!analysisEnabled && (
<div className="absolute inset-0 z-10 flex items-center justify-center overflow-hidden rounded bg-background-1/80 backdrop-blur-sm">
<div className="rounded bg-background-2/90 p-4 text-center shadow-lg">
<span className="material-symbols-outlined mb-2 text-3xl text-human-3">
lock
</span>
<p className="font-medium text-primary">Analysis Disabled</p>
<p className="text-sm text-secondary">
Enable analysis to see move evaluations
</p>
</div>
</div>
)}
</div>
</div>
</motion.div>
)
}
Loading
Loading