import { useEffect, useRef, useState } from 'react' import { useStore } from '../store' interface FlipItem { row: number col: number oldLetter: string newLetter: string clueNumber: number | undefined id: number } let flipId = 0 export default function CellFlip({ svgRef }: { svgRef: React.RefObject }) { const entries = useStore((s) => s.entries) const puzzle = useStore((s) => s.puzzle) const [flips, setFlips] = useState([]) const prevEntries = useRef([]) const timeouts = useRef[]>([]) useEffect(() => () => { timeouts.current.forEach(id => clearTimeout(id)) }, []) useEffect(() => { if (!puzzle) return const prev = prevEntries.current const curr = entries const newFlips: FlipItem[] = [] for (let r = 0; r < curr.length; r++) { for (let c = 0; c < (curr[r]?.length || 0); c++) { const was = prev[r]?.[c] || '' const now = curr[r]?.[c] || '' if (now && now !== was) { const num = puzzle.cell_numbers[`${r},${c}`] newFlips.push({ row: r, col: c, oldLetter: was, newLetter: now, clueNumber: num, id: ++flipId }) } } } prevEntries.current = curr.map(r => [...r]) // skip animation when bulk-loading (e.g. reopening a solved puzzle) const totalWhiteCells = puzzle.cells.flat().filter(c => c !== 'black').length if (newFlips.length > 0 && newFlips.length < totalWhiteCells * 0.5) { // hide the SVG letter text for the flipping cells if (svgRef.current) { for (const flip of newFlips) { const texts = svgRef.current.querySelectorAll(`g[data-cell="${flip.row},${flip.col}"] .cell-letter`) texts.forEach(t => { t.style.opacity = '0' }) } } setFlips(f => [...f, ...newFlips]) timeouts.current.push(setTimeout(() => { // restore SVG letter visibility if (svgRef.current) { for (const flip of newFlips) { const texts = svgRef.current.querySelectorAll(`g[data-cell="${flip.row},${flip.col}"] .cell-letter`) texts.forEach(t => { t.style.opacity = '1' }) } } setFlips(f => f.filter(fl => !newFlips.some(n => n.id === fl.id))) }, 600)) } }, [entries, puzzle, svgRef]) if (!puzzle || !svgRef.current || flips.length === 0) return null const svg = svgRef.current const rect = svg.getBoundingClientRect() const cellW = rect.width / puzzle.width const cellH = rect.height / puzzle.height const fontSize = Math.round(cellW * 0.52) const numSize = Math.round(cellW * 0.26) return ( <> {flips.map(flip => (
{flip.clueNumber && ( {flip.clueNumber} )} {flip.oldLetter && }
{flip.clueNumber && ( {flip.clueNumber} )}
))} ) }