226 lines
5.8 KiB
TypeScript
226 lines
5.8 KiB
TypeScript
|
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[] = []
|
||
|
|
||
|
for (let 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'
|
||
|
}} 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>
|
||
|
)
|
||
|
}
|