From a339f71116c3de99da26608d70221775db564da9 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:35:09 +0100 Subject: [PATCH] Overlay PDF tool (#4620) # Description of Changes - Added the OverlayPDF tool --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../public/locales/en-GB/translation.json | 50 ++++- .../public/locales/en-US/translation.json | 45 ++++- frontend/src/components/FileManager.tsx | 1 + .../src/components/shared/FilePickerModal.tsx | 1 + .../OverlayPdfsSettings.module.css | 50 +++++ .../tools/overlayPdfs/OverlayPdfsSettings.tsx | 178 ++++++++++++++++++ .../components/tooltips/useOverlayPdfsTips.ts | 56 ++++++ .../src/data/useTranslatedToolRegistry.tsx | 20 +- .../overlayPdfs/useOverlayPdfsOperation.ts | 46 +++++ .../overlayPdfs/useOverlayPdfsParameters.ts | 37 ++++ frontend/src/tools/OverlayPdfs.tsx | 60 ++++++ 11 files changed, 525 insertions(+), 19 deletions(-) create mode 100644 frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.module.css create mode 100644 frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx create mode 100644 frontend/src/components/tooltips/useOverlayPdfsTips.ts create mode 100644 frontend/src/hooks/tools/overlayPdfs/useOverlayPdfsOperation.ts create mode 100644 frontend/src/hooks/tools/overlayPdfs/useOverlayPdfsParameters.ts create mode 100644 frontend/src/tools/OverlayPdfs.tsx diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index d633c5163..81f4fc7e4 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -598,11 +598,6 @@ "title": "Redact", "desc": "Redacts (blacks out) a PDF based on selected text, drawn shapes and/or selected page(s)" }, - "overlayPdfs": { - "tags": "overlay,combine,stack", - "title": "Overlay PDFs", - "desc": "Overlays PDFs on-top of another PDF" - }, "splitBySections": { "tags": "split,sections,divide", "title": "Split PDF by Sections", @@ -2550,11 +2545,15 @@ "overlay-pdfs": { "tags": "Overlay", "header": "Overlay PDF Files", + "title": "Overlay PDFs", + "desc": "Overlay one PDF on top of another", "baseFile": { "label": "Select Base PDF File" }, "overlayFiles": { - "label": "Select Overlay PDF Files" + "label": "Select Overlay PDF Files", + "placeholder": "Choose PDF(s)...", + "addMore": "Add more PDFs..." }, "mode": { "label": "Select Overlay Mode", @@ -2564,14 +2563,49 @@ }, "counts": { "label": "Overlay Counts (for Fixed Repeat Mode)", - "placeholder": "Enter comma-separated counts (e.g., 2,3,1)" + "placeholder": "Enter comma-separated counts (e.g., 2,3,1)", + "item": "Count for file" }, "position": { "label": "Select Overlay Position", "foreground": "Foreground", "background": "Background" }, - "submit": "Submit" + "submit": "Submit", + "settings": { + "title": "Settings" + }, + "results": { + "title": "Overlay Results" + }, + "tooltip": { + "header": { + "title": "Overlay PDFs Overview" + }, + "description": { + "title": "Description", + "text": "Combine a base PDF with one or more overlay PDFs. Overlays can be applied page-by-page in different modes and placed in the foreground or background." + }, + "mode": { + "title": "Overlay Mode", + "text": "Choose how to distribute overlay pages across the base PDF pages.", + "sequential": "Sequential Overlay: Use pages from the first overlay PDF until it ends, then move to the next.", + "interleaved": "Interleaved Overlay: Take one page from each overlay in turn.", + "fixedRepeat": "Fixed Repeat Overlay: Take a set number of pages from each overlay before moving to the next. Use Counts to set the numbers." + }, + "position": { + "title": "Overlay Position", + "text": "Foreground places the overlay on top of the page. Background places it behind." + }, + "overlayFiles": { + "title": "Overlay Files", + "text": "Select one or more PDFs to overlay on the base. The order of these files affects how pages are applied in Sequential and Fixed Repeat modes." + }, + "counts": { + "title": "Counts (Fixed Repeat only)", + "text": "Provide a positive number for each overlay file showing how many pages to take before moving to the next. Required when mode is Fixed Repeat." + } + } }, "split-by-sections": { "tags": "Section Split, Divide, Customize,Customise", diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index b4ac8302d..ce4d8db0b 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -1521,11 +1521,15 @@ "overlay-pdfs": { "tags": "Overlay", "header": "Overlay PDF Files", + "title": "Overlay PDFs", + "desc": "Overlay one PDF on top of another", "baseFile": { "label": "Select Base PDF File" }, "overlayFiles": { - "label": "Select Overlay PDF Files" + "label": "Select Overlay PDF Files", + "placeholder": "Choose PDF(s)...", + "addMore": "Add more PDFs..." }, "mode": { "label": "Select Overlay Mode", @@ -1535,14 +1539,49 @@ }, "counts": { "label": "Overlay Counts (for Fixed Repeat Mode)", - "placeholder": "Enter comma-separated counts (e.g., 2,3,1)" + "placeholder": "Enter comma-separated counts (e.g., 2,3,1)", + "item": "Count for file" }, "position": { "label": "Select Overlay Position", "foreground": "Foreground", "background": "Background" }, - "submit": "Submit" + "submit": "Submit", + "settings": { + "title": "Settings" + }, + "results": { + "title": "Overlay Results" + }, + "tooltip": { + "header": { + "title": "Overlay PDFs Overview" + }, + "description": { + "title": "Description", + "text": "Combine a base PDF with one or more overlay PDFs. Overlays can be applied page-by-page in different modes and placed in the foreground or background." + }, + "mode": { + "title": "Overlay Mode", + "text": "Choose how to distribute overlay pages across the base PDF pages.", + "sequential": "Sequential Overlay: Use pages from the first overlay PDF until it ends, then move to the next.", + "interleaved": "Interleaved Overlay: Take one page from each overlay in turn.", + "fixedRepeat": "Fixed Repeat Overlay: Take a set number of pages from each overlay before moving to the next. Use Counts to set the numbers." + }, + "position": { + "title": "Overlay Position", + "text": "Foreground places the overlay on top of the page. Background places it behind." + }, + "overlayFiles": { + "title": "Overlay Files", + "text": "Select one or more PDFs to overlay on the base. The order of these files affects how pages are applied in Sequential and Fixed Repeat modes." + }, + "counts": { + "title": "Counts (Fixed Repeat only)", + "text": "Provide a positive number for each overlay file showing how many pages to take before moving to the next. Required when mode is Fixed Repeat." + } + } }, "split-by-sections": { "tags": "Section Split, Divide, Customize", diff --git a/frontend/src/components/FileManager.tsx b/frontend/src/components/FileManager.tsx index 96185902b..2aca5e648 100644 --- a/frontend/src/components/FileManager.tsx +++ b/frontend/src/components/FileManager.tsx @@ -125,6 +125,7 @@ const FileManager: React.FC = ({ selectedTool }) => { radius="md" className="overflow-hidden p-0" withCloseButton={false} + zIndex={1100} styles={{ content: { position: 'relative', diff --git a/frontend/src/components/shared/FilePickerModal.tsx b/frontend/src/components/shared/FilePickerModal.tsx index 9bb2694a1..bb571b589 100644 --- a/frontend/src/components/shared/FilePickerModal.tsx +++ b/frontend/src/components/shared/FilePickerModal.tsx @@ -125,6 +125,7 @@ const FilePickerModal = ({ title={t("fileUpload.selectFromStorage", "Select Files from Storage")} size="lg" scrollAreaComponent={ScrollArea.Autosize} + zIndex={1100} > {storedFiles.length === 0 ? ( diff --git a/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.module.css b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.module.css new file mode 100644 index 000000000..b6efb3bbc --- /dev/null +++ b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.module.css @@ -0,0 +1,50 @@ +.fileListContainer { + max-height: 16.25rem; + overflow-y: auto; + overflow-x: hidden; + width: 100%; +} + +.fileItem { + border: 1px solid var(--mantine-color-gray-3); + border-radius: var(--mantine-radius-sm); + align-items: center; + width: 100%; +} + +.fileNameContainer { + flex: 1; + min-width: 0; +} + +.fileName { + font-size: var(--mantine-font-size-sm); + font-weight: 400; + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + white-space: normal; + word-break: break-word; +} + +.fileSize { + flex-shrink: 0; +} + +.removeButton { + flex-shrink: 0; +} + +.countLabel { + width: 140px; + flex-shrink: 0; +} + +.fileGroup { + flex: 1; + min-width: 0; + align-items: center; +} diff --git a/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx new file mode 100644 index 000000000..43ba64db5 --- /dev/null +++ b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx @@ -0,0 +1,178 @@ +import { Stack, Text, Group, Select, SegmentedControl, NumberInput, Button, ActionIcon, Divider } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { type OverlayPdfsParameters, type OverlayMode } from '../../../hooks/tools/overlayPdfs/useOverlayPdfsParameters'; +import LocalIcon from '../../shared/LocalIcon'; +import { useFilesModalContext } from '../../../contexts/FilesModalContext'; +import styles from './OverlayPdfsSettings.module.css'; + +interface OverlayPdfsSettingsProps { + parameters: OverlayPdfsParameters; + onParameterChange: (key: K, value: OverlayPdfsParameters[K]) => void; + disabled?: boolean; +} + +export default function OverlayPdfsSettings({ parameters, onParameterChange, disabled = false }: OverlayPdfsSettingsProps) { + const { t } = useTranslation(); + const { openFilesModal } = useFilesModalContext(); + + const handleOverlayFilesChange = (files: File[]) => { + onParameterChange('overlayFiles', files); + // Reset counts to match number of files if in FixedRepeatOverlay + if (parameters.overlayMode === 'FixedRepeatOverlay') { + const nextCounts = files.map((_, i) => parameters.counts[i] && parameters.counts[i] > 0 ? parameters.counts[i] : 1); + onParameterChange('counts', nextCounts); + } + }; + + const handleModeChange = (mode: OverlayMode) => { + onParameterChange('overlayMode', mode); + if (mode !== 'FixedRepeatOverlay') { + onParameterChange('counts', []); + } else if (parameters.overlayFiles?.length > 0) { + onParameterChange('counts', parameters.overlayFiles.map((_, i) => parameters.counts[i] && parameters.counts[i] > 0 ? parameters.counts[i] : 1)); + } + }; + + const handleOpenOverlayFilesModal = () => { + if (disabled) return; + openFilesModal({ + customHandler: (files: File[]) => { + handleOverlayFilesChange([...(parameters.overlayFiles || []), ...files]); + } + }); + }; + + return ( + + + + {t('overlay-pdfs.mode.label', 'Overlay Mode')} +