97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
const PDF_PREVIEW_WIDTH = 200;
|
|
|
|
/**
|
|
* Превью первой страницы PDF для карточки файла задания.
|
|
* Рендерит только на клиенте (react-pdf использует pdf.js).
|
|
*/
|
|
export function PdfFirstPagePreview({ url, alt }: { url: string; alt?: string }) {
|
|
const [PdfComponents, setPdfComponents] = useState<{
|
|
Document: React.ComponentType<any>;
|
|
Page: React.ComponentType<any>;
|
|
pdfjs: { GlobalWorkerOptions: { workerSrc: string }; version: string };
|
|
} | null>(null);
|
|
const [error, setError] = useState(false);
|
|
|
|
useEffect(() => {
|
|
import('react-pdf').then((mod) => {
|
|
const { Document, Page, pdfjs } = mod;
|
|
if (pdfjs?.GlobalWorkerOptions) {
|
|
pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version || '4.0.379'}/build/pdf.worker.min.mjs`;
|
|
}
|
|
setPdfComponents({ Document, Page, pdfjs });
|
|
}).catch(() => setError(true));
|
|
}, []);
|
|
|
|
const fileOptionsRef = useRef<{ url: string; withCredentials: true } | null>(null);
|
|
if (fileOptionsRef.current === null || fileOptionsRef.current.url !== url) {
|
|
fileOptionsRef.current = { url, withCredentials: true as const };
|
|
}
|
|
const fileOptions = fileOptionsRef.current;
|
|
|
|
const onLoadError = useCallback(() => setError(true), []);
|
|
|
|
const loadingElement = useMemo(
|
|
() => (
|
|
<div style={{ padding: 24 }}>
|
|
<span className="material-symbols-outlined" style={{ fontSize: 32, color: 'var(--md-sys-color-on-surface-variant)' }}>hourglass_empty</span>
|
|
</div>
|
|
),
|
|
[]
|
|
);
|
|
|
|
const wrapperStyle = useMemo<React.CSSProperties>(
|
|
() => ({
|
|
width: PDF_PREVIEW_WIDTH,
|
|
minHeight: 120,
|
|
background: '#f5f5f5',
|
|
borderRadius: 10,
|
|
overflow: 'hidden' as const,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}),
|
|
[]
|
|
);
|
|
|
|
if (error || !PdfComponents) {
|
|
return (
|
|
<div
|
|
style={{
|
|
width: PDF_PREVIEW_WIDTH,
|
|
height: Math.round(PDF_PREVIEW_WIDTH * 1.41),
|
|
background: 'var(--md-sys-color-surface-variant)',
|
|
borderRadius: 10,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<span className="material-symbols-outlined" style={{ fontSize: 40, color: 'var(--md-sys-color-primary)' }}>picture_as_pdf</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const { Document, Page } = PdfComponents;
|
|
|
|
return (
|
|
<div style={wrapperStyle}>
|
|
<Document
|
|
file={fileOptions}
|
|
onLoadError={onLoadError}
|
|
loading={loadingElement}
|
|
>
|
|
<Page
|
|
pageNumber={1}
|
|
width={PDF_PREVIEW_WIDTH}
|
|
renderTextLayer={false}
|
|
renderAnnotationLayer={false}
|
|
/>
|
|
</Document>
|
|
</div>
|
|
);
|
|
}
|