From 41c82b15da41154ad34a475ba345f6ea9efd6cc0 Mon Sep 17 00:00:00 2001 From: Reece Date: Wed, 21 May 2025 21:47:44 +0100 Subject: [PATCH] TSX rewrite and query strings initial set up --- frontend/package-lock.json | 25 +- frontend/package.json | 5 +- frontend/src/components/DeepLinks.tsx | 44 +++ .../{FileManager.js => FileManager.tsx} | 82 +++-- frontend/src/components/PageEditor.js | 9 - frontend/src/components/PageEditor.tsx | 196 ++++++++++++ frontend/src/components/ToolPicker.tsx | 76 +++++ .../src/components/{Viewer.js => Viewer.tsx} | 33 +- frontend/src/global.d.ts | 6 + frontend/src/pages/HomePage.js | 201 ------------- frontend/src/pages/HomePage.tsx | 283 ++++++++++++++++++ .../src/tools/{Compress.js => Compress.tsx} | 52 ++-- frontend/src/tools/Merge.js | 101 ------- frontend/src/tools/Merge.tsx | 112 +++++++ frontend/src/tools/Split.js | 208 ------------- frontend/src/tools/Split.tsx | 232 ++++++++++++++ frontend/tsconfig.json | 117 ++++++++ .../controller/api/AnalysisController.java | 3 +- .../api/misc/CompressController.java | 44 +-- .../controller/api/security/GetInfoOnPDF.java | 4 +- .../api/security/RedactController.java | 1 - .../model/api/security/RedactPdfRequest.java | 4 +- 22 files changed, 1228 insertions(+), 610 deletions(-) create mode 100644 frontend/src/components/DeepLinks.tsx rename frontend/src/components/{FileManager.js => FileManager.tsx} (67%) delete mode 100644 frontend/src/components/PageEditor.js create mode 100644 frontend/src/components/PageEditor.tsx create mode 100644 frontend/src/components/ToolPicker.tsx rename frontend/src/components/{Viewer.js => Viewer.tsx} (79%) create mode 100644 frontend/src/global.d.ts delete mode 100644 frontend/src/pages/HomePage.js create mode 100644 frontend/src/pages/HomePage.tsx rename frontend/src/tools/{Compress.js => Compress.tsx} (67%) delete mode 100644 frontend/src/tools/Merge.js create mode 100644 frontend/src/tools/Merge.tsx delete mode 100644 frontend/src/tools/Split.js create mode 100644 frontend/src/tools/Split.tsx create mode 100644 frontend/tsconfig.json diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 152b15d39..486a3eab3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,10 +28,13 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", "postcss": "^8.5.3", "postcss-cli": "^11.0.1", "postcss-preset-mantine": "^1.17.0", - "postcss-simple-vars": "^7.0.1" + "postcss-simple-vars": "^7.0.1", + "typescript": "^5.8.3" } }, "node_modules/@adobe/css-tools": { @@ -4372,11 +4375,20 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", + "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", @@ -17958,17 +17970,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { diff --git a/frontend/package.json b/frontend/package.json index 8762eb14b..45f2c5fc5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,9 +48,12 @@ ] }, "devDependencies": { + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", "postcss": "^8.5.3", "postcss-cli": "^11.0.1", "postcss-preset-mantine": "^1.17.0", - "postcss-simple-vars": "^7.0.1" + "postcss-simple-vars": "^7.0.1", + "typescript": "^5.8.3" } } diff --git a/frontend/src/components/DeepLinks.tsx b/frontend/src/components/DeepLinks.tsx new file mode 100644 index 000000000..53a4ab7b5 --- /dev/null +++ b/frontend/src/components/DeepLinks.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Button, Stack, Text, Group } from '@mantine/core'; + +const DeepLinks: React.FC = () => { + const commonLinks = [ + { + name: "Split PDF Pages 1-5", + url: "/?tool=split&splitMode=byPages&pages=1-5&view=viewer", + description: "Split a PDF and extract pages 1-5" + }, + { + name: "Compress PDF (High)", + url: "/?tool=compress&level=9&grayscale=true&view=viewer", + description: "Compress a PDF with high compression level" + }, + { + name: "Merge PDFs", + url: "/?tool=merge&view=fileManager", + description: "Combine multiple PDF files into one" + } + ]; + + return ( + + Common PDF Operations + {commonLinks.map((link, index) => ( + + + {link.description} + + ))} + + ); +}; + +export default DeepLinks; diff --git a/frontend/src/components/FileManager.js b/frontend/src/components/FileManager.tsx similarity index 67% rename from frontend/src/components/FileManager.js rename to frontend/src/components/FileManager.tsx index df0078759..f4a05e704 100644 --- a/frontend/src/components/FileManager.js +++ b/frontend/src/components/FileManager.tsx @@ -1,25 +1,34 @@ import React, { useState, useEffect } from "react"; import { Card, Group, Text, Stack, Image, Badge, Button, Box, Flex } from "@mantine/core"; import { Dropzone, MIME_TYPES } from "@mantine/dropzone"; -import { GlobalWorkerOptions, getDocument, version as pdfjsVersion } from "pdfjs-dist"; -GlobalWorkerOptions.workerSrc = `${process.env.PUBLIC_URL}/pdf.worker.js`; +import { GlobalWorkerOptions, getDocument } from "pdfjs-dist"; -function getFileDate(file) { +GlobalWorkerOptions.workerSrc = + (import.meta as any).env?.PUBLIC_URL + ? `${(import.meta as any).env.PUBLIC_URL}/pdf.worker.js` + : "/pdf.worker.js"; + +export interface FileWithUrl extends File { + url?: string; + file?: File; +} + +function getFileDate(file: File): string { if (file.lastModified) { return new Date(file.lastModified).toLocaleString(); } return "Unknown"; } -function getFileSize(file) { +function getFileSize(file: File): string { if (!file.size) return "Unknown"; if (file.size < 1024) return `${file.size} B`; if (file.size < 1024 * 1024) return `${(file.size / 1024).toFixed(1)} KB`; return `${(file.size / (1024 * 1024)).toFixed(2)} MB`; } -function usePdfThumbnail(file) { - const [thumb, setThumb] = useState(null); +function usePdfThumbnail(file: File | undefined | null): string | null { + const [thumb, setThumb] = useState(null); useEffect(() => { let cancelled = false; @@ -34,8 +43,10 @@ function usePdfThumbnail(file) { canvas.width = viewport.width; canvas.height = viewport.height; const context = canvas.getContext("2d"); - await page.render({ canvasContext: context, viewport }).promise; - if (!cancelled) setThumb(canvas.toDataURL()); + if (context) { + await page.render({ canvasContext: context, viewport }).promise; + if (!cancelled) setThumb(canvas.toDataURL()); + } } catch { if (!cancelled) setThumb(null); } @@ -47,7 +58,13 @@ function usePdfThumbnail(file) { return thumb; } -function FileCard({ file, onRemove, onDoubleClick }) { +interface FileCardProps { + file: File; + onRemove: () => void; + onDoubleClick?: () => void; +} + +function FileCard({ file, onRemove, onDoubleClick }: FileCardProps) { const thumb = usePdfThumbnail(file); return ( @@ -59,7 +76,7 @@ function FileCard({ file, onRemove, onDoubleClick }) { style={{ width: 225, minWidth: 180, maxWidth: 260, cursor: onDoubleClick ? "pointer" : undefined }} onDoubleClick={onDoubleClick} > - + ) : ( - PDF + PDF )} - + {file.name} - + {getFileSize(file)} @@ -104,12 +121,26 @@ function FileCard({ file, onRemove, onDoubleClick }) { ); } -export default function FileManager({ files = [], setFiles, allowMultiple = true, setPdfFile, setCurrentView }) { - const handleDrop = (uploadedFiles) => { +interface FileManagerProps { + files: FileWithUrl[]; + setFiles: React.Dispatch>; + allowMultiple?: boolean; + setPdfFile?: (fileObj: { file: File; url: string }) => void; + setCurrentView?: (view: string) => void; +} + +const FileManager: React.FC = ({ + files = [], + setFiles, + allowMultiple = true, + setPdfFile, + setCurrentView, +}) => { + const handleDrop = (uploadedFiles: File[]) => { setFiles((prevFiles) => (allowMultiple ? [...prevFiles, ...uploadedFiles] : uploadedFiles)); }; - const handleRemoveFile = (index) => { + const handleRemoveFile = (index: number) => { setFiles((prevFiles) => prevFiles.filter((_, i) => i !== index)); }; @@ -131,14 +162,14 @@ export default function FileManager({ files = [], setFiles, allowMultiple = true justifyContent: "center", }} > - + Drag PDF files here or click to select {files.length === 0 ? ( - + No files uploaded yet. ) : ( @@ -155,11 +186,12 @@ export default function FileManager({ files = [], setFiles, allowMultiple = true file={file} onRemove={() => handleRemoveFile(idx)} onDoubleClick={() => { - const fileObj = file.file || file; // handle wrapped or raw File - setPdfFile && setPdfFile({ - file: fileObj, - url: URL.createObjectURL(fileObj), - }); + const fileObj = (file as FileWithUrl).file || file; + setPdfFile && + setPdfFile({ + file: fileObj, + url: URL.createObjectURL(fileObj), + }); setCurrentView && setCurrentView("viewer"); }} /> @@ -169,4 +201,6 @@ export default function FileManager({ files = [], setFiles, allowMultiple = true )} ); -} +}; + +export default FileManager; diff --git a/frontend/src/components/PageEditor.js b/frontend/src/components/PageEditor.js deleted file mode 100644 index 5e331d954..000000000 --- a/frontend/src/components/PageEditor.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -export default function PageEditor({ pdfFile }) { - return ( -
-

Page Editor is under construction.

-
- ); -} diff --git a/frontend/src/components/PageEditor.tsx b/frontend/src/components/PageEditor.tsx new file mode 100644 index 000000000..2be6acea0 --- /dev/null +++ b/frontend/src/components/PageEditor.tsx @@ -0,0 +1,196 @@ +import React, { useState } from "react"; +import { + Paper, Button, Group, Text, Stack, Center, Checkbox, ScrollArea, Box, Tooltip, ActionIcon, Notification +} from "@mantine/core"; +import UndoIcon from "@mui/icons-material/Undo"; +import RedoIcon from "@mui/icons-material/Redo"; +import AddIcon from "@mui/icons-material/Add"; +import ContentCutIcon from "@mui/icons-material/ContentCut"; +import DownloadIcon from "@mui/icons-material/Download"; +import RotateLeftIcon from "@mui/icons-material/RotateLeft"; +import RotateRightIcon from "@mui/icons-material/RotateRight"; +import DeleteIcon from "@mui/icons-material/Delete"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; + +export interface PageEditorProps { + file: { file: File; url: string } | null; + setFile?: (file: { file: File; url: string } | null) => void; + downloadUrl?: string | null; + setDownloadUrl?: (url: string | null) => void; +} + +const DUMMY_PAGE_COUNT = 8; // Replace with real page count from PDF + +const PageEditor: React.FC = ({ + file, + setFile, + downloadUrl, + setDownloadUrl, +}) => { + const [selectedPages, setSelectedPages] = useState([]); + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(false); + const [undoStack, setUndoStack] = useState([]); + const [redoStack, setRedoStack] = useState([]); + + // Dummy page thumbnails + const pages = Array.from({ length: DUMMY_PAGE_COUNT }, (_, i) => i + 1); + + const selectAll = () => setSelectedPages(pages); + const deselectAll = () => setSelectedPages([]); + const togglePage = (page: number) => + setSelectedPages((prev) => + prev.includes(page) ? prev.filter((p) => p !== page) : [...prev, page] + ); + + // Undo/redo logic for selection + const handleUndo = () => { + if (undoStack.length > 0) { + setRedoStack([selectedPages, ...redoStack]); + setSelectedPages(undoStack[0]); + setUndoStack(undoStack.slice(1)); + } + }; + const handleRedo = () => { + if (redoStack.length > 0) { + setUndoStack([selectedPages, ...undoStack]); + setSelectedPages(redoStack[0]); + setRedoStack(redoStack.slice(1)); + } + }; + + // Example action handlers (replace with real API calls) + const handleRotateLeft = () => setStatus("Rotated left: " + selectedPages.join(", ")); + const handleRotateRight = () => setStatus("Rotated right: " + selectedPages.join(", ")); + const handleDelete = () => setStatus("Deleted: " + selectedPages.join(", ")); + const handleMoveLeft = () => setStatus("Moved left: " + selectedPages.join(", ")); + const handleMoveRight = () => setStatus("Moved right: " + selectedPages.join(", ")); + const handleSplit = () => setStatus("Split at: " + selectedPages.join(", ")); + const handleInsertPageBreak = () => setStatus("Inserted page break at: " + selectedPages.join(", ")); + const handleAddFile = () => setStatus("Add file not implemented in demo"); + + if (!file) { + return ( + +
+ No PDF loaded. Please upload a PDF to edit. +
+
+ ); + } + + return ( + + + {/* Sidebar */} + + PDF Multitool + + + + + + + + + + + + + {/* Main multitool area */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {pages.map((page) => ( + + togglePage(page)} + label={`Page ${page}`} + /> + + {/* Replace with real thumbnail */} +
+ + {page} + +
+
+
+ ))} +
+
+
+
+ {status && ( + setStatus(null)}> + {status} + + )} +
+ ); +}; + +export default PageEditor; diff --git a/frontend/src/components/ToolPicker.tsx b/frontend/src/components/ToolPicker.tsx new file mode 100644 index 000000000..5ece14018 --- /dev/null +++ b/frontend/src/components/ToolPicker.tsx @@ -0,0 +1,76 @@ +import React, { useState } from "react"; +import { Box, Text, Stack, Button, TextInput } from "@mantine/core"; + +type Tool = { + icon: React.ReactNode; + name: string; +}; + +type ToolRegistry = { + [id: string]: Tool; +}; + +interface ToolPickerProps { + selectedToolKey: string; + onSelect: (id: string) => void; + toolRegistry: ToolRegistry; +} + +const ToolPicker: React.FC = ({ selectedToolKey, onSelect, toolRegistry }) => { + const [search, setSearch] = useState(""); + + const filteredTools = Object.entries(toolRegistry).filter(([_, { name }]) => + name.toLowerCase().includes(search.toLowerCase()) + ); + + return ( + + + Tools + + setSearch(e.currentTarget.value)} + mb="md" + autoComplete="off" + /> + + {filteredTools.length === 0 ? ( + + No tools found + + ) : ( + filteredTools.map(([id, { icon, name }]) => ( + + )) + )} + + + ); +}; + +export default ToolPicker; diff --git a/frontend/src/components/Viewer.js b/frontend/src/components/Viewer.tsx similarity index 79% rename from frontend/src/components/Viewer.js rename to frontend/src/components/Viewer.tsx index 1973582d0..64c5bc620 100644 --- a/frontend/src/components/Viewer.js +++ b/frontend/src/components/Viewer.tsx @@ -1,13 +1,18 @@ import React, { useEffect, useState } from "react"; import { Paper, Stack, Text, ScrollArea, Loader, Center, Button, Group } from "@mantine/core"; -import { getDocument, GlobalWorkerOptions, version as pdfjsVersion } from "pdfjs-dist"; +import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; GlobalWorkerOptions.workerSrc = `${process.env.PUBLIC_URL}/pdf.worker.js`; -export default function Viewer({ pdfFile, setPdfFile }) { - const [numPages, setNumPages] = useState(0); - const [pageImages, setPageImages] = useState([]); - const [loading, setLoading] = useState(false); +export interface ViewerProps { + pdfFile: { file: File; url: string } | null; + setPdfFile: (file: { file: File; url: string } | null) => void; +} + +const Viewer: React.FC = ({ pdfFile, setPdfFile }) => { + const [numPages, setNumPages] = useState(0); + const [pageImages, setPageImages] = useState([]); + const [loading, setLoading] = useState(false); useEffect(() => { let cancelled = false; @@ -21,7 +26,7 @@ export default function Viewer({ pdfFile, setPdfFile }) { try { const pdf = await getDocument(pdfFile.url).promise; setNumPages(pdf.numPages); - const images = []; + const images: string[] = []; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const viewport = page.getViewport({ scale: 1.2 }); @@ -29,8 +34,10 @@ export default function Viewer({ pdfFile, setPdfFile }) { canvas.width = viewport.width; canvas.height = viewport.height; const ctx = canvas.getContext("2d"); - await page.render({ canvasContext: ctx, viewport }).promise; - images.push(canvas.toDataURL()); + if (ctx) { + await page.render({ canvasContext: ctx, viewport }).promise; + images.push(canvas.toDataURL()); + } } if (!cancelled) setPageImages(images); } catch { @@ -59,7 +66,7 @@ export default function Viewer({ pdfFile, setPdfFile }) { accept="application/pdf" hidden onChange={(e) => { - const file = e.target.files[0]; + const file = e.target.files?.[0]; if (file && file.type === "application/pdf") { const fileUrl = URL.createObjectURL(file); setPdfFile({ file, url: fileUrl }); @@ -75,7 +82,7 @@ export default function Viewer({ pdfFile, setPdfFile }) { ) : ( - + {pageImages.length === 0 && ( No pages to display. )} @@ -97,7 +104,7 @@ export default function Viewer({ pdfFile, setPdfFile }) { )} {pdfFile && ( - + - ))} -
- - - {/* Middle: Main View (Viewer, Editor, Manager) */} - -
- - - -
- - {(currentView === "viewer" || currentView === "pageEditor") && !pdfFile ? ( - - ) : currentView === "viewer" ? ( - - ) : currentView === "pageEditor" ? ( - - ) : ( - - )} - -
- {/* Right: Tool Interaction */} - - {selectedTool && selectedTool.component && ( - <> - {React.createElement(selectedTool.component, { - file: pdfFile, - setPdfFile, - files, - setFiles, - downloadUrl, - setDownloadUrl, - })} - - )} - - - ); -} diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx new file mode 100644 index 000000000..baebfbc46 --- /dev/null +++ b/frontend/src/pages/HomePage.tsx @@ -0,0 +1,283 @@ +import React, { useState, useCallback, useEffect } from "react"; +import { useSearchParams } from "react-router-dom"; +import AddToPhotosIcon from "@mui/icons-material/AddToPhotos"; +import ContentCutIcon from "@mui/icons-material/ContentCut"; +import ZoomInMapIcon from "@mui/icons-material/ZoomInMap"; +import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; +import VisibilityIcon from "@mui/icons-material/Visibility"; +import EditNoteIcon from "@mui/icons-material/EditNote"; +import { Group, SegmentedControl, Paper, Center, Box } from "@mantine/core"; + +import ToolPicker from "../components/ToolPicker"; +import FileManager from "../components/FileManager"; +import SplitPdfPanel from "../tools/Split"; +import CompressPdfPanel from "../tools/Compress"; +import MergePdfPanel from "../tools/Merge"; +import PageEditor from "../components/PageEditor"; +import Viewer from "../components/Viewer"; + +type ToolRegistryEntry = { + icon: React.ReactNode; + name: string; + component: React.ComponentType; + view: string; +}; + +type ToolRegistry = { + [key: string]: ToolRegistryEntry; +}; + +const toolRegistry: ToolRegistry = { + split: { icon: , name: "Split PDF", component: SplitPdfPanel, view: "viewer" }, + compress: { icon: , name: "Compress PDF", component: CompressPdfPanel, view: "viewer" }, + merge: { icon: , name: "Merge PDFs", component: MergePdfPanel, view: "fileManager" }, +}; + +const VIEW_OPTIONS = [ + { + label: ( + + + + ), + value: "viewer", + }, + { + label: ( + + + + ), + value: "pageEditor", + }, + { + label: ( + + + + ), + value: "fileManager", + }, +]; + +export default function HomePage() { + const [searchParams, setSearchParams] = useSearchParams(); + + // Core app state + const [selectedToolKey, setSelectedToolKey] = useState(searchParams.get("tool") || "split"); + const [currentView, setCurrentView] = useState(searchParams.get("view") || "viewer"); + const [pdfFile, setPdfFile] = useState(null); + const [files, setFiles] = useState([]); + const [downloadUrl, setDownloadUrl] = useState(null); + + // Tool-specific parameters + const [splitParams, setSplitParams] = useState({ + mode: searchParams.get("splitMode") || "byPages", + pages: searchParams.get("pages") || "", + hDiv: searchParams.get("hDiv") || "0", + vDiv: searchParams.get("vDiv") || "1", + merge: searchParams.get("merge") === "true", + splitType: searchParams.get("splitType") || "size", + splitValue: searchParams.get("splitValue") || "", + bookmarkLevel: searchParams.get("bookmarkLevel") || "0", + includeMetadata: searchParams.get("includeMetadata") === "true", + allowDuplicates: searchParams.get("allowDuplicates") === "true", + }); + + // Update URL when core state changes + useEffect(() => { + const params = new URLSearchParams(searchParams); + params.set("tool", selectedToolKey); + params.set("view", currentView); + setSearchParams(params, { replace: true }); + }, [selectedToolKey, currentView, setSearchParams]); + + // Handle tool selection + const handleToolSelect = useCallback( + (id: string) => { + setSelectedToolKey(id); + if (toolRegistry[id]?.view) setCurrentView(toolRegistry[id].view); + }, + [toolRegistry] + ); + + // Handle split parameter updates + const updateSplitParams = useCallback((newParams: Partial) => { + setSplitParams(prev => { + const updated = { ...prev, ...newParams }; + + // Update URL with split params + const params = new URLSearchParams(searchParams); + + // Clear old parameters when mode changes + if (newParams.mode && newParams.mode !== prev.mode) { + params.delete("pages"); + params.delete("hDiv"); + params.delete("vDiv"); + params.delete("merge"); + params.delete("splitType"); + params.delete("splitValue"); + params.delete("bookmarkLevel"); + params.delete("includeMetadata"); + params.delete("allowDuplicates"); + } + + // Set the mode + params.set("splitMode", updated.mode); + + // Set mode-specific parameters + if (updated.mode === "byPages" && updated.pages) { + params.set("pages", updated.pages); + } else if (updated.mode === "bySections") { + params.set("hDiv", updated.hDiv); + params.set("vDiv", updated.vDiv); + params.set("merge", String(updated.merge)); + } else if (updated.mode === "bySizeOrCount") { + params.set("splitType", updated.splitType); + if (updated.splitValue) params.set("splitValue", updated.splitValue); + } else if (updated.mode === "byChapters") { + params.set("bookmarkLevel", updated.bookmarkLevel); + params.set("includeMetadata", String(updated.includeMetadata)); + params.set("allowDuplicates", String(updated.allowDuplicates)); + } + + setSearchParams(params, { replace: true }); + return updated; + }); + }, [searchParams, setSearchParams]); + + const selectedTool = toolRegistry[selectedToolKey]; + + // Tool component rendering + const renderTool = () => { + if (!selectedTool || !selectedTool.component) { + return
Tool not found
; + } + + // Pass appropriate props based on tool type + if (selectedToolKey === "split") { + return React.createElement(selectedTool.component, { + file: pdfFile, + setPdfFile, + downloadUrl, + setDownloadUrl, + // Tool-specific params and update function + params: splitParams, + updateParams: updateSplitParams + }); + } + + // For other tools, pass standard props + return React.createElement(selectedTool.component, { + file: pdfFile, + setPdfFile, + files, + setFiles, + downloadUrl, + setDownloadUrl, + }); + }; + + + return ( + + {/* Left: Tool Picker */} + + + {/* Middle: Main View (Viewer, Editor, Manager) */} + +
+ + + +
+ + {(currentView === "viewer" || currentView === "pageEditor") && !pdfFile ? ( + + ) : currentView === "viewer" ? ( + + ) : currentView === "pageEditor" ? ( + + ) : ( + + )} + +
+ {/* Right: Tool Interaction */} + + {selectedTool && selectedTool.component && ( + <> + {renderTool()} + + )} + +
+ ); +} diff --git a/frontend/src/tools/Compress.js b/frontend/src/tools/Compress.tsx similarity index 67% rename from frontend/src/tools/Compress.js rename to frontend/src/tools/Compress.tsx index 43cadcf58..2701bb64a 100644 --- a/frontend/src/tools/Compress.js +++ b/frontend/src/tools/Compress.tsx @@ -1,21 +1,31 @@ import React, { useState } from "react"; import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Paper } from "@mantine/core"; -export default function CompressPdfPanel({ files = [], setDownloadUrl, setLoading }) { - const [selected, setSelected] = useState(files.map(() => false)); - const [compressionLevel, setCompressionLevel] = useState(5); // 1-9, default 5 - const [grayscale, setGrayscale] = useState(false); - const [removeMetadata, setRemoveMetadata] = useState(false); - const [expectedSize, setExpectedSize] = useState(""); - const [aggressive, setAggressive] = useState(false); - const [localLoading, setLocalLoading] = useState(false); +export interface CompressProps { + files?: File[]; + setDownloadUrl?: (url: string) => void; + setLoading?: (loading: boolean) => void; +} + +const CompressPdfPanel: React.FC = ({ + files = [], + setDownloadUrl, + setLoading, +}) => { + const [selected, setSelected] = useState(files.map(() => false)); + const [compressionLevel, setCompressionLevel] = useState(5); + const [grayscale, setGrayscale] = useState(false); + const [removeMetadata, setRemoveMetadata] = useState(false); + const [expectedSize, setExpectedSize] = useState(""); + const [aggressive, setAggressive] = useState(false); + const [localLoading, setLocalLoading] = useState(false); // Update selection state if files prop changes React.useEffect(() => { setSelected(files.map(() => false)); }, [files]); - const handleCheckbox = idx => { + const handleCheckbox = (idx: number) => { setSelected(sel => sel.map((v, i) => (i === idx ? !v : v))); }; @@ -27,10 +37,10 @@ export default function CompressPdfPanel({ files = [], setDownloadUrl, setLoadin const formData = new FormData(); selectedFiles.forEach(file => formData.append("fileInput", file)); - formData.append("compressionLevel", compressionLevel); - formData.append("grayscale", grayscale); - formData.append("removeMetadata", removeMetadata); - formData.append("aggressive", aggressive); + formData.append("compressionLevel", compressionLevel.toString()); + formData.append("grayscale", grayscale.toString()); + formData.append("removeMetadata", removeMetadata.toString()); + formData.append("aggressive", aggressive.toString()); if (expectedSize) formData.append("expectedSize", expectedSize); try { @@ -39,7 +49,7 @@ export default function CompressPdfPanel({ files = [], setDownloadUrl, setLoadin body: formData, }); const blob = await res.blob(); - setDownloadUrl(URL.createObjectURL(blob)); + setDownloadUrl?.(URL.createObjectURL(blob)); } finally { setLocalLoading(false); setLoading?.(false); @@ -49,9 +59,9 @@ export default function CompressPdfPanel({ files = [], setDownloadUrl, setLoadin return ( - Select files to compress: - - {files.length === 0 && No files loaded.} + Select files to compress: + + {files.length === 0 && No files loaded.} {files.map((file, idx) => ( ))} - + Compression Level - + ); -} +}; + +export default CompressPdfPanel; diff --git a/frontend/src/tools/Merge.js b/frontend/src/tools/Merge.js deleted file mode 100644 index 6c7945894..000000000 --- a/frontend/src/tools/Merge.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, { useState, useEffect } from "react"; - -export default function MergePdfPanel({ files, setDownloadUrl }) { - const [selectedFiles, setSelectedFiles] = useState([]); - const [downloadUrl, setLocalDownloadUrl] = useState(null); // Local state for download URL - const [isLoading, setIsLoading] = useState(false); // Loading state - const [errorMessage, setErrorMessage] = useState(null); // Error message state - - // Sync selectedFiles with files whenever files change - useEffect(() => { - setSelectedFiles(files.map(() => true)); // Select all files by default - }, [files]); - - const handleMerge = async () => { - const filesToMerge = files.filter((_, index) => selectedFiles[index]); - - if (filesToMerge.length < 2) { - alert("Please select at least two PDFs to merge."); - return; - } - - const formData = new FormData(); - filesToMerge.forEach((file) => formData.append("fileInput", file)); // Use "fileInput" as the key - - setIsLoading(true); // Start loading - setErrorMessage(null); // Clear previous errors - - try { - const response = await fetch("/api/v1/general/merge-pdfs", { - method: "POST", - body: formData, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Failed to merge PDFs: ${errorText}`); - } - - const blob = await response.blob(); - const downloadUrl = URL.createObjectURL(blob); - setDownloadUrl(downloadUrl); // Pass to parent component - setLocalDownloadUrl(downloadUrl); // Store locally for download button - } catch (error) { - console.error("Error merging PDFs:", error); - setErrorMessage(error.message); // Set error message - } finally { - setIsLoading(false); // Stop loading - } - }; - - const handleCheckboxChange = (index) => { - setSelectedFiles((prevSelectedFiles) => - prevSelectedFiles.map((selected, i) => (i === index ? !selected : selected)) - ); - }; - - return ( -
-

Merge PDFs

-
    - {files.map((file, index) => ( -
  • - handleCheckboxChange(index)} - className="form-checkbox" - /> - {file.name} -
  • - ))} -
- {files.filter((_, index) => selectedFiles[index]).length < 2 && ( -

- Please select at least two PDFs to merge. -

- )} - - {errorMessage && ( -

- {errorMessage} -

- )} - {downloadUrl && ( - - Download Merged PDF - - )} -
- ); -} diff --git a/frontend/src/tools/Merge.tsx b/frontend/src/tools/Merge.tsx new file mode 100644 index 000000000..9cc969a0f --- /dev/null +++ b/frontend/src/tools/Merge.tsx @@ -0,0 +1,112 @@ +import React, { useState, useEffect } from "react"; +import { Paper, Button, Checkbox, Stack, Text, Group, Loader, Alert } from "@mantine/core"; + +export interface MergePdfPanelProps { + files: File[]; + setDownloadUrl: (url: string) => void; +} + +const MergePdfPanel: React.FC = ({ files, setDownloadUrl }) => { + const [selectedFiles, setSelectedFiles] = useState([]); + const [downloadUrl, setLocalDownloadUrl] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + useEffect(() => { + setSelectedFiles(files.map(() => true)); + }, [files]); + + const handleMerge = async () => { + const filesToMerge = files.filter((_, index) => selectedFiles[index]); + if (filesToMerge.length < 2) { + setErrorMessage("Please select at least two PDFs to merge."); + return; + } + + const formData = new FormData(); + filesToMerge.forEach((file) => formData.append("fileInput", file)); + + setIsLoading(true); + setErrorMessage(null); + + try { + const response = await fetch("/api/v1/general/merge-pdfs", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to merge PDFs: ${errorText}`); + } + + const blob = await response.blob(); + const url = URL.createObjectURL(blob); + setDownloadUrl(url); + setLocalDownloadUrl(url); + } catch (error: any) { + setErrorMessage(error.message || "Unknown error occurred."); + } finally { + setIsLoading(false); + } + }; + + const handleCheckboxChange = (index: number) => { + setSelectedFiles((prev) => + prev.map((selected, i) => (i === index ? !selected : selected)) + ); + }; + + const selectedCount = selectedFiles.filter(Boolean).length; + + return ( + + + Merge PDFs + + {files.map((file, index) => ( + + handleCheckboxChange(index)} + /> + {file.name} + + ))} + + {selectedCount < 2 && ( + + Please select at least two PDFs to merge. + + )} + + {errorMessage && ( + + {errorMessage} + + )} + {downloadUrl && ( + + )} + + + ); +}; + +export default MergePdfPanel; diff --git a/frontend/src/tools/Split.js b/frontend/src/tools/Split.js deleted file mode 100644 index 6dadabacd..000000000 --- a/frontend/src/tools/Split.js +++ /dev/null @@ -1,208 +0,0 @@ -import React, { useState } from "react"; -import axios from "axios"; -import { - Button, - Select, - TextInput, - Checkbox, - Notification, - Stack, -} from "@mantine/core"; -import DownloadIcon from "@mui/icons-material/Download"; - -export default function SplitPdfPanel({ file, downloadUrl, setDownloadUrl }) { - const [mode, setMode] = useState("byPages"); - const [pageNumbers, setPageNumbers] = useState(""); - - const [horizontalDivisions, setHorizontalDivisions] = useState("0"); - const [verticalDivisions, setVerticalDivisions] = useState("1"); - const [mergeSections, setMergeSections] = useState(false); - - const [splitType, setSplitType] = useState("size"); - const [splitValue, setSplitValue] = useState(""); - - const [bookmarkLevel, setBookmarkLevel] = useState("0"); - const [includeMetadata, setIncludeMetadata] = useState(false); - const [allowDuplicates, setAllowDuplicates] = useState(false); - - const [status, setStatus] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(null); - - const handleSubmit = async (e) => { - e.preventDefault(); - if (!file) { - setStatus("Please upload a PDF first."); - return; - } - - const formData = new FormData(); - formData.append("fileInput", file.file); - - let endpoint = ""; - - switch (mode) { - case "byPages": - formData.append("pageNumbers", pageNumbers); - endpoint = "/api/v1/general/split-pages"; - break; - case "bySections": - formData.append("horizontalDivisions", horizontalDivisions); - formData.append("verticalDivisions", verticalDivisions); - formData.append("merge", mergeSections); - endpoint = "/api/v1/general/split-pdf-by-sections"; - break; - case "bySizeOrCount": - formData.append("splitType", splitType === "size" ? 0 : splitType === "pages" ? 1 : 2); - formData.append("splitValue", splitValue); - endpoint = "/api/v1/general/split-by-size-or-count"; - break; - case "byChapters": - formData.append("bookmarkLevel", bookmarkLevel); - formData.append("includeMetadata", includeMetadata); - formData.append("allowDuplicates", allowDuplicates); - endpoint = "/api/v1/general/split-pdf-by-chapters"; - break; - default: - return; - } - - setStatus("Processing split..."); - setIsLoading(true); - setErrorMessage(null); - - try { - const response = await axios.post(endpoint, formData, { responseType: "blob" }); - const blob = new Blob([response.data], { type: "application/zip" }); - const url = window.URL.createObjectURL(blob); - setDownloadUrl(url); - setStatus("Download ready."); - } catch (error) { - console.error(error); - setErrorMessage(error.response?.data || "An error occurred while splitting the PDF."); - setStatus("Split failed."); - } finally { - setIsLoading(false); - } - }; - - return ( -
-

Split PDF

- - - setSplitValue(e.target.value)} - /> - - )} - - {mode === "byChapters" && ( - - setBookmarkLevel(e.target.value)} - /> - setIncludeMetadata(e.currentTarget.checked)} - /> - setAllowDuplicates(e.currentTarget.checked)} - /> - - )} - - - - {status &&

{status}

} - - {errorMessage && ( - setErrorMessage(null)}> - {errorMessage} - - )} - - {status === "Download ready." && downloadUrl && ( - - )} - -
- ); -} diff --git a/frontend/src/tools/Split.tsx b/frontend/src/tools/Split.tsx new file mode 100644 index 000000000..2e4d3a7c9 --- /dev/null +++ b/frontend/src/tools/Split.tsx @@ -0,0 +1,232 @@ +import React, { useState } from "react"; +import axios from "axios"; +import { + Button, + Select, + TextInput, + Checkbox, + Notification, + Stack, +} from "@mantine/core"; +import DownloadIcon from "@mui/icons-material/Download"; + +export interface SplitPdfPanelProps { + file: { file: File; url: string } | null; + downloadUrl?: string | null; + setDownloadUrl: (url: string | null) => void; + params: { + mode: string; + pages: string; + hDiv: string; + vDiv: string; + merge: boolean; + splitType: string; + splitValue: string; + bookmarkLevel: string; + includeMetadata: boolean; + allowDuplicates: boolean; + }; + updateParams: (newParams: Partial) => void; +} + +const SplitPdfPanel: React.FC = ({ + file, + downloadUrl, + setDownloadUrl, + params, + updateParams, +}) => { + const [status, setStatus] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const { + mode, pages, hDiv, vDiv, merge, + splitType, splitValue, bookmarkLevel, + includeMetadata, allowDuplicates + } = params; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!file) { + setStatus("Please upload a PDF first."); + return; + } + + const formData = new FormData(); + formData.append("fileInput", file.file); + + let endpoint = ""; + + switch (mode) { + case "byPages": + formData.append("pageNumbers", pages); + endpoint = "/api/v1/general/split-pages"; + break; + case "bySections": + formData.append("horizontalDivisions", hDiv); + formData.append("verticalDivisions", vDiv); + formData.append("merge", merge.toString()); + endpoint = "/api/v1/general/split-pdf-by-sections"; + break; + case "bySizeOrCount": + formData.append( + "splitType", + splitType === "size" ? "0" : splitType === "pages" ? "1" : "2" + ); + formData.append("splitValue", splitValue); + endpoint = "/api/v1/general/split-by-size-or-count"; + break; + case "byChapters": + formData.append("bookmarkLevel", bookmarkLevel); + formData.append("includeMetadata", includeMetadata.toString()); + formData.append("allowDuplicates", allowDuplicates.toString()); + endpoint = "/api/v1/general/split-pdf-by-chapters"; + break; + default: + return; + } + + setStatus("Processing split..."); + setIsLoading(true); + setErrorMessage(null); + + try { + const response = await axios.post(endpoint, formData, { responseType: "blob" }); + const blob = new Blob([response.data], { type: "application/zip" }); + const url = window.URL.createObjectURL(blob); + setDownloadUrl(url); + setStatus("Download ready."); + } catch (error: any) { + console.error(error); + setErrorMessage( + error.response?.data || "An error occurred while splitting the PDF." + ); + setStatus("Split failed."); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ + v && updateParams({ splitType: v })} + data={[ + { value: "size", label: "By Size" }, + { value: "pages", label: "By Page Count" }, + { value: "docs", label: "By Document Count" }, + ]} + /> + updateParams({ splitValue: e.target.value })} + /> + + )} + + {mode === "byChapters" && ( + + updateParams({ bookmarkLevel: e.target.value })} + /> + updateParams({ includeMetadata: e.currentTarget.checked })} + /> + updateParams({ allowDuplicates: e.currentTarget.checked })} + /> + + )} + + + + {status &&

{status}

} + + {errorMessage && ( + setErrorMessage(null)}> + {errorMessage} + + )} + + {status === "Download ready." && downloadUrl && ( + + )} + +
+ ); +}; + +export default SplitPdfPanel; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 000000000..bf86ee2b5 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,117 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "jsx": "react-jsx", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "esnext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "src", + "src/global.d.ts" + ] +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java b/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java index 95dbf0618..0fb2f2e8d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java @@ -181,7 +181,8 @@ public class AnalysisController { // Get permissions Map permissions = new HashMap<>(); - permissions.put("preventPrinting", !document.getCurrentAccessPermission().canPrint()); + permissions.put( + "preventPrinting", !document.getCurrentAccessPermission().canPrint()); permissions.put( "preventModify", !document.getCurrentAccessPermission().canModify()); permissions.put( diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index da4a77962..cbaa12a0c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -626,32 +626,32 @@ public class CompressController { // Scale factors for different optimization levels private double getScaleFactorForLevel(int optimizeLevel) { - return switch (optimizeLevel) { - case 3 -> 0.85; - case 4 -> 0.75; - case 5 -> 0.65; - case 6 -> 0.55; - case 7 -> 0.45; - case 8 -> 0.35; - case 9 -> 0.25; - case 10 -> 0.15; - default -> 1.0; - }; + return switch (optimizeLevel) { + case 3 -> 0.85; + case 4 -> 0.75; + case 5 -> 0.65; + case 6 -> 0.55; + case 7 -> 0.45; + case 8 -> 0.35; + case 9 -> 0.25; + case 10 -> 0.15; + default -> 1.0; + }; } // JPEG quality for different optimization levels private float getJpegQualityForLevel(int optimizeLevel) { - return switch (optimizeLevel) { - case 3 -> 0.85f; - case 4 -> 0.80f; - case 5 -> 0.75f; - case 6 -> 0.70f; - case 7 -> 0.60f; - case 8 -> 0.50f; - case 9 -> 0.35f; - case 10 -> 0.2f; - default -> 0.7f; - }; + return switch (optimizeLevel) { + case 3 -> 0.85f; + case 4 -> 0.80f; + case 5 -> 0.75f; + case 6 -> 0.70f; + case 7 -> 0.60f; + case 8 -> 0.50f; + case 9 -> 0.35f; + case 10 -> 0.2f; + default -> 0.7f; + }; } @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index 95049b0bd..ef82a2942 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -622,8 +622,8 @@ public class GetInfoOnPDF { permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument())); permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent())); permissionsNode.put( - "Extracting for accessibility", - getPermissionState(ap.canExtractForAccessibility())); + "Extracting for accessibility", + getPermissionState(ap.canExtractForAccessibility())); permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm())); permissionsNode.put("Modifying", getPermissionState(ap.canModify())); permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations())); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java index df5e2499e..6e75cdf3b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java @@ -180,7 +180,6 @@ public class RedactController { } } - private List getPageNumbers(ManualRedactPdfRequest request, int pagesCount) { String pageNumbersInput = request.getPageNumbers(); String[] parsedPageNumbers = diff --git a/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java index e92ae7c5a..d13122571 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/RedactPdfRequest.java @@ -23,7 +23,9 @@ public class RedactPdfRequest extends PDFFile { @Schema(description = "Whether to use whole word search", defaultValue = "false") private boolean wholeWordSearch; - @Schema(description = "Hexadecimal color code for redaction, e.g. #FF0000 or 000000", defaultValue = "#000000") + @Schema( + description = "Hexadecimal color code for redaction, e.g. #FF0000 or 000000", + defaultValue = "#000000") private String redactColor = "#000000"; @Schema(description = "Custom padding for redaction", type = "number")