Skip to content

Commit

Permalink
fix: moves container alignment + fen & pgn export + added toasts to c…
Browse files Browse the repository at this point in the history
…opy clipboard
  • Loading branch information
kevinjosethomas committed Feb 12, 2025
1 parent a9c4072 commit 6c48f78
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/components/Board/AnalysisMovesContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const AnalysisMovesContainer: React.FC<Props> = ({
)

return (
<div className="red-scrollbar grid h-64 grid-cols-5 overflow-y-auto overflow-x-hidden whitespace-nowrap rounded-sm bg-background-1/60 md:h-full md:w-full">
<div className="red-scrollbar grid h-64 auto-rows-min grid-cols-5 overflow-y-auto overflow-x-hidden whitespace-nowrap rounded-sm bg-background-1/60 md:h-full md:w-full">
<Tooltip id="check" />
{moves.map(([whiteNode, blackNode], index) => {
return (
Expand Down
111 changes: 111 additions & 0 deletions src/components/Misc/AnalysisExportGame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Chess } from 'chess.ts'
import toast from 'react-hot-toast'
import { useEffect, useState } from 'react'
import { AnalyzedGame, GameNode } from 'src/types'

interface Props {
game: AnalyzedGame
currentNode: GameNode
whitePlayer: string
blackPlayer: string
event: string
}

export const AnalysisExportGame: React.FC<Props> = ({
game,
currentNode,
whitePlayer,
blackPlayer,
event,
}) => {
const [pgn, setPgn] = useState('')

useEffect(() => {
const chess = new Chess()
game.moves.forEach((move) => {
if (move.san) {
chess.move(move.san)
}
})
setPgn(chess.pgn())
}, [game.moves])

useEffect(() => {
if (!game.tree) return

game.tree.setHeader('ID', game.id)
game.tree.setHeader('Event', event)
game.tree.setHeader('Site', `https://maiachess.com/`)
game.tree.setHeader('White', whitePlayer)
game.tree.setHeader('Black', blackPlayer)
if (game.termination) {
game.tree.setHeader('Result', game.termination.result)
if (game.termination.condition) {
game.tree.setHeader('Termination', game.termination.condition)
}
}

setPgn(game.tree.toPGN())
}, [currentNode, game.moves])

const copy = (content: string) => {
navigator.clipboard.writeText(content)
toast.success('Copied to clipboard')
}

return (
<div className="flex w-full flex-col gap-2">
<div className="flex w-full flex-col gap-0.5">
<div className="flex w-full items-center justify-between">
<p className="select-none text-sm font-semibold tracking-wider text-secondary">
FEN
</p>
<i
tabIndex={0}
role="button"
onClick={() => copy(currentNode.fen)}
className="material-symbols-outlined select-none text-sm text-secondary hover:text-primary"
>
content_copy
</i>
</div>
<div
role="button"
tabIndex={0}
onClick={() => copy(currentNode.fen)}
className="border-1 group flex w-full cursor-pointer overflow-x-hidden rounded border border-white/5 bg-background-1/50 px-3 py-2"
>
<p className="whitespace-nowrap text-xs text-secondary group-hover:text-secondary/80">
{currentNode.fen}
</p>
</div>
</div>
<div className="flex w-full flex-col gap-0.5">
<div className="flex w-full items-center justify-between">
<p className="select-none text-sm font-semibold tracking-wider text-secondary">
PGN
</p>
<i
tabIndex={0}
role="button"
onClick={() => copy(pgn)}
className="material-symbols-outlined select-none text-sm text-secondary hover:text-primary"
>
content_copy
</i>
</div>
<div
role="button"
tabIndex={0}
onClick={() => copy(pgn)}
className="group flex max-h-32 w-full cursor-pointer overflow-x-hidden overflow-y-scroll rounded border border-white/5 bg-background-1/50 p-3"
>
<p className="whitespace-pre-wrap text-xs text-secondary group-hover:text-secondary/80">
{pgn}
</p>
</div>
</div>
</div>
)
}
8 changes: 4 additions & 4 deletions src/pages/analysis/[...id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
MaiaEvaluation,
StockfishEvaluation,
GameNode,
PlayedGame,
} from 'src/types'
import {
ModalContext,
Expand All @@ -31,7 +30,6 @@ import {
MoveMap,
GameInfo,
Highlight,
ExportGame,
BlunderMeter,
AnalysisGameList,
AnalysisGameBoard,
Expand All @@ -51,6 +49,7 @@ import { useAnalysisController } from 'src/hooks'
import { AnimatePresence, motion } from 'framer-motion'
import type { DrawBrushes, DrawShape } from 'chessground/draw'
import { ConfigureAnalysis } from 'src/components/Analysis/ConfigureAnalysis'
import { AnalysisExportGame } from 'src/components/Misc/AnalysisExportGame'

const MAIA_MODELS = [
'maia_kdd_1100',
Expand Down Expand Up @@ -537,8 +536,9 @@ const Analysis: React.FC<Props> = ({
/>
) : screen.id === 'export' ? (
<div className="flex w-full flex-col p-4">
<ExportGame
game={analyzedGame as unknown as PlayedGame}
<AnalysisExportGame
game={analyzedGame}
currentNode={controller.currentNode as GameNode}
whitePlayer={analyzedGame.whitePlayer.name}
blackPlayer={analyzedGame.blackPlayer.name}
event="Analysis"
Expand Down
44 changes: 10 additions & 34 deletions src/types/base/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class GameTree {
return this.headers.get(key)
}

toPGN(includeVariations = true): string {
toPGN(): string {
const chess = new Chess()

if (this.root.fen !== chess.fen()) {
Expand All @@ -39,45 +39,21 @@ export class GameTree {

this.headers.forEach((value, key) => {
chess.addHeader(key, value)
chess.addHeader(key, value)
})

const buildLine = (node: GameNode): string => {
let line = ''

if (node.san) {
chess.move(node.san)
line += ' ' + node.san
}

if (includeVariations) {
const variations = node.getVariations()
if (variations.length > 0) {
variations.forEach((variation) => {
const boardState = chess.fen()
line += ' ('
line += buildLine(variation)
line += ')'
chess.load(boardState)
})
}
}

let complete = false
let node = this.root
while (!complete) {
if (node.mainChild) {
line += buildLine(node.mainChild)
node = node.mainChild
chess.move(node.san || node.move || '')
} else {
complete = true
}

return line
}

const result = this.headers.get('Result')
if (result) {
chess.addHeader('Result', result)
}

return chess
.pgn()
.replace(/\s{2,}/g, ' ')
.trim()
return chess.pgn()
}

getRoot(): GameNode {
Expand Down

0 comments on commit 6c48f78

Please sign in to comment.