import React, { useEffect, useMemo, useRef, useState } from 'react' import { BaseTooltip } from '@jbrowse/core/ui' import { useTheme } from '@mui/material' import { autorun } from 'mobx' import { observer } from 'mobx-react' import { renderBoxFeatureCanvasBlock } from './renderBoxFeatureCanvasBlock.ts' import { renderMSABlock } from './renderMSABlock.ts' import { colorContrast } from '../../util.ts' import type { MsaViewModel } from '../../model.ts' const MSACanvasBlock = observer(function ({ model, offsetX, offsetY, }: { model: MsaViewModel offsetX: number offsetY: number }) { const { colWidth, rowHeight, scrollY, scrollX, colorScheme, blockSize, mouseClickCol, mouseClickRow, highResScaleFactor, } = model const theme = useTheme() const contrastScheme = useMemo( () => colorContrast(colorScheme, theme), [colorScheme, theme], ) const ref = useRef(null) useEffect(() => { const ctx = ref.current?.getContext('2d') if (!ctx) { return } return autorun(() => { ctx.resetTransform() ctx.clearRect( 0, 0, blockSize * highResScaleFactor, blockSize * highResScaleFactor, ) if (model.actuallyShowDomains) { renderBoxFeatureCanvasBlock({ ctx, offsetX, offsetY, model, }) } renderMSABlock({ ctx, theme, offsetX, offsetY, contrastScheme, model, }) }) }, [ model, offsetX, offsetY, theme, blockSize, highResScaleFactor, contrastScheme, ]) const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>() const { hoveredInsertion } = model return ( <> { if (!ref.current) { return } setMousePosition({ x: event.clientX, y: event.clientY }) const { left, top } = ref.current.getBoundingClientRect() const mouseX = event.clientX - left + offsetX const mouseY = event.clientY - top + offsetY const x = Math.floor(mouseX / colWidth) const y = Math.floor(mouseY / rowHeight) // Only set mouse position if within valid MSA bounds if (x >= 0 && x < model.numColumns && y >= 0 && y < model.numRows) { model.setMousePos(x, y) } else { model.setMousePos(undefined, undefined) } }} onClick={event => { if (!ref.current) { return } const { left, top } = ref.current.getBoundingClientRect() const mouseX = event.clientX - left + offsetX const mouseY = event.clientY - top + offsetY const x = Math.floor(mouseX / colWidth) const y = Math.floor(mouseY / rowHeight) if (x === mouseClickCol && y === mouseClickRow) { model.setMouseClickPos(undefined, undefined) } else { model.setMouseClickPos(x, y) } }} onMouseLeave={() => { model.setMousePos() setMousePosition(undefined) }} width={blockSize * highResScaleFactor} height={blockSize * highResScaleFactor} style={{ position: 'absolute', top: scrollY + offsetY, left: scrollX + offsetX, width: blockSize, height: blockSize, }} /> {hoveredInsertion && mousePosition ? ( Insertion ({hoveredInsertion.letters.length} {model.sequenceType === 'amino' ? 'aa' : 'bp'}):{' '} {hoveredInsertion.letters.length > 20 ? `${hoveredInsertion.letters.slice(0, 20)}...` : hoveredInsertion.letters} ) : null} ) }) export default MSACanvasBlock