226 lines
5.8 KiB
TypeScript
Raw Normal View History

import { AccessTime, CalendarMonth, ExpandMore, Gesture, PictureAsPdf, Badge, Work } from '@mui/icons-material'
import { Box, Typography, Accordion, AccordionDetails, AccordionSummary, CircularProgress } from '@mui/material'
import styles from './style.module.scss'
import { useEffect, useState } from 'react'
import * as PDFJS from "pdfjs-dist";
PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs';
interface Props {
selectedFiles: any[]
}
interface PdfFile {
file: File,
pages: string[]
expanded?: boolean
}
interface DrawTool {
identifier: 'signature' | 'jobtitle' | 'fullname' | 'date' | 'datetime'
label: string
icon: JSX.Element,
defaultValue?: string
selected?: boolean
}
export const DrawPDFFields = (props: Props) => {
const { selectedFiles } = props
const [pdfFiles, setPdfFiles] = useState<PdfFile[]>([])
const [parsingPdf, setParsingPdf] = useState<boolean>(false)
const [showDrawToolBox, setShowDrawToolBox] = useState<boolean>(false)
const [selectedTool, setSelectedTool] = useState<DrawTool | null>()
const [toolbox] = useState<DrawTool[]>([
{
identifier: 'signature',
icon: <Gesture/>,
label: 'Signature'
},
{
identifier: 'fullname',
icon: <Badge/>,
label: 'Full Name'
},
{
identifier: 'jobtitle',
icon: <Work/>,
label: 'Job Title'
},
{
identifier: 'date',
icon: <CalendarMonth/>,
label: 'Date'
},
{
identifier: 'datetime',
icon: <AccessTime/>,
label: 'Datetime'
},
])
useEffect(() => {
if (selectedFiles) {
setParsingPdf(true)
parsePdfPages().finally(() => {
setParsingPdf(false)
})
}
}, [selectedFiles])
const parsePdfPages = async () => {
const pdfFiles: PdfFile[] = []
2024-06-29 00:44:06 +02:00
for (const file of selectedFiles) {
if (file.type.toLowerCase().includes('pdf')) {
const data = await readPdf(file)
const pages = await pdfToImages(data)
pdfFiles.push({
file: file,
pages: pages
})
}
}
setPdfFiles(pdfFiles)
}
const getPdfPages = (pdfFile: PdfFile) => {
return (
<Box sx={{
width: '100%'
}}>
{pdfFile.pages.map((page: string) => {
return (
<div style={{
border: '1px solid #c4c4c4',
marginBottom: '10px'
2024-06-29 00:44:06 +02:00
}} className={`${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`}>
<img draggable="false" style={{ width: '100%' }} src={page}/>
</div>
)
})}
</Box>
)
}
/**
* Converts pdf to the images
* @param data pdf file bytes
*/
const pdfToImages = async (data: any): Promise<string[]> => {
const images: string[] = [];
const pdf = await PDFJS.getDocument(data).promise;
const canvas = document.createElement("canvas");
for (let i = 0; i < pdf.numPages; i++) {
const page = await pdf.getPage(i + 1);
const viewport = page.getViewport({ scale: 3 });
const context = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({ canvasContext: context!, viewport: viewport }).promise;
images.push(canvas.toDataURL());
}
return Promise.resolve(images)
}
const readPdf = (file: File) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e: any) => {
const data = e.target.result
resolve(data)
};
reader.onerror = (err) => {
console.error('err', err)
reject(err)
};
reader.readAsDataURL(file);
})
}
const hasExpandedPdf = () => {
return !!pdfFiles.filter(pdfFile => !!pdfFile.expanded).length
}
const handleAccordionExpandChange = (event: React.SyntheticEvent<Element, Event>, expanded: boolean, pdfFile: PdfFile) => {
pdfFile.expanded = expanded
setPdfFiles(pdfFiles)
setShowDrawToolBox(hasExpandedPdf())
}
const handleToolSelect = (drawTool: DrawTool) => {
// If clicked on the same tool, unselect
if (drawTool.identifier === selectedTool?.identifier) {
setSelectedTool(null)
return
}
setSelectedTool(drawTool)
}
if (parsingPdf) {
return (
<Box sx={{ width: '100%', textAlign: 'center' }}>
<CircularProgress/>
</Box>
)
}
if (!pdfFiles.length) {
return ''
}
return (
<Box>
<Box sx={{ mt: 1 }}>
<Typography sx={{ mb: 1 }}>Draw fields on the PDFs:</Typography>
{pdfFiles.map((pdfFile) => {
return (
<Accordion expanded={pdfFile.expanded} onChange={(event, expanded) => {handleAccordionExpandChange(event, expanded, pdfFile)}}>
<AccordionSummary
expandIcon={<ExpandMore />}
aria-controls="panel1-content"
id="panel1-header"
>
<PictureAsPdf sx={{ mr: 1 }}/>
{pdfFile.file.name}
</AccordionSummary>
<AccordionDetails>
{getPdfPages(pdfFile)}
</AccordionDetails>
</Accordion>
)
})}
</Box>
{showDrawToolBox && (
<Box className={styles.drawToolBoxContainer}>
<Box className={styles.drawToolBox}>
{toolbox.map((drawTool: DrawTool) => {
return (
<Box onClick={() => {handleToolSelect(drawTool)}} className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`}>
{ drawTool.icon }
{ drawTool.label }
</Box>
)
})}
</Box>
</Box>
)}
</Box>
)
}