248 lines
6.3 KiB
TypeScript
248 lines
6.3 KiB
TypeScript
'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>
|
||
);
|
||
} |