import React, { useState, useEffect } from 'react'; import { setTimeout } from 'timers'; import useActions from '../actions'; import useStore from '../store'; import SearchComponent from '../components/Search'; import { getPdfPage, normalize, calculatePhraseMatch } from '../helpers/pdf'; import { extractTextItems } from '../helpers/search'; import { scrollIntoView } from '../helpers/utility'; let timer: ReturnType = setTimeout(() => '', 100); let localMatchesMap: MatchType[] = []; let localTotal = 0; const Search: React.FC = () => { const [isProcessing, setProcessing] = useState(false); const [matchTotal, setMatchTotal] = useState(0); const [ { navbarState, pdf, totalPage, queryString, currentIndex, matchesMap }, dispatch, ] = useStore(); const { setNavbar, setQueryString, setMatchesMap, setCurrentIndex, } = useActions(dispatch); const getMatchTextIndex = async ( pageNum: number, queryStr: string, ): Promise => { const contentItems = await extractTextItems(() => getPdfPage(pdf, pageNum)); const content = normalize(contentItems.join('').toLowerCase()); const matches = calculatePhraseMatch(content, queryStr); if (matches.length) { matches.forEach((ele) => { localMatchesMap.push({ page: pageNum, index: ele, }); }); localTotal += matches.length; } if (pageNum === totalPage) { setMatchesMap(localMatchesMap); setMatchTotal(localTotal); setProcessing(false); } if (pageNum < totalPage) { await getMatchTextIndex(pageNum + 1, queryStr); } }; const startSearchPdf = async (queryStr: string): Promise => { getMatchTextIndex(1, queryStr); }; const reset = (): void => { localMatchesMap = []; localTotal = 0; setMatchesMap([]); setMatchTotal(0); setCurrentIndex(-1); setQueryString(''); }; const handleSearch = (val: string): void => { if (!val) return; const newQueryString = normalize(val.toLowerCase()); if (newQueryString !== queryString) { setProcessing(true); reset(); setQueryString(newQueryString); startSearchPdf(newQueryString); } }; const scrollToPage = (pageNum: number) => { const pageDiv: HTMLDivElement = document.getElementById( `page_${pageNum}`, ) as HTMLDivElement; scrollIntoView(pageDiv); }; const highlightTarget = (match: MatchType, color: string) => { timer = setTimeout(() => { const id = `${match.page}_${match.index}`; const pageElement: HTMLDivElement = document.getElementById( `page_${match.page}`, ) as HTMLDivElement; const textLayer: HTMLDivElement = pageElement.querySelector( '[data-id="text-layer"]', ) as HTMLDivElement; if (textLayer) { const span = textLayer.querySelector(`[class="${id}"]`); if (span) { (span as HTMLElement).style.backgroundColor = color; } else { highlightTarget(match, color); } } else { highlightTarget(match, color); } }, 200); }; const handleClickPrev = (): void => { if (currentIndex > 0) { const currentMatch = matchesMap[currentIndex]; if (currentMatch) { highlightTarget(currentMatch, 'rgba(255, 211, 0, 0.7)'); } setCurrentIndex(currentIndex - 1); const nextMatch = matchesMap[currentIndex - 1]; scrollToPage(nextMatch.page); } }; const handleClickNext = (): void => { if (currentIndex + 1 < matchTotal) { const currentMatch = matchesMap[currentIndex]; if (currentMatch) { highlightTarget(currentMatch, 'rgba(255, 211, 0, 0.7)'); } setCurrentIndex(currentIndex + 1); const indexObj = matchesMap[currentIndex + 1]; scrollToPage(indexObj.page); } }; const handleClose = (): void => { setNavbar(''); reset(); }; useEffect(() => { if (matchTotal >= 1 && currentIndex === -1) { setCurrentIndex(currentIndex + 1); const indexObj = localMatchesMap[currentIndex + 1]; scrollToPage(indexObj.page); } }, [matchTotal, currentIndex]); useEffect(() => { if (currentIndex >= 0) { clearTimeout(timer); const match = matchesMap[currentIndex]; highlightTarget(match, 'rgb(255, 141, 0)'); } }, [currentIndex, matchesMap]); return ( ); }; export default Search;