import { Paper , Typography } from '@material-ui/core' ; import cx from 'classnames' ; import React from 'react' ; import ReactDom from 'react-dom' ; import Linkify from 'react-linkify' ; import baseStyles from '../../base.module.scss' ; import { References } from '../../models' ; import { usePaperStore } from '../../stores/paper' ; import { PopupManager } from '../../utils/Popup' ; import { componentDecoratorWithTargetBlank , LinkifyArxivIds } from '../../utils/TextLinkifyLatex' ; import { useLatestCallback } from '../../utils/useLatestCallback' ; import { findOrCreateLayer } from '../pdfUtils/pdfjs-dom' ; export interface ActiveReference { anchor : HTMLElement ; id : string ; } interface PageCitationsProps { viewer : React . MutableRefObject < any > ; pageNumber : number ; references : References ; } const CITATION_CLASS_NAME = 'citation' ; const CITATION_TARGET = 'reference_id' ; export const PageCitations = ( { viewer , pageNumber , references } ) => { const currentCitations = references . citations . filter ( citation => citation . coordinates [ 0 ] ?. page === pageNumber ) ; const page = viewer . current . getPageView ( pageNumber - 1 ) ; const { viewport } = page ; const pageHeight = viewport . viewBox [ 3 ] ; return ( < div className = "page-citations absolute top-0 left-0" > { currentCitations . map ( ( citation , idx ) => { const props = { [ `data-${ CITATION_TARGET }` ] : citation . target , } ; return ( < React . Fragment key = { idx } > { citation . coordinates . map ( ( coords , coordsIdx ) => { const [ left , top ] = viewport . convertToViewportPoint ( coords . x , pageHeight - coords . y ) ; const [ width , height ] = viewport . convertToViewportPoint ( coords . w , pageHeight - coords . h ) ; return ( < div key = { `${ idx }-${ coordsIdx }` } className = { cx ( 'absolute' , CITATION_CLASS_NAME ) } style = { { top , left , width , height } } { ... props } / > ) ; } ) } < /React.Fragment>); } ) } < /div>); } ; interface UseRenderCitationsProps { viewer : React . MutableRefObject < any > ; setActiveReference ? : ( reference : ActiveReference ) => void ; isDocumentReady : boolean ; pagesReadyToRender : React . MutableRefObject < number [ ] > ; } const clearLinkAnnotations = ( viewer , pageNumber ) => { const pageDiv = viewer . current . getPageView ( pageNumber - 1 ) ?. div as Element | undefined ; if ( ! pageDiv ) return ; const linkAnnotations = pageDiv . querySelectorAll ( '.linkAnnotation' ) ; linkAnnotations . forEach ( link => link . remove ( ) ) ; } ; export const useRenderCitations = ( { viewer , isDocumentReady , pagesReadyToRender , setActiveReference , } ) => { const references = usePaperStore ( state => state . references ) ; const renderPageCitations = useLatestCallback ( ( pageNumber ) => { if ( ! references ) return ; const citationsLayer = findOrCreateLayer ( viewer , pageNumber , 'page-citations' ) ; if ( citationsLayer ) { clearLinkAnnotations ( viewer , pageNumber ) ; ReactDom . render ( < PageCitations { ... { viewer , references , pageNumber , setActiveReference } } / > , citationsLayer ) ; } } ) ; React . useEffect ( ( ) => { if ( ! isDocumentReady || ! references ) return ; for ( const pageNumber of pagesReadyToRender . current ) { renderPageCitations ( pageNumber ) ; } } , [ isDocumentReady , pagesReadyToRender , references , renderPageCitations ] ) ; return renderPageCitations ; } ; export const ReferencesPopupManager = ( { activeReference , clearReference } ) => { const references = usePaperStore ( state => state . references ) ; if ( ! references ) return null ; const activeReferenceData = activeReference ? references . bibliography [ activeReference . id ] : undefined ; const content = activeReferenceData ? ( < Paper className = { baseStyles . popup } > < Typography variant = "body2" className = "leading-5" > < LinkifyArxivIds > < Linkify componentDecorator = { componentDecoratorWithTargetBlank } > { activeReferenceData . text } < /Linkify> < /LinkifyArxivIds> < /Typography> < /Paper>) : null; return < PopupManager anchorEl = { activeReference ?. anchor } clearAnchor = { clearReference } popupContent = { content } / > ; } ; export const useToggleReferencesOnMove = ( isSelecting , setActiveReference ) => { const onMouseMove = useLatestCallback ( ( e ) => { if ( isSelecting ) return ; const target = e . target as HTMLElement ; if ( ! target ) return ; if ( target . classList . contains ( CITATION_CLASS_NAME ) ) { const referenceId = target . dataset [ CITATION_TARGET ] ; if ( ! referenceId ) { throw new Error ( 'referenceId is undefined' ) ; } setActiveReference ( { anchor : target , id : referenceId } ) ; } } ) ; return onMouseMove ; } ;