add document viewer

This commit is contained in:
smanylov
2026-05-01 15:12:00 +07:00
parent 881f3295c0
commit a3cbaaa2e7
10 changed files with 814 additions and 92 deletions
+248
View File
@@ -0,0 +1,248 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import { usePdf } from '@/app/contexts/PdfContext';
interface PdfViewerProps {
fileId: string;
fileName?: string;
}
export default function PdfViewer({ fileId, fileName = 'document.pdf' }: PdfViewerProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [fullscreen, setFullscreen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const pdfDocumentRef = useRef<any>(null);
const { setPdfData } = usePdf();
useEffect(() => {
let isMounted = true;
let objectUrl: string | null = null;
const loadAndDisplayPdf = async () => {
try {
setLoading(true);
// 1. Получаем данные документа как ArrayBuffer (а не blob URL)
const { requestFileAsBuffer } = await import('@/app/actions/trackingActions');
const pdfData = await requestFileAsBuffer(fileId);
if (!pdfData) {
throw new Error('Failed to load document');
}
setPdfData(pdfData, fileName);
if (!isMounted) return;
const pdfjs = await import('pdfjs-dist');
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.mjs',
import.meta.url
).toString();
const loadingTask = pdfjs.getDocument({
data: pdfData,
useSystemFonts: true,
disableFontFace: false,
});
const pdfDocument = await loadingTask.promise;
if (!isMounted) return;
pdfDocumentRef.current = pdfDocument;
setTotalPages(pdfDocument.numPages);
setCurrentPage(1);
await renderPage(pdfDocument, 1);
} catch (err) {
console.error('Error loading PDF:', err);
if (isMounted) {
setError('Не удалось загрузить документ');
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
loadAndDisplayPdf();
return () => {
isMounted = false;
if (pdfDocumentRef.current) {
pdfDocumentRef.current.destroy();
}
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
};
}, [fileId, setPdfData]);
const renderPage = async (pdfDocument: any, pageNum: number) => {
if (!canvasRef.current) return;
try {
const page = await pdfDocument.getPage(pageNum);
const container = canvasRef.current.parentElement;
if (!container) return;
const containerWidth = container.clientWidth || 800;
// Если ширина контейнера 0, откладываем рендер
if (containerWidth === 0) {
console.log('Container width is 0, waiting...');
setTimeout(() => renderPage(pdfDocument, pageNum), 100);
return;
}
const originalViewport = page.getViewport({ scale: 1 });
const scale = containerWidth / originalViewport.width;
const viewport = page.getViewport({ scale: Math.min(scale, 1.5) });
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context!,
viewport: viewport,
};
await page.render(renderContext).promise;
} catch (err) {
console.log('Error rendering page:', err);
}
};
useEffect(() => {
if (!pdfDocumentRef.current || loading) return;
const checkContainer = () => {
const container = canvasRef.current?.parentElement;
if (container && container.clientWidth > 0) {
renderPage(pdfDocumentRef.current, currentPage);
} else {
setTimeout(checkContainer, 100);
}
};
checkContainer();
}, [pdfDocumentRef.current, loading]);
const goToPrevPage = async () => {
if (pdfDocumentRef.current && currentPage > 1) {
const newPage = currentPage - 1;
setCurrentPage(newPage);
await renderPage(pdfDocumentRef.current, newPage);
}
};
const goToNextPage = async () => {
if (pdfDocumentRef.current && currentPage < totalPages) {
const newPage = currentPage + 1;
setCurrentPage(newPage);
await renderPage(pdfDocumentRef.current, newPage);
}
};
const toggleFullscreen = () => {
if (!containerRef.current) return;
if (!document.fullscreenElement) {
containerRef.current.requestFullscreen();
setFullscreen(true);
} else {
document.exitFullscreen();
setFullscreen(false);
}
};
useEffect(() => {
const handleFullscreenChange = () => {
setFullscreen(!!document.fullscreenElement);
if (pdfDocumentRef.current && !document.fullscreenElement) {
setTimeout(() => renderPage(pdfDocumentRef.current, currentPage), 100);
}
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
}, [currentPage]);
useEffect(() => {
const handleResize = () => {
if (pdfDocumentRef.current && !loading) {
renderPage(pdfDocumentRef.current, currentPage);
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [currentPage, loading]);
if (loading) {
return (
<div className="prev-wrap">
<div className="pdf-loading">Загрузка документа...</div>
</div>
);
}
if (error) {
return (
<div className="prev-wrap">
<div className="pdf-error">{error}</div>
</div>
);
}
return (
<div ref={containerRef} className="pdf-container">
<div className="prev-wrap">
<canvas ref={canvasRef} className="pdf-canvas" />
</div>
{totalPages > 0 && (
<div className="document-controls">
<div className="document-controls__nav">
<button
className="document-controls__prev"
onClick={goToPrevPage}
disabled={currentPage === 1}
>
Пред.
</button>
<span className="document-controls__current-page">
{currentPage} / {totalPages}
</span>
<button
className="document-controls__next"
onClick={goToNextPage}
disabled={currentPage === totalPages}
>
След.
</button>
</div>
<button
className="document-controls__fullscreen"
onClick={toggleFullscreen}
>
{fullscreen ? 'Выйти' : 'На весь экран'}
</button>
</div>
)}
</div>
);
}