feat(merge): Migrate merge operations to Web Workers and reorganize tool categories
- Create merge.worker.js and alternate-merge.worker.js for offloading PDF merge operations to background threads - Migrate merge and alternate-merge logic to use Web Worker architecture for improved performance - Move flatten tool from singlePdfLoadTools to multiFileTools configuration - Update merge and alternate-merge tool subtitles to indicate bookmark preservation - Refactor alternate-merge.ts to use Web Worker instead of pdf-lib for interleaving operations - Refactor merge.ts to use Web Worker for standard merge operations - Update fileHandler.ts to recognize flatten as a multi-file tool requiring process button handling - Simplify form-creator.ts and flatten.ts implementations with Web Worker integration - Update UI state management to support Web Worker-based PDF processing - Improve performance by moving computationally intensive PDF operations off the main thread
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { PDFDocument, StandardFonts, rgb, TextAlignment, PDFName, PDFString, PageSizes, PDFBool, PDFDict, PDFArray } from 'pdf-lib'
|
||||
import { PDFDocument, StandardFonts, rgb, TextAlignment, PDFName, PDFString, PageSizes, PDFBool, PDFDict, PDFArray, PDFRadioGroup } from 'pdf-lib'
|
||||
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'
|
||||
import { downloadFile, hexToRgb, getPDFDocument } from '../utils/helpers.js'
|
||||
import { createIcons, icons } from 'lucide'
|
||||
import * as pdfjsLib from 'pdfjs-dist'
|
||||
import 'pdfjs-dist/web/pdf_viewer.css'
|
||||
|
||||
// Initialize PDF.js worker
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString()
|
||||
@@ -49,18 +50,21 @@ interface PageData {
|
||||
let fields: FormField[] = []
|
||||
let selectedField: FormField | null = null
|
||||
let fieldCounter = 0
|
||||
let existingFieldNames: Set<string> = new Set()
|
||||
let existingRadioGroups: Set<string> = new Set()
|
||||
let draggedElement: HTMLElement | null = null
|
||||
let offsetX = 0
|
||||
let offsetY = 0
|
||||
|
||||
// Multi-page state
|
||||
let pages: PageData[] = []
|
||||
let currentPageIndex = 0
|
||||
let uploadedPdfDoc: PDFDocument | null = null
|
||||
let uploadedPdfjsDoc: any = null
|
||||
let pageSize: { width: number; height: number } = { width: 612, height: 792 }
|
||||
let currentScale = 1.333
|
||||
let pdfViewerOffset = { x: 0, y: 0 }
|
||||
let pdfViewerScale = 1.333
|
||||
|
||||
// Resize state
|
||||
let resizing = false
|
||||
let resizeField: FormField | null = null
|
||||
let resizePos: string | null = null
|
||||
@@ -419,8 +423,8 @@ function renderField(field: FormField): void {
|
||||
let newX = touch.clientX - rect.left - offsetX
|
||||
let newY = touch.clientY - rect.top - offsetY
|
||||
|
||||
newX = Math.max(0, Math.min(newX, 816 - fieldWrapper.offsetWidth))
|
||||
newY = Math.max(0, Math.min(newY, 1056 - fieldWrapper.offsetHeight))
|
||||
newX = Math.max(0, Math.min(newX, rect.width - fieldWrapper.offsetWidth))
|
||||
newY = Math.max(0, Math.min(newY, rect.height - fieldWrapper.offsetHeight))
|
||||
|
||||
fieldWrapper.style.left = newX + 'px'
|
||||
fieldWrapper.style.top = newY + 'px'
|
||||
@@ -496,8 +500,8 @@ document.addEventListener('mousemove', (e) => {
|
||||
let newX = e.clientX - rect.left - offsetX
|
||||
let newY = e.clientY - rect.top - offsetY
|
||||
|
||||
newX = Math.max(0, Math.min(newX, 816 - draggedElement.offsetWidth))
|
||||
newY = Math.max(0, Math.min(newY, 1056 - draggedElement.offsetHeight))
|
||||
newX = Math.max(0, Math.min(newX, rect.width - draggedElement.offsetWidth))
|
||||
newY = Math.max(0, Math.min(newY, rect.height - draggedElement.offsetHeight))
|
||||
|
||||
draggedElement.style.left = newX + 'px'
|
||||
draggedElement.style.top = newY + 'px'
|
||||
@@ -834,9 +838,21 @@ function showProperties(field: FormField): void {
|
||||
propertiesPanel.innerHTML = `
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-300 mb-1">Field Name</label>
|
||||
<label class="block text-xs font-semibold text-gray-300 mb-1">Field Name ${field.type === 'radio' ? '(Group Name)' : ''}</label>
|
||||
<input type="text" id="propName" value="${field.name}" class="w-full bg-gray-600 border border-gray-500 text-white rounded px-2 py-1 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<div id="nameError" class="hidden text-red-400 text-xs mt-1"></div>
|
||||
</div>
|
||||
${field.type === 'radio' && (existingRadioGroups.size > 0 || fields.some(f => f.type === 'radio' && f.id !== field.id)) ? `
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-300 mb-1">Existing Radio Groups</label>
|
||||
<select id="existingGroups" class="w-full bg-gray-600 border border-gray-500 text-white rounded px-2 py-1 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<option value="">-- Select existing group --</option>
|
||||
${Array.from(existingRadioGroups).map(name => `<option value="${name}">${name}</option>`).join('')}
|
||||
${Array.from(new Set(fields.filter(f => f.type === 'radio' && f.id !== field.id).map(f => f.name))).map(name => !existingRadioGroups.has(name) ? `<option value="${name}">${name}</option>` : '').join('')}
|
||||
</select>
|
||||
<p class="text-xs text-gray-400 mt-1">Select to add this button to an existing group</p>
|
||||
</div>
|
||||
` : ''}
|
||||
${specificProps}
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-300 mb-1">Tooltip / Help Text</label>
|
||||
@@ -858,25 +874,50 @@ function showProperties(field: FormField): void {
|
||||
|
||||
// Common listeners
|
||||
const propName = document.getElementById('propName') as HTMLInputElement
|
||||
const nameError = document.getElementById('nameError') as HTMLDivElement
|
||||
const propTooltip = document.getElementById('propTooltip') as HTMLInputElement
|
||||
const propRequired = document.getElementById('propRequired') as HTMLInputElement
|
||||
const propReadOnly = document.getElementById('propReadOnly') as HTMLInputElement
|
||||
const deleteBtn = document.getElementById('deleteBtn') as HTMLButtonElement
|
||||
|
||||
const validateName = (newName: string): boolean => {
|
||||
if (!newName) {
|
||||
nameError.textContent = 'Field name cannot be empty'
|
||||
nameError.classList.remove('hidden')
|
||||
propName.classList.add('border-red-500')
|
||||
return false
|
||||
}
|
||||
|
||||
if (field.type === 'radio') {
|
||||
nameError.classList.add('hidden')
|
||||
propName.classList.remove('border-red-500')
|
||||
return true
|
||||
}
|
||||
|
||||
const isDuplicateInFields = fields.some(f => f.id !== field.id && f.name === newName)
|
||||
const isDuplicateInPdf = existingFieldNames.has(newName)
|
||||
|
||||
if (isDuplicateInFields || isDuplicateInPdf) {
|
||||
nameError.textContent = `Field name "${newName}" already exists in this ${isDuplicateInPdf ? 'PDF' : 'form'}. Please try using a unique name.`
|
||||
nameError.classList.remove('hidden')
|
||||
propName.classList.add('border-red-500')
|
||||
return false
|
||||
}
|
||||
|
||||
nameError.classList.add('hidden')
|
||||
propName.classList.remove('border-red-500')
|
||||
return true
|
||||
}
|
||||
|
||||
propName.addEventListener('input', (e) => {
|
||||
const newName = (e.target as HTMLInputElement).value.trim()
|
||||
validateName(newName)
|
||||
})
|
||||
|
||||
propName.addEventListener('change', (e) => {
|
||||
const newName = (e.target as HTMLInputElement).value.trim()
|
||||
|
||||
if (!newName) {
|
||||
showModal('Invalid Name', 'Field name cannot be empty.', 'warning');
|
||||
(e.target as HTMLInputElement).value = field.name
|
||||
return
|
||||
}
|
||||
|
||||
// Check for duplicate name
|
||||
const isDuplicate = fields.some(f => f.id !== field.id && f.name === newName)
|
||||
|
||||
if (isDuplicate) {
|
||||
showModal('Duplicate Name', `A field with the name "${newName}" already exists. Please choose a unique name.`, 'error');
|
||||
if (!validateName(newName)) {
|
||||
(e.target as HTMLInputElement).value = field.name
|
||||
return
|
||||
}
|
||||
@@ -893,6 +934,27 @@ function showProperties(field: FormField): void {
|
||||
field.tooltip = (e.target as HTMLInputElement).value
|
||||
})
|
||||
|
||||
if (field.type === 'radio') {
|
||||
const existingGroupsSelect = document.getElementById('existingGroups') as HTMLSelectElement
|
||||
if (existingGroupsSelect) {
|
||||
existingGroupsSelect.addEventListener('change', (e) => {
|
||||
const selectedGroup = (e.target as HTMLSelectElement).value
|
||||
if (selectedGroup) {
|
||||
propName.value = selectedGroup
|
||||
field.name = selectedGroup
|
||||
validateName(selectedGroup)
|
||||
|
||||
// Update field label
|
||||
const fieldWrapper = document.getElementById(field.id)
|
||||
if (fieldWrapper) {
|
||||
const label = fieldWrapper.querySelector('.field-label') as HTMLElement
|
||||
if (label) label.textContent = field.name
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
propRequired.addEventListener('change', (e) => {
|
||||
field.required = (e.target as HTMLInputElement).checked
|
||||
})
|
||||
@@ -1255,18 +1317,41 @@ downloadBtn.addEventListener('click', async () => {
|
||||
// Check for duplicate field names before generating PDF
|
||||
const nameCount = new Map<string, number>()
|
||||
const duplicates: string[] = []
|
||||
const conflictsWithPdf: string[] = []
|
||||
|
||||
fields.forEach(field => {
|
||||
const count = nameCount.get(field.name) || 0
|
||||
nameCount.set(field.name, count + 1)
|
||||
|
||||
if (existingFieldNames.has(field.name)) {
|
||||
if (field.type === 'radio' && existingRadioGroups.has(field.name)) {
|
||||
} else {
|
||||
conflictsWithPdf.push(field.name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
nameCount.forEach((count, name) => {
|
||||
if (count > 1) {
|
||||
duplicates.push(name)
|
||||
const fieldsWithName = fields.filter(f => f.name === name)
|
||||
const allRadio = fieldsWithName.every(f => f.type === 'radio')
|
||||
|
||||
if (!allRadio) {
|
||||
duplicates.push(name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (conflictsWithPdf.length > 0) {
|
||||
const conflictList = [...new Set(conflictsWithPdf)].map(name => `"${name}"`).join(', ')
|
||||
showModal(
|
||||
'Field Name Conflict',
|
||||
`The following field names already exist in the uploaded PDF: ${conflictList}. Please rename these fields before downloading.`,
|
||||
'error'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
const duplicateList = duplicates.map(name => `"${name}"`).join(', ')
|
||||
showModal(
|
||||
@@ -1318,14 +1403,24 @@ downloadBtn.addEventListener('click', async () => {
|
||||
const pdfPage = pdfDoc.getPage(field.pageIndex)
|
||||
const { height: pageHeight } = pdfPage.getSize()
|
||||
|
||||
const scaleX = 1 / currentScale
|
||||
const scaleY = 1 / currentScale
|
||||
const scaleX = 1 / pdfViewerScale
|
||||
const scaleY = 1 / pdfViewerScale
|
||||
|
||||
const x = field.x * scaleX
|
||||
const y = pageHeight - field.y * scaleY - field.height * scaleY // PDF coordinates from bottom
|
||||
const adjustedX = field.x - pdfViewerOffset.x
|
||||
const adjustedY = field.y - pdfViewerOffset.y
|
||||
|
||||
const x = adjustedX * scaleX
|
||||
const y = pageHeight - (adjustedY * scaleY) - (field.height * scaleY)
|
||||
const width = field.width * scaleX
|
||||
const height = field.height * scaleY
|
||||
|
||||
console.log(`Field "${field.name}":`, {
|
||||
screenPos: { x: field.x, y: field.y },
|
||||
adjustedPos: { x: adjustedX, y: adjustedY },
|
||||
pdfPos: { x, y, width, height },
|
||||
metrics: { offset: pdfViewerOffset, scale: pdfViewerScale }
|
||||
})
|
||||
|
||||
if (field.type === 'text') {
|
||||
const textField = form.createTextField(field.name)
|
||||
const rgbColor = hexToRgb(field.textColor)
|
||||
@@ -1398,14 +1493,23 @@ downloadBtn.addEventListener('click', async () => {
|
||||
}
|
||||
|
||||
} else if (field.type === 'radio') {
|
||||
const groupName = field.groupName || 'RadioGroup1'
|
||||
const groupName = field.name
|
||||
let radioGroup
|
||||
|
||||
if (radioGroups.has(groupName)) {
|
||||
radioGroup = radioGroups.get(groupName)
|
||||
} else {
|
||||
radioGroup = form.createRadioGroup(groupName)
|
||||
radioGroups.set(groupName, radioGroup)
|
||||
const existingField = form.getFieldMaybe(groupName)
|
||||
|
||||
if (existingField) {
|
||||
radioGroup = existingField
|
||||
radioGroups.set(groupName, radioGroup)
|
||||
console.log(`Using existing radio group from PDF: ${groupName}`)
|
||||
} else {
|
||||
radioGroup = form.createRadioGroup(groupName)
|
||||
radioGroups.set(groupName, radioGroup)
|
||||
console.log(`Created new radio group: ${groupName}`)
|
||||
}
|
||||
}
|
||||
|
||||
radioGroup.addOptionToPage(field.exportValue || 'Yes', pdfPage as any, {
|
||||
@@ -1585,7 +1689,7 @@ downloadBtn.addEventListener('click', async () => {
|
||||
|
||||
// Add Date Format and Keystroke Actions to the FIELD (not widget)
|
||||
const dateFormat = field.dateFormat || 'mm/dd/yyyy'
|
||||
|
||||
|
||||
const formatAction = pdfDoc.context.obj({
|
||||
Type: 'Action',
|
||||
S: 'JavaScript',
|
||||
@@ -1707,6 +1811,9 @@ downloadBtn.addEventListener('click', async () => {
|
||||
const pdfBytes = await pdfDoc.save()
|
||||
const blob = new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' })
|
||||
downloadFile(blob, 'fillable-form.pdf')
|
||||
showModal('Success', 'Your PDF has been downloaded successfully.', 'info', () => {
|
||||
resetToInitial()
|
||||
}, 'Okay')
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF:', error)
|
||||
const errorMessage = (error as Error).message
|
||||
@@ -1716,7 +1823,12 @@ downloadBtn.addEventListener('click', async () => {
|
||||
// Extract the field name from the error message
|
||||
const match = errorMessage.match(/A field already exists with the specified name: "(.+?)"/)
|
||||
const fieldName = match ? match[1] : 'unknown'
|
||||
showModal('Duplicate Field Name', `A field named "${fieldName}" already exists. Please rename this field to use a unique name before downloading.`, 'error')
|
||||
|
||||
if (existingRadioGroups.has(fieldName)) {
|
||||
console.log(`Adding to existing radio group: ${fieldName}`)
|
||||
} else {
|
||||
showModal('Duplicate Field Name', `A field named "${fieldName}" already exists. Please rename this field to use a unique name before downloading.`, 'error')
|
||||
}
|
||||
} else {
|
||||
showModal('Error', 'Error generating PDF: ' + errorMessage, 'error')
|
||||
}
|
||||
@@ -1805,7 +1917,7 @@ function switchToPage(pageIndex: number): void {
|
||||
}
|
||||
|
||||
// Render the canvas for the current page
|
||||
function renderCanvas(): void {
|
||||
async function renderCanvas(): Promise<void> {
|
||||
const currentPage = pages[currentPageIndex]
|
||||
if (!currentPage) return
|
||||
|
||||
@@ -1814,6 +1926,7 @@ function renderCanvas(): void {
|
||||
|
||||
currentScale = scale
|
||||
|
||||
// Use actual PDF page dimensions (not scaled)
|
||||
const canvasWidth = currentPage.width * scale
|
||||
const canvasHeight = currentPage.height * scale
|
||||
|
||||
@@ -1822,20 +1935,135 @@ function renderCanvas(): void {
|
||||
|
||||
canvas.innerHTML = ''
|
||||
|
||||
if (currentPage.pdfPageData) {
|
||||
const img = document.createElement('img')
|
||||
img.src = currentPage.pdfPageData as string
|
||||
img.style.position = 'absolute'
|
||||
img.style.top = '0'
|
||||
img.style.left = '0'
|
||||
img.style.width = '100%'
|
||||
img.style.height = '100%'
|
||||
img.style.pointerEvents = 'none'
|
||||
img.style.opacity = '0.8' // Slightly transparent so fields are visible
|
||||
canvas.appendChild(img)
|
||||
if (uploadedPdfDoc) {
|
||||
try {
|
||||
const arrayBuffer = await uploadedPdfDoc.save()
|
||||
const blob = new Blob([arrayBuffer.buffer as ArrayBuffer], { type: 'application/pdf' })
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.src = `/pdfjs-viewer/viewer.html?file=${encodeURIComponent(blobUrl)}#page=${currentPageIndex + 1}&toolbar=0`
|
||||
iframe.style.width = '100%'
|
||||
iframe.style.height = `${canvasHeight}px`
|
||||
iframe.style.border = 'none'
|
||||
iframe.style.position = 'absolute'
|
||||
iframe.style.top = '0'
|
||||
iframe.style.left = '0'
|
||||
iframe.style.pointerEvents = 'none'
|
||||
iframe.style.opacity = '0.8'
|
||||
|
||||
iframe.onload = () => {
|
||||
try {
|
||||
const viewerWindow = iframe.contentWindow as any
|
||||
if (viewerWindow && viewerWindow.PDFViewerApplication) {
|
||||
const app = viewerWindow.PDFViewerApplication
|
||||
|
||||
const style = viewerWindow.document.createElement('style')
|
||||
style.textContent = `
|
||||
* {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background-color: transparent !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#toolbarContainer {
|
||||
display: none !important;
|
||||
}
|
||||
#mainContainer {
|
||||
top: 0 !important;
|
||||
position: absolute !important;
|
||||
left: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
#outerContainer {
|
||||
background-color: transparent !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
#viewerContainer {
|
||||
top: 0 !important;
|
||||
background-color: transparent !important;
|
||||
overflow: hidden !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.toolbar {
|
||||
display: none !important;
|
||||
}
|
||||
.pdfViewer {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.page {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
`
|
||||
viewerWindow.document.head.appendChild(style)
|
||||
|
||||
const checkRender = setInterval(() => {
|
||||
if (app.pdfViewer && app.pdfViewer.pagesCount > 0) {
|
||||
clearInterval(checkRender)
|
||||
|
||||
const pageContainer = viewerWindow.document.querySelector('.page')
|
||||
if (pageContainer) {
|
||||
const initialRect = pageContainer.getBoundingClientRect()
|
||||
|
||||
const offsetX = -initialRect.left
|
||||
const offsetY = -initialRect.top
|
||||
pageContainer.style.transform = `translate(${offsetX}px, ${offsetY}px)`
|
||||
|
||||
setTimeout(() => {
|
||||
const rect = pageContainer.getBoundingClientRect()
|
||||
const style = viewerWindow.getComputedStyle(pageContainer)
|
||||
|
||||
const borderLeft = parseFloat(style.borderLeftWidth) || 0
|
||||
const borderTop = parseFloat(style.borderTopWidth) || 0
|
||||
const borderRight = parseFloat(style.borderRightWidth) || 0
|
||||
|
||||
pdfViewerOffset = {
|
||||
x: rect.left + borderLeft,
|
||||
y: rect.top + borderTop
|
||||
}
|
||||
|
||||
const contentWidth = rect.width - borderLeft - borderRight
|
||||
pdfViewerScale = contentWidth / currentPage.width
|
||||
|
||||
console.log('📏 Calibrated Metrics (force positioned):', {
|
||||
initialPosition: { left: initialRect.left, top: initialRect.top },
|
||||
appliedTransform: { x: offsetX, y: offsetY },
|
||||
finalRect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },
|
||||
computedBorders: { left: borderLeft, top: borderTop, right: borderRight },
|
||||
finalOffset: pdfViewerOffset,
|
||||
finalScale: pdfViewerScale,
|
||||
pdfDimensions: { width: currentPage.width, height: currentPage.height }
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error accessing iframe content:', e)
|
||||
}
|
||||
}
|
||||
|
||||
canvas.appendChild(iframe)
|
||||
|
||||
console.log('Canvas dimensions:', { width: canvasWidth, height: canvasHeight, scale: currentScale })
|
||||
console.log('PDF page dimensions:', { width: currentPage.width, height: currentPage.height })
|
||||
} catch (error) {
|
||||
console.error('Error rendering PDF:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Render fields for current page
|
||||
fields.filter(f => f.pageIndex === currentPageIndex).forEach(field => {
|
||||
renderField(field)
|
||||
})
|
||||
@@ -1904,25 +2132,41 @@ async function handlePdfUpload(file: File) {
|
||||
uploadedPdfDoc = await PDFDocument.load(arrayBuffer)
|
||||
|
||||
// Check for existing fields and update counter
|
||||
existingFieldNames.clear()
|
||||
try {
|
||||
const form = uploadedPdfDoc.getForm()
|
||||
const fields = form.getFields()
|
||||
fields.forEach(field => {
|
||||
const pdfFields = form.getFields()
|
||||
|
||||
// console.log('📋 Found', pdfFields.length, 'existing fields in uploaded PDF')
|
||||
|
||||
pdfFields.forEach(field => {
|
||||
const name = field.getName()
|
||||
// Check if name matches pattern Type_Number
|
||||
existingFieldNames.add(name) // Track all existing field names
|
||||
|
||||
if (field instanceof PDFRadioGroup) {
|
||||
existingRadioGroups.add(name)
|
||||
}
|
||||
|
||||
// console.log(' Field:', name, '| Type:', field.constructor.name)
|
||||
|
||||
const match = name.match(/([a-zA-Z]+)_(\d+)/)
|
||||
if (match) {
|
||||
const num = parseInt(match[2])
|
||||
if (!isNaN(num) && num >= fieldCounter) {
|
||||
if (!isNaN(num) && num > fieldCounter) {
|
||||
fieldCounter = num
|
||||
console.log(' → Updated field counter to:', fieldCounter)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// TODO@ALAM: DEBUGGER
|
||||
// console.log('Field counter after upload:', fieldCounter)
|
||||
// console.log('Existing field names:', Array.from(existingFieldNames))
|
||||
} catch (e) {
|
||||
// No form or error getting fields, ignore
|
||||
console.log('No form fields found or error reading fields:', e)
|
||||
}
|
||||
|
||||
const pdfjsDoc = await getPDFDocument({ data: arrayBuffer }).promise
|
||||
uploadedPdfjsDoc = await getPDFDocument({ data: arrayBuffer }).promise
|
||||
|
||||
const pageCount = uploadedPdfDoc.getPageCount()
|
||||
pages = []
|
||||
@@ -1931,27 +2175,11 @@ async function handlePdfUpload(file: File) {
|
||||
const page = uploadedPdfDoc.getPage(i)
|
||||
const { width, height } = page.getSize()
|
||||
|
||||
const pdfjsPage = await pdfjsDoc.getPage(i + 1)
|
||||
const viewport = pdfjsPage.getViewport({ scale: 1.333 })
|
||||
|
||||
const tempCanvas = document.createElement('canvas')
|
||||
tempCanvas.width = viewport.width
|
||||
tempCanvas.height = viewport.height
|
||||
|
||||
const context = tempCanvas.getContext('2d')!
|
||||
await pdfjsPage.render({
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
canvas: tempCanvas,
|
||||
}).promise
|
||||
|
||||
const dataUrl = tempCanvas.toDataURL('image/png')
|
||||
|
||||
pages.push({
|
||||
index: i,
|
||||
width,
|
||||
height,
|
||||
pdfPageData: dataUrl as any
|
||||
pdfPageData: undefined
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2005,17 +2233,26 @@ const errorModalTitle = document.getElementById('errorModalTitle')
|
||||
const errorModalMessage = document.getElementById('errorModalMessage')
|
||||
const errorModalClose = document.getElementById('errorModalClose')
|
||||
|
||||
function showModal(title: string, message: string, type: 'error' | 'warning' | 'info' = 'error') {
|
||||
if (!errorModal || !errorModalTitle || !errorModalMessage) return
|
||||
let modalCloseCallback: (() => void) | null = null
|
||||
|
||||
function showModal(title: string, message: string, type: 'error' | 'warning' | 'info' = 'error', onClose?: () => void, buttonText: string = 'Close') {
|
||||
if (!errorModal || !errorModalTitle || !errorModalMessage || !errorModalClose) return
|
||||
|
||||
errorModalTitle.textContent = title
|
||||
errorModalMessage.textContent = message
|
||||
errorModalClose.textContent = buttonText
|
||||
|
||||
modalCloseCallback = onClose || null
|
||||
errorModal.classList.remove('hidden')
|
||||
}
|
||||
|
||||
if (errorModalClose) {
|
||||
errorModalClose.addEventListener('click', () => {
|
||||
errorModal?.classList.add('hidden')
|
||||
if (modalCloseCallback) {
|
||||
modalCloseCallback()
|
||||
modalCloseCallback = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2024,6 +2261,10 @@ if (errorModal) {
|
||||
errorModal.addEventListener('click', (e) => {
|
||||
if (e.target === errorModal) {
|
||||
errorModal.classList.add('hidden')
|
||||
if (modalCloseCallback) {
|
||||
modalCloseCallback()
|
||||
modalCloseCallback = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user