Spaces:
Runtime error
Runtime error
| import React, { useState, useEffect } from 'react'; | |
| import { Document, Page, pdfjs } from 'react-pdf'; | |
| import { Icons } from '../constants'; | |
| // Set worker source | |
| pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`; | |
| interface PdfViewerProps { | |
| fileBase64: string; | |
| mimeType: string; | |
| currentPage?: number; | |
| } | |
| const PdfViewer: React.FC<PdfViewerProps> = ({ fileBase64, mimeType, currentPage = 1 }) => { | |
| const [numPages, setNumPages] = useState<number>(0); | |
| const [pageNumber, setPageNumber] = useState(currentPage); | |
| const [scale, setScale] = useState(1.0); | |
| const [containerWidth, setContainerWidth] = useState<number>(0); | |
| // Sync with prop | |
| useEffect(() => { | |
| if (currentPage && currentPage !== pageNumber) { | |
| setPageNumber(currentPage); | |
| } | |
| }, [currentPage]); | |
| // Handle Resize | |
| const containerRef = React.useRef<HTMLDivElement>(null); | |
| useEffect(() => { | |
| if (!containerRef.current) return; | |
| const observer = new ResizeObserver(entries => { | |
| for (const entry of entries) { | |
| setContainerWidth(entry.contentRect.width); | |
| } | |
| }); | |
| observer.observe(containerRef.current); | |
| return () => observer.disconnect(); | |
| }, []); | |
| function onDocumentLoadSuccess({ numPages }: { numPages: number }) { | |
| setNumPages(numPages); | |
| } | |
| const handlePrevPage = () => setPageNumber(p => Math.max(p - 1, 1)); | |
| const handleNextPage = () => setPageNumber(p => Math.min(p + 1, numPages)); | |
| const handleZoomIn = () => setScale(s => Math.min(s + 0.25, 2.0)); | |
| const handleZoomOut = () => setScale(s => Math.max(s - 0.25, 0.5)); | |
| const dataUrl = `data:${mimeType};base64,${fileBase64}`; | |
| // If not PDF, show image | |
| if (mimeType !== 'application/pdf') { | |
| return ( | |
| <div className="h-full w-full bg-industrial-900 rounded-lg border border-industrial-800 flex flex-col overflow-hidden"> | |
| <div className="flex-1 relative bg-gray-800 overflow-auto flex items-center justify-center p-4"> | |
| <img src={dataUrl} alt="Source" className="max-w-full shadow-lg" /> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="h-full w-full bg-industrial-900 rounded-lg border border-industrial-800 flex flex-col overflow-hidden"> | |
| {/* Controls */} | |
| <div className="bg-industrial-950 px-4 py-2 border-b border-industrial-800 flex justify-between items-center shrink-0"> | |
| <span className="text-xs font-bold text-gray-500 uppercase">Document Viewer</span> | |
| <div className="flex items-center gap-2"> | |
| {/* Page Nav */} | |
| <div className="flex items-center gap-1 bg-industrial-900 rounded px-2 py-1"> | |
| <button onClick={handlePrevPage} disabled={pageNumber <= 1} className="p-1 hover:bg-industrial-800 rounded disabled:opacity-30"> | |
| <Icons.ChevronLeft /> | |
| </button> | |
| <span className="text-xs text-gray-400 px-2 min-w-[60px] text-center"> | |
| {pageNumber} / {numPages || '--'} | |
| </span> | |
| <button onClick={handleNextPage} disabled={pageNumber >= numPages} className="p-1 hover:bg-industrial-800 rounded disabled:opacity-30"> | |
| <Icons.ChevronRight /> | |
| </button> | |
| </div> | |
| {/* Zoom */} | |
| <div className="flex items-center gap-1 bg-industrial-900 rounded px-2 py-1"> | |
| <button onClick={handleZoomOut} className="p-1 hover:bg-industrial-800 rounded"> | |
| <Icons.ZoomOut /> | |
| </button> | |
| <span className="text-xs text-gray-400 px-2">{Math.round(scale * 100)}%</span> | |
| <button onClick={handleZoomIn} className="p-1 hover:bg-industrial-800 rounded"> | |
| <Icons.ZoomIn /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| {/* PDF Canvas */} | |
| <div ref={containerRef} className="flex-1 relative bg-gray-800 overflow-auto flex justify-center p-8"> | |
| <Document | |
| file={dataUrl} | |
| onLoadSuccess={onDocumentLoadSuccess} | |
| loading={<div className="text-white text-sm">Loading PDF...</div>} | |
| error={<div className="text-red-400 text-sm">Failed to load PDF.</div>} | |
| className="shadow-2xl" | |
| > | |
| <Page | |
| pageNumber={pageNumber} | |
| width={containerWidth ? Math.min(containerWidth - 64, 800) : 600} | |
| scale={scale} | |
| renderTextLayer={false} | |
| renderAnnotationLayer={false} | |
| className="shadow-lg" | |
| loading={null} | |
| /> | |
| </Document> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default PdfViewer; |