import { useCallback, useEffect, useRef, useState } from "react"; import { Document, Page } from "react-pdf"; import "react-pdf/dist/esm/Page/AnnotationLayer.css"; import "react-pdf/dist/esm/Page/TextLayer.css"; import ArrowCircleLeftIcon from "@mui/icons-material/ArrowCircleLeft"; import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight"; import { Box, IconButton } from "@mui/material"; import type { CustomTextRenderer, OnGetTextSuccess, } from "node_modules/react-pdf/dist/esm/shared/types"; import { socket } from "../socket"; import { API_HOST } from "../util/api"; import { highlightPattern } from "../util/highlighting"; interface PDFViewerProps { pitchBookId: string; currentPage?: number; onPageChange?: (page: number) => void; highlight: { text: string; page: number }[]; focusHighlight: { text: string; page: number }; } export default function PDFViewer({ pitchBookId, currentPage, onPageChange, highlight = [], focusHighlight, }: PDFViewerProps) { const [numPages, setNumPages] = useState(null); const [pageNumber, setPageNumber] = useState(currentPage || 1); const [containerWidth, setContainerWidth] = useState(null); const [pdfKey, setPdfKey] = useState(Date.now()); const containerRef = useRef(null); const [posHighlight, setPosHighlight] = useState([]); const [posHighlightFocus, setPosHighlightFocus] = useState([]); const [textContent, setTextContent] = useState< { posKey: string; text: string; i: number }[] >([]); const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { setNumPages(numPages); }; useEffect(() => { const updateWidth = () => { if (containerRef.current) { setContainerWidth(containerRef.current.offsetWidth); } }; updateWidth(); window.addEventListener("resize", updateWidth); return () => window.removeEventListener("resize", updateWidth); }, []); useEffect(() => { if (currentPage && currentPage !== pageNumber) { setPageNumber(currentPage); } }, [currentPage, pageNumber]); useEffect(() => { const handleProgress = (data: { id: number; progress: number }) => { if (data.id.toString() === pitchBookId && data.progress === 50) { setPdfKey(Date.now()); } }; socket.on("progress", handleProgress); return () => { socket.off("progress", handleProgress); }; }, [pitchBookId]); const handlePageChange = (newPage: number) => { setPageNumber(newPage); onPageChange?.(newPage); }; const textRenderer: CustomTextRenderer = useCallback( (textItem) => { return highlightPattern( textItem.str, `${textItem.width};${textItem.height};${textItem.transform}`, posHighlight, posHighlightFocus, ); }, [posHighlight, posHighlightFocus], ); useEffect(() => { const tmpPos: string[] = []; const tmpPosHighlight: string[] = []; if (textContent.length === 0) { setPosHighlight([]); setPosHighlightFocus([]); return; } const findTextPositions = (searchText: string): number[] => { const positions: number[] = []; const normalizedSearch = searchText.toLowerCase().trim(); textContent.forEach((item, index) => { if (item.text.toLowerCase().trim() === normalizedSearch) { positions.push(index); } }); if (positions.length === 0) { let cumulativeText = ''; const textBoundaries: { start: number; end: number; index: number }[] = []; textContent.forEach((item, index) => { const start = cumulativeText.length; cumulativeText += item.text; const end = cumulativeText.length; textBoundaries.push({ start, end, index }); }); const lowerCumulative = cumulativeText.toLowerCase(); let searchIndex = lowerCumulative.indexOf(normalizedSearch); while (searchIndex !== -1) { const endIndex = searchIndex + normalizedSearch.length; textBoundaries.forEach(boundary => { if ( (boundary.start <= searchIndex && searchIndex < boundary.end) || // Search starts in this item (boundary.start < endIndex && endIndex <= boundary.end) || // Search ends in this item (searchIndex <= boundary.start && boundary.end <= endIndex) // This item is completely within search ) { if (!positions.includes(boundary.index)) { positions.push(boundary.index); } } }); searchIndex = lowerCumulative.indexOf(normalizedSearch, searchIndex + 1); } } return positions.sort((a, b) => a - b); }; highlight .filter(h => h.page === pageNumber) .forEach(highlightItem => { const positions = findTextPositions(highlightItem.text); positions.forEach(pos => { if (pos >= 0 && pos < textContent.length) { tmpPos.push(textContent[pos].posKey); } }); }); if (focusHighlight?.page === pageNumber && focusHighlight.text) { const positions = findTextPositions(focusHighlight.text); positions.forEach(pos => { if (pos >= 0 && pos < textContent.length) { tmpPosHighlight.push(textContent[pos].posKey); } }); } setPosHighlight([...new Set(tmpPos)]); setPosHighlightFocus([...new Set(tmpPosHighlight)]); }, [highlight, focusHighlight, pageNumber, textContent]); const onGetTextSuccess: OnGetTextSuccess = useCallback((fullText) => { setTextContent( fullText.items.map((e, i) => ({ posKey: `${"width" in e ? e.width : 0};${"height" in e ? e.height : 0};${"transform" in e ? e.transform : ""}`, text: "str" in e ? e.str : "", i, })), ); }, []); return ( console.error("Es gab ein Fehler beim Laden des PDFs:", error) } onSourceError={(error) => console.error("Ungültige PDF:", error)} > {containerWidth && ( )} handlePageChange(pageNumber - 1)} > {pageNumber} / {numPages} = (numPages || 1)} onClick={() => handlePageChange(pageNumber + 1)} > ); }