disambiguation popup
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '../store'
|
||||
import Tooltip from './ui/Tooltip'
|
||||
|
||||
interface Props {
|
||||
svgRef: React.RefObject<SVGSVGElement | null>
|
||||
}
|
||||
|
||||
interface PopupGeom {
|
||||
top: number
|
||||
left: number
|
||||
}
|
||||
|
||||
export default function DisambiguationPopup({ svgRef }: Props) {
|
||||
const { t } = useTranslation()
|
||||
const show = useStore((s) => s.showDisambiguation)
|
||||
const result = useStore((s) => s.recognitionResult)
|
||||
const puzzle = useStore((s) => s.puzzle)
|
||||
const acceptCandidate = useStore((s) => s.acceptCandidate)
|
||||
const dismissDisambiguation = useStore((s) => s.dismissDisambiguation)
|
||||
|
||||
const popupRef = useRef<HTMLDivElement>(null)
|
||||
const [geom, setGeom] = useState<PopupGeom | null>(null)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (show && result && puzzle && svgRef.current) {
|
||||
const rect = svgRef.current.getBoundingClientRect()
|
||||
const cellW = rect.width / puzzle.width
|
||||
const cellH = rect.height / puzzle.height
|
||||
const cellX = result.cell.col * cellW
|
||||
const cellY = result.cell.row * cellH
|
||||
// position above the cell, or below if near the top
|
||||
const above = result.cell.row > 1
|
||||
setGeom({ top: above ? cellY - 40 : cellY + cellH + 4, left: Math.max(0, cellX - 20) })
|
||||
} else {
|
||||
setGeom(null)
|
||||
}
|
||||
}, [show, result, puzzle, svgRef])
|
||||
|
||||
useEffect(() => {
|
||||
if (show && popupRef.current) {
|
||||
const firstBtn = popupRef.current.querySelector<HTMLButtonElement>('.disambiguation-btn')
|
||||
firstBtn?.focus()
|
||||
}
|
||||
}, [show])
|
||||
|
||||
if (!show || !result || !geom) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={popupRef}
|
||||
className="disambiguation"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: geom.top,
|
||||
left: geom.left,
|
||||
}}
|
||||
>
|
||||
{result.candidates.slice(0, 3).map((c) => (
|
||||
<Tooltip text={`${Math.round(c.confidence * 100)}%`} key={c.letter}>
|
||||
<button
|
||||
className="disambiguation-btn"
|
||||
onClick={() => acceptCandidate(c.letter)}
|
||||
>
|
||||
{c.letter}
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
<Tooltip text={t('common.dismiss')}>
|
||||
<button
|
||||
className="disambiguation-btn dismiss"
|
||||
onClick={dismissDisambiguation}
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user