diff --git a/frontend/scripts/generate-icons.js b/frontend/scripts/generate-icons.js index 681b06728..0fd42a4df 100644 --- a/frontend/scripts/generate-icons.js +++ b/frontend/scripts/generate-icons.js @@ -1,7 +1,6 @@ #!/usr/bin/env node const { icons } = require('@iconify-json/material-symbols'); -const { getIcons } = require('@iconify/utils'); const fs = require('fs'); const path = require('path'); @@ -89,68 +88,73 @@ function scanForUsedIcons() { return iconArray; } -// Auto-detect used icons -const usedIcons = scanForUsedIcons(); +// Main async function +async function main() { + // Auto-detect used icons + const usedIcons = scanForUsedIcons(); -// Check if we need to regenerate (compare with existing) -const outputPath = path.join(__dirname, '..', 'src', 'assets', 'material-symbols-icons.json'); -let needsRegeneration = true; + // Check if we need to regenerate (compare with existing) + const outputPath = path.join(__dirname, '..', 'src', 'assets', 'material-symbols-icons.json'); + let needsRegeneration = true; -if (fs.existsSync(outputPath)) { - try { - const existingSet = JSON.parse(fs.readFileSync(outputPath, 'utf8')); - const existingIcons = Object.keys(existingSet.icons || {}).sort(); - const currentIcons = [...usedIcons].sort(); - - if (JSON.stringify(existingIcons) === JSON.stringify(currentIcons)) { - needsRegeneration = false; - info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`); + if (fs.existsSync(outputPath)) { + try { + const existingSet = JSON.parse(fs.readFileSync(outputPath, 'utf8')); + const existingIcons = Object.keys(existingSet.icons || {}).sort(); + const currentIcons = [...usedIcons].sort(); + + if (JSON.stringify(existingIcons) === JSON.stringify(currentIcons)) { + needsRegeneration = false; + info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`); + } + } catch (error) { + // If we can't parse existing file, regenerate + needsRegeneration = true; } - } catch (error) { - // If we can't parse existing file, regenerate - needsRegeneration = true; } -} -if (!needsRegeneration) { - info('🎉 No regeneration needed!'); - process.exit(0); -} + if (!needsRegeneration) { + info('🎉 No regeneration needed!'); + process.exit(0); + } -info(`🔍 Extracting ${usedIcons.length} icons from Material Symbols...`); + info(`🔍 Extracting ${usedIcons.length} icons from Material Symbols...`); -// Extract only our used icons from the full set -const extractedIcons = getIcons(icons, usedIcons); + // Dynamic import of ES module + const { getIcons } = await import('@iconify/utils'); + + // Extract only our used icons from the full set + const extractedIcons = getIcons(icons, usedIcons); -if (!extractedIcons) { - console.error('❌ Failed to extract icons'); - process.exit(1); -} + if (!extractedIcons) { + console.error('❌ Failed to extract icons'); + process.exit(1); + } -// Check for missing icons -const extractedIconNames = Object.keys(extractedIcons.icons || {}); -const missingIcons = usedIcons.filter(icon => !extractedIconNames.includes(icon)); + // Check for missing icons + const extractedIconNames = Object.keys(extractedIcons.icons || {}); + const missingIcons = usedIcons.filter(icon => !extractedIconNames.includes(icon)); -if (missingIcons.length > 0) { - info(`⚠️ Missing icons (${missingIcons.length}): ${missingIcons.join(', ')}`); - info('💡 These icons don\'t exist in Material Symbols. Please use available alternatives.'); -} + if (missingIcons.length > 0) { + info(`⚠️ Missing icons (${missingIcons.length}): ${missingIcons.join(', ')}`); + info('💡 These icons don\'t exist in Material Symbols. Please use available alternatives.'); + } -// Create output directory -const outputDir = path.join(__dirname, '..', 'src', 'assets'); -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} + // Create output directory + const outputDir = path.join(__dirname, '..', 'src', 'assets'); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } -// Write the extracted icon set to a file (outputPath already defined above) -fs.writeFileSync(outputPath, JSON.stringify(extractedIcons, null, 2)); + // Write the extracted icon set to a file (outputPath already defined above) + fs.writeFileSync(outputPath, JSON.stringify(extractedIcons, null, 2)); -info(`✅ Successfully extracted ${Object.keys(extractedIcons.icons || {}).length} icons`); -info(`📦 Bundle size: ${Math.round(JSON.stringify(extractedIcons).length / 1024)}KB`); -info(`💾 Saved to: ${outputPath}`); + info(`✅ Successfully extracted ${Object.keys(extractedIcons.icons || {}).length} icons`); + info(`📦 Bundle size: ${Math.round(JSON.stringify(extractedIcons).length / 1024)}KB`); + info(`💾 Saved to: ${outputPath}`); -// Generate TypeScript types -const typesContent = `// Auto-generated icon types + // Generate TypeScript types + const typesContent = `// Auto-generated icon types // This file is automatically generated by scripts/generate-icons.js // Do not edit manually - changes will be overwritten @@ -168,8 +172,15 @@ declare const iconSet: IconSet; export default iconSet; `; -const typesPath = path.join(outputDir, 'material-symbols-icons.d.ts'); -fs.writeFileSync(typesPath, typesContent); + const typesPath = path.join(outputDir, 'material-symbols-icons.d.ts'); + fs.writeFileSync(typesPath, typesContent); -info(`📝 Generated types: ${typesPath}`); -info(`🎉 Icon extraction complete!`); \ No newline at end of file + info(`📝 Generated types: ${typesPath}`); + info(`🎉 Icon extraction complete!`); +} + +// Run the main function +main().catch(error => { + console.error('❌ Script failed:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index 6c20a8c3c..19c83f56c 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -327,7 +327,22 @@ const PageEditor = ({ return sourceFiles.size > 0 ? sourceFiles : null; }, [activeFileIds, selectors]); + // Helper function to generate proper filename for exports + const getExportFilename = useCallback((): string => { + if (activeFileIds.length <= 1) { + // Single file - use original name + return displayDocument?.name || 'document.pdf'; + } + // Multiple files - use first file name with " (merged)" suffix + const firstFile = selectors.getFile(activeFileIds[0]); + if (firstFile) { + const baseName = firstFile.name.replace(/\.pdf$/i, ''); + return `${baseName} (merged).pdf`; + } + + return 'merged-document.pdf'; + }, [activeFileIds, selectors, displayDocument]); const onExportSelected = useCallback(async () => { if (!displayDocument || selectedPageNumbers.length === 0) return; @@ -355,17 +370,18 @@ const PageEditor = ({ console.log('Exporting selected pages:', selectedPageNumbers, 'with DOM rotations applied'); const sourceFiles = getSourceFiles(); + const exportFilename = getExportFilename(); const result = sourceFiles ? await pdfExportService.exportPDFMultiFile( documentWithDOMState, sourceFiles, selectedPageIds, - { selectedOnly: true, filename: documentWithDOMState.name } + { selectedOnly: true, filename: exportFilename } ) : await pdfExportService.exportPDF( documentWithDOMState, selectedPageIds, - { selectedOnly: true, filename: documentWithDOMState.name } + { selectedOnly: true, filename: exportFilename } ); // Step 4: Download the result @@ -376,7 +392,7 @@ const PageEditor = ({ console.error('Export failed:', error); setExportLoading(false); } - }, [displayDocument, selectedPageNumbers, mergedPdfDocument, splitPositions, getSourceFiles]); + }, [displayDocument, selectedPageNumbers, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]); const onExportAll = useCallback(async () => { if (!displayDocument) return; @@ -399,10 +415,11 @@ const PageEditor = ({ const filenames: string[] = []; const sourceFiles = getSourceFiles(); + const exportFilename = getExportFilename(); for (const doc of processedDocuments) { const result = sourceFiles - ? await pdfExportService.exportPDFMultiFile(doc, sourceFiles, [], { filename: doc.name }) - : await pdfExportService.exportPDF(doc, [], { filename: doc.name }); + ? await pdfExportService.exportPDFMultiFile(doc, sourceFiles, [], { filename: exportFilename }) + : await pdfExportService.exportPDF(doc, [], { filename: exportFilename }); blobs.push(result.blob); filenames.push(result.filename); } @@ -416,24 +433,25 @@ const PageEditor = ({ }); const zipBlob = await zip.generateAsync({ type: 'blob' }); - const zipFilename = displayDocument.name.replace(/\.pdf$/i, '.zip'); + const zipFilename = exportFilename.replace(/\.pdf$/i, '.zip'); pdfExportService.downloadFile(zipBlob, zipFilename); } else { // Single document - regular export console.log('Exporting as single PDF'); const sourceFiles = getSourceFiles(); + const exportFilename = getExportFilename(); const result = sourceFiles ? await pdfExportService.exportPDFMultiFile( processedDocuments, sourceFiles, [], - { selectedOnly: false, filename: processedDocuments.name } + { selectedOnly: false, filename: exportFilename } ) : await pdfExportService.exportPDF( processedDocuments, [], - { selectedOnly: false, filename: processedDocuments.name } + { selectedOnly: false, filename: exportFilename } ); pdfExportService.downloadFile(result.blob, result.filename); @@ -444,7 +462,7 @@ const PageEditor = ({ console.error('Export failed:', error); setExportLoading(false); } - }, [displayDocument, mergedPdfDocument, splitPositions, getSourceFiles]); + }, [displayDocument, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]); // Apply DOM changes to document state using dedicated service const applyChanges = useCallback(() => { @@ -543,32 +561,6 @@ const PageEditor = ({ {displayDocument && ( - {/* File name and basic controls */} - - - - - {selectionMode && ( - <> - - - {selectedPageNumbers.length} selected - - - )} - - {/* Split Lines Overlay */} diff --git a/frontend/src/services/pdfExportService.ts b/frontend/src/services/pdfExportService.ts index 0a71cb4b2..96b8b8670 100644 --- a/frontend/src/services/pdfExportService.ts +++ b/frontend/src/services/pdfExportService.ts @@ -31,7 +31,7 @@ export class PDFExportService { const originalPDFBytes = await pdfDocument.file.arrayBuffer(); const sourceDoc = await PDFLibDocument.load(originalPDFBytes); const blob = await this.createSingleDocument(sourceDoc, pagesToExport); - const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly); + const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly, false); return { blob, filename: exportFilename }; } catch (error) { @@ -62,7 +62,7 @@ export class PDFExportService { } const blob = await this.createMultiSourceDocument(sourceFiles, pagesToExport); - const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly); + const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly, false); return { blob, filename: exportFilename }; } catch (error) { diff --git a/frontend/src/types/pageEditor.ts b/frontend/src/types/pageEditor.ts index 32b64a933..5e638f02d 100644 --- a/frontend/src/types/pageEditor.ts +++ b/frontend/src/types/pageEditor.ts @@ -51,6 +51,8 @@ export interface PageEditorFunctions { handleDelete: () => void; handleSplit: () => void; handleSplitAll: () => void; + handlePageBreak: () => void; + handlePageBreakAll: () => void; onExportSelected: () => void; onExportAll: () => void; exportLoading: boolean;