import { useState } from 'react'; import { Box, Button, Stack, Typography } from '@mui/material'; import { AssetRecordType, Box as TLBox, createShapeId } from '@tldraw/editor'; import { ExamPdfs, Pdf, PdfPage } from './types'; interface CCPdfPickerProps { onOpenPdfs: (pdfs: ExamPdfs) => void; } const pageSpacing = 32; export function CCPdfPicker({ onOpenPdfs }: CCPdfPickerProps) { const [isLoading, setIsLoading] = useState(false); const [selectedPdfs, setSelectedPdfs] = useState>({}); async function loadPdf(name: string, source: ArrayBuffer): Promise { const PdfJS = await import('pdfjs-dist'); PdfJS.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url ).toString(); const pdf = await PdfJS.getDocument(source.slice()).promise; const pages: PdfPage[] = []; const canvas = window.document.createElement('canvas'); const context = canvas.getContext('2d'); if (!context) throw new Error('Failed to create canvas context'); const visualScale = 1.5; const scale = window.devicePixelRatio; let top = 0; let widest = 0; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const viewport = page.getViewport({ scale: scale * visualScale }); canvas.width = viewport.width; canvas.height = viewport.height; const renderContext = { canvasContext: context, viewport, }; await page.render(renderContext).promise; const width = viewport.width / scale; const height = viewport.height / scale; pages.push({ src: canvas.toDataURL(), bounds: new TLBox(0, top, width, height), assetId: AssetRecordType.createId(), shapeId: createShapeId(), }); top += height + pageSpacing; widest = Math.max(widest, width); } canvas.width = 0; canvas.height = 0; for (const page of pages) { page.bounds.x = (widest - page.bounds.width) / 2; } return { name, pages, source, }; } const handleFileSelect = async (type: keyof ExamPdfs, file: File) => { setIsLoading(true); try { const pdf = await loadPdf(file.name, await file.arrayBuffer()); // Validate student responses page count if (type === 'studentResponses' && selectedPdfs.examPaper) { const examPageCount = selectedPdfs.examPaper.pages.length; if (pdf.pages.length % examPageCount !== 0) { alert(`Student responses PDF must have a number of pages that is a multiple of the exam paper's ${examPageCount} pages.\n\nStudent responses PDF has ${pdf.pages.length} pages, which is not a multiple of ${examPageCount}.`); return; } } setSelectedPdfs((prev) => ({ ...prev, [type]: pdf })); } catch (error) { console.error('Error loading PDF:', error); alert('Error loading PDF (mismatch between responses and exam paper). Please try again.'); } finally { setIsLoading(false); } }; const createFileInput = (type: keyof ExamPdfs) => { const input = window.document.createElement('input'); input.type = 'file'; input.accept = 'application/pdf'; input.addEventListener('change', async (e) => { const fileList = (e.target as HTMLInputElement).files; if (!fileList || fileList.length === 0) return; await handleFileSelect(type, fileList[0]); }); input.click(); }; const allPdfsSelected = () => { return ( selectedPdfs.examPaper && selectedPdfs.markScheme && selectedPdfs.studentResponses ); }; if (isLoading) { return ( Loading... ); } return ( Select PDF Files {allPdfsSelected() && ( )} ); }