add document viewer
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user