import { state } from '../state.js'; import { showLoader, hideLoader, showAlert, renderPageThumbnails, renderFileDisplay, switchView, } from '../ui.js'; import { formatIsoDate, readFileAsArrayBuffer, getPDFDocument, } from '../utils/helpers.js'; import { setupCanvasEditor } from '../canvasEditor.js'; import { toolLogic } from '../logic/index.js'; import { renderDuplicateOrganizeThumbnails } from '../logic/duplicate-organize.js'; import { icons, createIcons } from 'lucide'; import Sortable from 'sortablejs'; import { makeUniqueFileKey } from '../utils/deduplicate-filename.js'; import { promptAndDecryptFile, handleEncryptedFiles, } from '../utils/password-prompt.js'; import { multiFileTools, simpleTools, singlePdfLoadTools, } from '../config/pdf-tools.js'; import * as pdfjsLib from 'pdfjs-dist'; import { loadPdfDocument } from '../utils/load-pdf-document.js'; pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url ).toString(); export { getRotationState, updateRotationState, resetRotationState, initializeRotationState, } from '../utils/rotation-state.js'; const rotationState: number[] = []; let imageSortableInstance: Sortable | null = null; const activeImageUrls = new Map(); async function handleSinglePdfUpload(toolId: string, file: File) { showLoader('Loading PDF...'); try { if (toolId === 'form-filler') { hideLoader(); const optionsDiv = document.getElementById('form-filler-options'); if (optionsDiv) optionsDiv.classList.remove('hidden'); const processBtn = document.getElementById('process-btn'); if (processBtn) { const logic = toolLogic[toolId]; if (logic && typeof logic === 'object' && logic.process) { processBtn.onclick = logic.process; } } const logic = toolLogic[toolId]; if (logic && typeof logic === 'object' && logic.setup) { await logic.setup(); } return; } const pdfBytes = await readFileAsArrayBuffer(file); state.pdfDoc = await loadPdfDocument(pdfBytes as ArrayBuffer); hideLoader(); if ( state.pdfDoc.isEncrypted && toolId !== 'decrypt' && toolId !== 'change-permissions' && toolId !== 'remove-restrictions' ) { const decryptedFile = await promptAndDecryptFile(file); if (!decryptedFile) { switchView('grid'); return; } const decryptedBytes = await readFileAsArrayBuffer(decryptedFile); state.pdfDoc = await loadPdfDocument(decryptedBytes as ArrayBuffer); state.files = [decryptedFile]; } const optionsDiv = document.querySelector( '[id$="-options"], [id$="-preview"], [id$="-organizer"], [id$="-rotator"], [id$="-editor"]' ); if (optionsDiv) optionsDiv.classList.remove('hidden'); const processBtn = document.getElementById('process-btn'); if (processBtn) { (processBtn as HTMLButtonElement).disabled = false; processBtn.classList.remove('hidden'); const logic = toolLogic[toolId]; if (logic) { const func = typeof logic === 'object' && typeof logic.process === 'function' ? logic.process : typeof logic === 'function' ? logic : null; if (func) processBtn.onclick = func; } } if ( [ 'split', 'delete-pages', 'add-blank-page', 'extract-pages', 'add-header-footer', ].includes(toolId) ) { document.getElementById('total-pages').textContent = state.pdfDoc .getPageCount() .toString(); } if ( toolId === 'organize' || toolId === 'rotate' || toolId === 'delete-pages' ) { await renderPageThumbnails(toolId, state.pdfDoc); if (toolId === 'rotate') { rotationState.length = 0; for (let i = 0; i < state.pdfDoc.getPageCount(); i++) { rotationState.push(0); } const rotateAllControls = document.getElementById( 'rotate-all-controls' ); const rotateAllLeftBtn = document.getElementById('rotate-all-left-btn'); const rotateAllRightBtn = document.getElementById( 'rotate-all-right-btn' ); const rotateAllCustomBtn = document.getElementById( 'rotate-all-custom-btn' ); const rotateAllCustomInput = document.getElementById( 'custom-rotate-all-input' ) as HTMLInputElement; const rotateAllDecrementBtn = document.getElementById( 'rotate-all-decrement-btn' ); const rotateAllIncrementBtn = document.getElementById( 'rotate-all-increment-btn' ); rotateAllControls.classList.remove('hidden'); createIcons({ icons }); const rotateAll = (angle: number) => { for (let i = 0; i < rotationState.length; i++) { rotationState[i] = rotationState[i] + angle; } document.querySelectorAll('.page-rotator-item').forEach((item) => { const pageIndex = parseInt( (item as HTMLElement).dataset.pageIndex || '0' ); const newRotation = rotationState[pageIndex]; (item as HTMLElement).dataset.rotation = newRotation.toString(); const thumbnail = item.querySelector('canvas, img'); if (thumbnail) { (thumbnail as HTMLElement).style.transform = `rotate(${newRotation}deg)`; } const input = item.querySelector('input'); if (input) { input.value = newRotation.toString(); } }); }; rotateAllLeftBtn.onclick = () => rotateAll(-90); rotateAllRightBtn.onclick = () => rotateAll(90); if (rotateAllCustomBtn && rotateAllCustomInput) { rotateAllCustomBtn.onclick = () => { const angle = parseInt(rotateAllCustomInput.value); if (!isNaN(angle) && angle !== 0) { rotateAll(angle); } }; if (rotateAllDecrementBtn) { rotateAllDecrementBtn.onclick = () => { const current = parseInt(rotateAllCustomInput.value) || 0; rotateAllCustomInput.value = (current - 1).toString(); }; } if (rotateAllIncrementBtn) { rotateAllIncrementBtn.onclick = () => { const current = parseInt(rotateAllCustomInput.value) || 0; rotateAllCustomInput.value = (current + 1).toString(); }; } } } } if (toolId === 'duplicate-organize') { await renderDuplicateOrganizeThumbnails(); } if (['crop', 'redact'].includes(toolId)) { await setupCanvasEditor(toolId); } if (toolId === 'view-metadata') { const resultsDiv = document.getElementById('metadata-results'); showLoader('Analyzing full PDF metadata...'); try { const pdfBytes = await readFileAsArrayBuffer(state.files[0]); const pdfjsDoc = await getPDFDocument({ data: pdfBytes as ArrayBuffer, }).promise; const [metadataResult, fieldObjects] = await Promise.all([ pdfjsDoc.getMetadata(), pdfjsDoc.getFieldObjects(), ]); const { info, metadata } = metadataResult; const rawXmpString = metadata ? metadata.getRaw() : null; resultsDiv.textContent = ''; // Clear safely const createSection = (title: string) => { const wrapper = document.createElement('div'); wrapper.className = 'mb-4'; const h3 = document.createElement('h3'); h3.className = 'text-lg font-semibold text-white mb-2'; h3.textContent = title; const ul = document.createElement('ul'); ul.className = 'space-y-3 text-sm bg-gray-900 p-4 rounded-lg border border-gray-700'; wrapper.append(h3, ul); return { wrapper, ul }; }; const createListItem = (key: string, value: string) => { const li = document.createElement('li'); li.className = 'flex flex-col sm:flex-row'; const strong = document.createElement('strong'); strong.className = 'w-40 flex-shrink-0 text-gray-400'; strong.textContent = key; const div = document.createElement('div'); div.className = 'flex-grow text-white break-all'; div.textContent = value; li.append(strong, div); return li; }; const parsePdfDate = (pdfDate: string): string => { if (!pdfDate || !pdfDate.startsWith('D:')) return pdfDate; try { const year = pdfDate.substring(2, 6); const month = pdfDate.substring(6, 8); const day = pdfDate.substring(8, 10); const hour = pdfDate.substring(10, 12); const minute = pdfDate.substring(12, 14); const second = pdfDate.substring(14, 16); return new Date( `${year}-${month}-${day}T${hour}:${minute}:${second}Z` ).toLocaleString(); } catch { return pdfDate; } }; const infoSection = createSection('Info Dictionary'); if (info && Object.keys(info).length > 0) { for (const key in info) { const value = (info as Record)[key]; let displayValue: string; if (value === null || typeof value === 'undefined') { displayValue = '- Not Set -'; } else if ( typeof value === 'object' && 'name' in (value as object) && (value as Record).name ) { displayValue = String((value as Record).name); } else if (typeof value === 'object') { try { displayValue = JSON.stringify(value); } catch { displayValue = '[object Object]'; } } else if ( (key === 'CreationDate' || key === 'ModDate') && typeof value === 'string' ) { displayValue = parsePdfDate(value); } else { displayValue = String(value); } infoSection.ul.appendChild(createListItem(key, displayValue)); } } else { infoSection.ul.innerHTML = `
  • - No Info Dictionary data found -
  • `; } resultsDiv.appendChild(infoSection.wrapper); const fieldsSection = createSection('Interactive Form Fields'); if (fieldObjects && Object.keys(fieldObjects).length > 0) { for (const fieldName in fieldObjects) { const field = fieldObjects[fieldName][0] as Record; const value = field.fieldValue || '- Not Set -'; fieldsSection.ul.appendChild( createListItem(fieldName, String(value)) ); } } else { fieldsSection.ul.innerHTML = `
  • - No interactive form fields found -
  • `; } resultsDiv.appendChild(fieldsSection.wrapper); const createXmpListItem = (key: string, value: string, indent = 0) => { const li = document.createElement('li'); li.className = 'flex flex-col sm:flex-row'; const strong = document.createElement('strong'); strong.className = 'w-56 flex-shrink-0 text-gray-400'; strong.textContent = key; strong.style.paddingLeft = `${indent * 1.2}rem`; const div = document.createElement('div'); div.className = 'flex-grow text-white break-all'; div.textContent = value; li.append(strong, div); return li; }; const createXmpHeaderItem = (key: string, indent = 0) => { const li = document.createElement('li'); li.className = 'flex pt-2'; const strong = document.createElement('strong'); strong.className = 'w-full flex-shrink-0 text-gray-300 font-medium'; strong.textContent = key; strong.style.paddingLeft = `${indent * 1.2}rem`; li.append(strong); return li; }; const appendXmpNodes = ( xmlNode: Element, ulElement: HTMLUListElement, indentLevel: number ) => { const xmpDateKeys = [ 'xap:CreateDate', 'xap:ModifyDate', 'xap:MetadataDate', ]; const childNodes = Array.from(xmlNode.children); for (const child of childNodes) { if (child.nodeType !== 1) continue; let key = child.tagName; const elementChildren = Array.from(child.children).filter( (c) => c.nodeType === 1 ); if (key === 'rdf:li') { appendXmpNodes(child, ulElement, indentLevel); continue; } if (key === 'rdf:Alt') { key = '(alt container)'; } if ( child.getAttribute('rdf:parseType') === 'Resource' && elementChildren.length === 0 ) { ulElement.appendChild( createXmpListItem(key, '(Empty Resource)', indentLevel) ); continue; } if (elementChildren.length > 0) { ulElement.appendChild(createXmpHeaderItem(key, indentLevel)); appendXmpNodes(child, ulElement, indentLevel + 1); } else { let value = (child.textContent ?? '').trim(); if (value) { if (xmpDateKeys.includes(key)) { value = formatIsoDate(value); } ulElement.appendChild( createXmpListItem(key, value, indentLevel) ); } } } }; const xmpSection = createSection('XMP Metadata'); if (rawXmpString) { try { const parser = new DOMParser(); const xmlDoc = parser.parseFromString( rawXmpString, 'application/xml' ); const descriptions = xmlDoc.getElementsByTagName('rdf:Description'); if (descriptions.length > 0) { for (const desc of descriptions) { appendXmpNodes(desc, xmpSection.ul, 0); } } else { appendXmpNodes(xmlDoc.documentElement, xmpSection.ul, 0); } if (xmpSection.ul.children.length === 0) { xmpSection.ul.innerHTML = `
  • - No parseable XMP properties found -
  • `; } } catch (xmlError) { console.error('Failed to parse XMP XML:', xmlError); xmpSection.ul.innerHTML = `
  • - Error parsing XMP XML. Displaying raw. -
  • `; const pre = document.createElement('pre'); pre.className = 'text-xs text-gray-300 whitespace-pre-wrap break-all'; pre.textContent = rawXmpString; xmpSection.ul.appendChild(pre); } } else { xmpSection.ul.innerHTML = `
  • - No XMP metadata found -
  • `; } resultsDiv.appendChild(xmpSection.wrapper); resultsDiv.classList.remove('hidden'); } catch (e) { console.error('Failed to view metadata or fields:', e); showAlert( 'Error', 'Could not fully analyze the PDF. It may be corrupted or have an unusual structure.' ); } finally { hideLoader(); } } if (toolId === 'edit-metadata') { const form = document.getElementById('metadata-form'); const container = document.getElementById('custom-metadata-container'); const addBtn = document.getElementById('add-custom-meta-btn'); const formatDateForInput = (date: Date | undefined) => { if (!date) return ''; const pad = (num: number) => num.toString().padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`; }; (document.getElementById('meta-title') as HTMLInputElement).value = state.pdfDoc.getTitle() || ''; (document.getElementById('meta-author') as HTMLInputElement).value = state.pdfDoc.getAuthor() || ''; (document.getElementById('meta-subject') as HTMLInputElement).value = state.pdfDoc.getSubject() || ''; (document.getElementById('meta-keywords') as HTMLInputElement).value = state.pdfDoc.getKeywords() || ''; (document.getElementById('meta-creator') as HTMLInputElement).value = state.pdfDoc.getCreator() || ''; (document.getElementById('meta-producer') as HTMLInputElement).value = state.pdfDoc.getProducer() || ''; ( document.getElementById('meta-creation-date') as HTMLInputElement ).value = formatDateForInput(state.pdfDoc.getCreationDate()); (document.getElementById('meta-mod-date') as HTMLInputElement).value = formatDateForInput(state.pdfDoc.getModificationDate()); addBtn.onclick = () => { const fieldWrapper = document.createElement('div'); fieldWrapper.className = 'flex flex-col sm:flex-row items-stretch sm:items-center gap-2 custom-field-wrapper'; const keyInput = document.createElement('input'); keyInput.type = 'text'; keyInput.placeholder = 'Key (e.g., Department)'; keyInput.className = 'custom-meta-key w-full sm:w-1/3 bg-gray-800 border border-gray-600 text-white rounded-lg p-2'; const valueInput = document.createElement('input'); valueInput.type = 'text'; valueInput.placeholder = 'Value (e.g., Marketing)'; valueInput.className = 'custom-meta-value w-full sm:flex-grow bg-gray-800 border border-gray-600 text-white rounded-lg p-2'; const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'btn p-2 text-red-500 hover:bg-gray-700 rounded-full self-center sm:self-auto'; removeBtn.innerHTML = ''; removeBtn.addEventListener('click', () => fieldWrapper.remove()); fieldWrapper.append(keyInput, valueInput, removeBtn); container.appendChild(fieldWrapper); createIcons({ icons }); }; form.classList.remove('hidden'); createIcons({ icons }); } if (toolId === 'cropper') { document .getElementById('cropper-ui-container') .classList.remove('hidden'); } if (toolId === 'page-dimensions') { const pageDimLogic = toolLogic['page-dimensions']; if (typeof pageDimLogic === 'function') pageDimLogic(); } if (toolId === 'pdf-to-jpg') { const qualitySlider = document.getElementById( 'jpg-quality' ) as HTMLInputElement; const qualityValue = document.getElementById('jpg-quality-value'); if (qualitySlider && qualityValue) { const updateValue = () => { qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`; }; qualitySlider.addEventListener('input', updateValue); updateValue(); } } if (toolId === 'pdf-to-png') { const qualitySlider = document.getElementById( 'png-quality' ) as HTMLInputElement; const qualityValue = document.getElementById('png-quality-value'); if (qualitySlider && qualityValue) { const updateValue = () => { qualityValue.textContent = `${qualitySlider.value}x`; }; qualitySlider.addEventListener('input', updateValue); updateValue(); } } if (toolId === 'pdf-to-webp') { const qualitySlider = document.getElementById( 'webp-quality' ) as HTMLInputElement; const qualityValue = document.getElementById('webp-quality-value'); if (qualitySlider && qualityValue) { const updateValue = () => { qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`; }; qualitySlider.addEventListener('input', updateValue); updateValue(); } } const setupLogic = toolLogic[toolId]; if ( setupLogic && typeof setupLogic === 'object' && typeof setupLogic.setup === 'function' ) { setupLogic.setup(); } } catch (e) { hideLoader(); showAlert( 'Error', 'Could not load PDF. The file may be invalid, corrupted, or password-protected.' ); console.error(e); } } async function handleMultiFileUpload(toolId: string) { if ( toolId === 'merge' || toolId === 'alternate-merge' || toolId === 'reverse-pages' ) { showLoader('Loading PDF documents...'); const pdfFilesUnloaded: File[] = []; state.files.forEach((file) => { if (file.type === 'application/pdf') { pdfFilesUnloaded.push(file); } }); const pdfFilesLoaded = await Promise.all( pdfFilesUnloaded.map(async (file) => { const pdfBytes = await readFileAsArrayBuffer(file); const pdfDoc = await loadPdfDocument(pdfBytes as ArrayBuffer); return { file, pdfDoc, }; }) ); const encryptedIndices: number[] = []; pdfFilesLoaded.forEach((pdf, index) => { if (pdf.pdfDoc.isEncrypted) { encryptedIndices.push(index); } }); if (encryptedIndices.length > 0) { hideLoader(); const decryptedFiles = await handleEncryptedFiles( pdfFilesUnloaded, encryptedIndices ); for (const [index, decryptedFile] of decryptedFiles) { const originalIndex = state.files.indexOf(pdfFilesUnloaded[index]); if (originalIndex !== -1) { state.files[originalIndex] = decryptedFile; } } const skippedFiles = new Set( encryptedIndices .filter((i) => !decryptedFiles.has(i)) .map((i) => pdfFilesUnloaded[i]) ); if (skippedFiles.size > 0) { state.files = state.files.filter((f) => !skippedFiles.has(f)); } if ( state.files.filter((f) => f.type === 'application/pdf').length === 0 ) { switchView('grid'); return; } showLoader('Loading PDF documents...'); } } const processBtn = document.getElementById('process-btn'); if (processBtn) { (processBtn as HTMLButtonElement).disabled = false; const logic = toolLogic[toolId]; if (logic) { const func = typeof logic === 'object' && typeof logic.process === 'function' ? logic.process : typeof logic === 'function' ? logic : null; if (func) processBtn.onclick = func; } } if (toolId === 'alternate-merge') { const altMerge = toolLogic['alternate-merge']; if (typeof altMerge === 'object' && altMerge.setup) altMerge.setup(); } else if (toolId === 'image-to-pdf') { const imageList = document.getElementById('image-list'); const renderedFiles = new Set( Array.from(imageList.querySelectorAll('li')).map( (li) => li.dataset.fileKey ) ); state.files.forEach((file, index) => { if (!file) { console.error('Invalid file encountered in state.files'); return; } const fileKey = makeUniqueFileKey(index, file.name); if (renderedFiles.has(fileKey)) { return; } let url = activeImageUrls.get(file); if (!url) { url = URL.createObjectURL(file); activeImageUrls.set(file, url); } const li = document.createElement('li'); li.className = 'relative group cursor-move'; li.dataset.fileKey = fileKey; li.dataset.fileIndex = String(index); const wrapper = document.createElement('div'); wrapper.className = 'w-full h-36 sm:h-40 md:h-44 bg-gray-900 rounded-md border-2 border-gray-600 flex items-center justify-center overflow-hidden'; const img = document.createElement('img'); try { const parsed = new URL(url); if (parsed.protocol === 'blob:') { img.src = parsed.href; } } catch { console.warn('Invalid blob URL for preview'); } img.className = 'max-w-full max-h-full object-contain'; const p = document.createElement('p'); p.className = 'absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-white text-xs text-center truncate p-1'; p.textContent = file.name; wrapper.appendChild(img); li.append(wrapper, p); imageList.appendChild(li); }); const syncStateWithDOM = () => { const domOrder = Array.from(imageList.querySelectorAll('li')).map((li) => Number(li.dataset.fileIndex) ); const reordered = domOrder.map((i) => state.files[i]); state.files.length = 0; reordered.forEach((f) => state.files.push(f)); Array.from(imageList.querySelectorAll('li')).forEach((li, i) => { (li as HTMLElement).dataset.fileIndex = String(i); }); }; if (!imageSortableInstance) { imageSortableInstance = Sortable.create(imageList, { animation: 150, onEnd: () => { syncStateWithDOM(); }, }); } syncStateWithDOM(); const opts = document.getElementById('image-to-pdf-options'); if (opts && opts.classList.contains('hidden')) { opts.classList.remove('hidden'); const slider = document.getElementById( 'image-pdf-quality' ) as HTMLInputElement; const value = document.getElementById('image-pdf-quality-value'); if (slider && value) { const update = () => (value.textContent = `${Math.round(parseFloat(slider.value) * 100)}%`); slider.addEventListener('input', update); update(); } } } if (toolId === 'pdf-to-jpg') { const qualitySlider = document.getElementById( 'jpg-quality' ) as HTMLInputElement; const qualityValue = document.getElementById('jpg-quality-value'); if (qualitySlider && qualityValue) { const updateValue = () => { qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`; }; qualitySlider.addEventListener('input', updateValue); updateValue(); } } if (toolId === 'pdf-to-png') { const qualitySlider = document.getElementById( 'png-quality' ) as HTMLInputElement; const qualityValue = document.getElementById('png-quality-value'); if (qualitySlider && qualityValue) { const updateValue = () => { qualityValue.textContent = `${qualitySlider.value}x`; }; qualitySlider.addEventListener('input', updateValue); updateValue(); } } if (toolId === 'pdf-to-webp') { const qualitySlider = document.getElementById( 'webp-quality' ) as HTMLInputElement; const qualityValue = document.getElementById('webp-quality-value'); if (qualitySlider && qualityValue) { const updateValue = () => { qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`; }; qualitySlider.addEventListener('input', updateValue); updateValue(); } } if (toolId === 'png-to-pdf') { const optionsDiv = document.getElementById(`${toolId}-options`); if (optionsDiv) { optionsDiv.classList.remove('hidden'); } } } export function setupFileInputHandler(toolId: string) { const fileInput = document.getElementById('file-input'); const isMultiFileTool = multiFileTools.includes(toolId); let isFirstUpload = true; const processFiles = async (newFiles: File[]) => { if (newFiles.length === 0) return; if (toolId === 'image-to-pdf') { const validTypes = [ 'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/bmp', 'image/tiff', ]; const validFiles = newFiles.filter((file) => validTypes.includes(file.type) ); if (validFiles.length < newFiles.length) { showAlert( 'Invalid Files', 'Some files were skipped because they are not supported images.' ); } newFiles = validFiles; if (newFiles.length === 0) return; } if (!isMultiFileTool || isFirstUpload) { state.files = newFiles; } else { state.files = [...state.files, ...newFiles]; } isFirstUpload = false; const fileDisplayArea = document.getElementById('file-display-area'); if (fileDisplayArea) { renderFileDisplay(fileDisplayArea, state.files); } const fileControls = document.getElementById('file-controls'); if (fileControls) { fileControls.classList.remove('hidden'); createIcons({ icons }); } if (isMultiFileTool) { if ( toolId === 'txt-to-pdf' || toolId === 'compress' || toolId === 'extract-attachments' || toolId === 'flatten' ) { const processBtn = document.getElementById('process-btn'); if (processBtn) { (processBtn as HTMLButtonElement).disabled = false; if (toolId === 'compress') { const optionsDiv = document.getElementById('compress-options'); if (optionsDiv) optionsDiv.classList.remove('hidden'); } processBtn.onclick = () => { const logic = toolLogic[toolId]; if (logic) { if (typeof logic === 'function') logic(); else if (logic.process) logic.process(); } }; } } else { await handleMultiFileUpload(toolId); } } else if (singlePdfLoadTools.includes(toolId)) { await handleSinglePdfUpload(toolId, state.files[0]); } else if (simpleTools.includes(toolId)) { const optionsDivId = toolId === 'change-permissions' ? 'permissions-options' : `${toolId}-options`; const optionsDiv = document.getElementById(optionsDivId); if (optionsDiv) optionsDiv.classList.remove('hidden'); const processBtn = document.getElementById('process-btn'); if (processBtn) { (processBtn as HTMLButtonElement).disabled = false; processBtn.onclick = () => { const logic = toolLogic[toolId]; if (logic) { if (typeof logic === 'function') logic(); else if (logic.process) logic.process(); } }; } } }; fileInput.addEventListener('change', (e) => processFiles(Array.from((e.target as HTMLInputElement).files || [])) ); const setupAddMoreButton = () => { const addMoreBtn = document.getElementById('add-more-btn'); if (addMoreBtn) { addMoreBtn.addEventListener('click', () => fileInput.click()); } }; const setupClearButton = () => { const clearBtn = document.getElementById('clear-files-btn'); if (clearBtn) { clearBtn.addEventListener('click', () => { activeImageUrls.forEach((url) => URL.revokeObjectURL(url)); activeImageUrls.clear(); state.files = []; isFirstUpload = true; (fileInput as HTMLInputElement).value = ''; const fileDisplayArea = document.getElementById('file-display-area'); if (fileDisplayArea) fileDisplayArea.textContent = ''; const fileControls = document.getElementById('file-controls'); if (fileControls) fileControls.classList.add('hidden'); const toolSpecificUI = [ 'file-list', 'page-merge-preview', 'image-list', 'alternate-file-list', ]; toolSpecificUI.forEach((id) => { const el = document.getElementById(id); if (el) el.textContent = ''; }); const processBtn = document.getElementById('process-btn'); if (processBtn) (processBtn as HTMLButtonElement).disabled = true; }); } }; setTimeout(() => { setupAddMoreButton(); setupClearButton(); }, 100); }