import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { Document, pdfjs } from "react-pdf"; import { FilePdf } from "@phosphor-icons/react"; import { api } from "./api.js"; import { Spinner } from "./ui.jsx"; import { useContinuousPdf } from "./pdf/useContinuousPdf.js"; import { PdfSheet } from "./pdf/PdfSheet.jsx"; import { PdfToolbar } from "./pdf/PdfToolbar.jsx"; import { BookSearchDialog } from "./pdf/BookSearchDialog.jsx"; import { readPdfOutline } from "./pdf/outline.js"; import "react-pdf/dist/Page/TextLayer.css"; import "react-pdf/dist/Page/AnnotationLayer.css"; pdfjs.GlobalWorkerOptions.workerSrc = new URL( "pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url, ).toString(); const pdfOptions = { cMapUrl: "/pdf-assets/cmaps/", cMapPacked: true, standardFontDataUrl: "/pdf-assets/standard_fonts/", // Bundled fallback fonts keep base-14 PDFs consistent across local platforms. useSystemFonts: false, wasmUrl: "/pdf-assets/wasm/", disableAutoFetch: true, disableStream: true, rangeChunkSize: 128 * 1024, }; const EMPTY_HIGHLIGHTS = []; export function PdfReader({ material, highlights, page, navigationToken, onPage, onVisiblePage, onSelection, onContext, onOutline, notify, screenshotRef, }) { const viewer = useContinuousPdf({ material, page, navigationToken, onVisiblePage, notify, screenshotRef, }); const [search, setSearch] = useState(false); const alive = useRef(true); useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []); const pageHighlights = useMemo(() => { const byPage = new Map(); for (const highlight of highlights) { if (!byPage.has(highlight.page)) byPage.set(highlight.page, []); byPage.get(highlight.page).push(highlight); } return byPage; }, [highlights]); const loaded = useCallback( async (pdf) => { try { await viewer.loadDocument(pdf); const outline = await readPdfOutline(pdf); if (alive.current) onOutline(outline); } catch (error) { if (alive.current) notify(error.message, "error"); } }, [viewer.loadDocument, onOutline, notify], ); const pages = []; for ( let number = viewer.windowRange.start; number <= viewer.windowRange.end; number++ ) { pages.push( , ); } return (
setSearch(true)} onContext={onContext} />
} error={} onLoadSuccess={loaded} onItemClick={({ pageNumber }) => { if (pageNumber) onPage(pageNumber); }} > {viewer.ready && (
{pages}
)}
); } function PdfError({ materialId }) { return (

Could not open this PDF

It may be password protected or damaged.

Open original file
); } function PdfNotice({ material, notify }) { if (material.status === "image-only") return (
This PDF has no searchable text. Enable image input in Settings, then attach the current page when asking a question.
); if (material.status === "indexing") return (
Indexing locally: {material.progress} / {material.page_count || "?"}{" "} pages. You can read while the index is built.
); if (material.status === "error") return (
{material.error}
); return null; }