docs / src /components /pdf-viewer.tsx
Zerotracex-Stuff
First model version
a5871f0
'use client';
import * as React from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
DialogClose,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import {
ChevronLeft,
ChevronRight,
ZoomIn,
ZoomOut,
Download,
Loader2,
Maximize,
Minimize,
} from 'lucide-react';
import { Document, Page, pdfjs } from 'react-pdf';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import 'react-pdf/dist/esm/Page/TextLayer.css';
import { cn } from '@/lib/utils';
// Configure the worker to load pdfs.
pdfjs.GlobalWorkerOptions.workerSrc = `/pdf.worker.min.js`;
interface PdfViewerProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
fileUrl: string;
fileName: string;
}
export function PdfViewer({
isOpen,
onOpenChange,
fileUrl,
fileName,
}: PdfViewerProps) {
const [numPages, setNumPages] = React.useState<number | null>(null);
const [pageNumber, setPageNumber] = React.useState(1);
const [scale, setScale] = React.useState(1.0);
const [isLoading, setIsLoading] = React.useState(true);
const [isFullscreen, setIsFullscreen] = React.useState(false);
const viewerRef = React.useRef<HTMLDivElement>(null);
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
setNumPages(numPages);
setPageNumber(1);
setIsLoading(false);
}
function onDocumentLoadError(error: Error) {
console.error('Failed to load PDF:', error);
setIsLoading(false);
}
const goToPrevPage = () => {
setPageNumber((prevPageNumber) => Math.max(prevPageNumber - 1, 1));
};
const goToNextPage = () => {
setPageNumber((prevPageNumber) =>
Math.min(prevPageNumber + 1, numPages || 1)
);
};
const zoomIn = () => {
setScale((prevScale) => Math.min(prevScale + 0.2, 3));
};
const zoomOut = () => {
setScale((prevScale) => Math.max(prevScale - 0.2, 0.5));
};
const toggleFullscreen = () => {
if (!viewerRef.current) return;
if (!document.fullscreenElement) {
viewerRef.current.requestFullscreen().catch(err => {
console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
});
} else {
document.exitFullscreen();
}
};
React.useEffect(() => {
const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement);
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
}, []);
React.useEffect(() => {
if (isOpen) {
setIsLoading(true);
setNumPages(null);
setPageNumber(1);
setScale(1.0);
}
}, [isOpen, fileUrl]);
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl w-full h-[90vh] flex flex-col p-0 gap-0">
<div ref={viewerRef} className="flex flex-col w-full h-full bg-background">
<DialogHeader className="p-4 border-b shrink-0">
<DialogTitle className="truncate">{fileName}</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-auto flex items-center justify-center bg-muted/20 relative">
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-10">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
)}
<Document
file={fileUrl}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onDocumentLoadError}
loading=""
>
<Page
pageNumber={pageNumber}
scale={scale}
renderTextLayer={false}
renderAnnotationLayer={false}
loading=""
className="flex justify-center"
/>
</Document>
</div>
<DialogFooter className="p-2 border-t bg-background flex-wrap justify-between shrink-0">
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={zoomOut}
disabled={!numPages}
>
<ZoomOut className="h-4 w-4" />
</Button>
<span className="text-sm text-muted-foreground">{Math.round(scale * 100)}%</span>
<Button
variant="outline"
size="icon"
onClick={zoomIn}
disabled={!numPages}
>
<ZoomIn className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" onClick={toggleFullscreen}>
{isFullscreen ? <Minimize className="h-4 w-4" /> : <Maximize className="h-4 w-4" />}
<span className="sr-only">{isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}</span>
</Button>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={goToPrevPage}
disabled={pageNumber <= 1}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-sm text-muted-foreground">
Page {pageNumber} of {numPages || '...'}
</span>
<Button
variant="outline"
size="icon"
onClick={goToNextPage}
disabled={!numPages || pageNumber >= numPages}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
<div className="flex items-center gap-2">
<a href={fileUrl} download={fileName}>
<Button variant="default">
<Download className="mr-2 h-4 w-4" />
Download
</Button>
</a>
<DialogClose asChild>
<Button variant="outline">Close</Button>
</DialogClose>
</div>
</DialogFooter>
</div>
</DialogContent>
</Dialog>
);
}