From d1cb3f0b300bbedbf786f82fbad6171b4e60f635 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:38:41 +0100 Subject: [PATCH] V2 PDF to large page (#4236) # Description of Changes --- ## 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 | 13 ++- .../public/locales/en-US/translation.json | 13 ++- .../SingleLargePageSettings.tsx | 27 +++++++ .../src/data/useTranslatedToolRegistry.tsx | 8 +- .../useSingleLargePageOperation.ts | 23 ++++++ .../useSingleLargePageParameters.ts | 19 +++++ frontend/src/tools/SingleLargePage.tsx | 80 +++++++++++++++++++ frontend/src/types/fileContext.ts | 1 + 8 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/tools/singleLargePage/SingleLargePageSettings.tsx create mode 100644 frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts create mode 100644 frontend/src/hooks/tools/singleLargePage/useSingleLargePageParameters.ts create mode 100644 frontend/src/tools/SingleLargePage.tsx diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index e3bb3c0c6..333f6ca8b 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1618,7 +1618,18 @@ "pdfToSinglePage": { "title": "PDF To Single Page", "header": "PDF To Single Page", - "submit": "Convert To Single Page" + "submit": "Convert To Single Page", + "description": "This tool will merge all pages of your PDF into one large single page. The width will remain the same as the original pages, but the height will be the sum of all page heights.", + "filenamePrefix": "single_page", + "files": { + "placeholder": "Select a PDF file in the main view to get started" + }, + "error": { + "failed": "An error occurred whilst converting to single page." + }, + "results": { + "title": "Single Page Results" + } }, "pageExtracter": { "title": "Extract Pages", diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index 541c19fb5..0638d78d3 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -1345,7 +1345,18 @@ "pdfToSinglePage": { "title": "PDF To Single Page", "header": "PDF To Single Page", - "submit": "Convert To Single Page" + "submit": "Convert To Single Page", + "description": "This tool will merge all pages of your PDF into one large single page. The width will remain the same as the original pages, but the height will be the sum of all page heights.", + "filenamePrefix": "single_page", + "files": { + "placeholder": "Select a PDF file in the main view to get started" + }, + "error": { + "failed": "An error occurred while converting to single page." + }, + "results": { + "title": "Single Page Results" + } }, "pageExtracter": { "title": "Extract Pages", diff --git a/frontend/src/components/tools/singleLargePage/SingleLargePageSettings.tsx b/frontend/src/components/tools/singleLargePage/SingleLargePageSettings.tsx new file mode 100644 index 000000000..87dfef926 --- /dev/null +++ b/frontend/src/components/tools/singleLargePage/SingleLargePageSettings.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SingleLargePageParameters } from '../../../hooks/tools/singleLargePage/useSingleLargePageParameters'; + +interface SingleLargePageSettingsProps { + parameters: SingleLargePageParameters; + onParameterChange: (parameter: K, value: SingleLargePageParameters[K]) => void; + disabled?: boolean; +} + +const SingleLargePageSettings: React.FC = ({ + parameters, + onParameterChange, + disabled = false +}) => { + const { t } = useTranslation(); + + return ( +
+

+ {t('pdfToSinglePage.description', 'This tool will merge all pages of your PDF into one large single page. The width will remain the same as the original pages, but the height will be the sum of all page heights.')} +

+
+ ); +}; + +export default SingleLargePageSettings; \ No newline at end of file diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index db6aca089..359cf6d35 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -11,10 +11,12 @@ import RemovePassword from '../tools/RemovePassword'; import { SubcategoryId, ToolCategory, ToolRegistry } from './toolsTaxonomy'; import AddWatermark from '../tools/AddWatermark'; import Repair from '../tools/Repair'; +import SingleLargePage from '../tools/SingleLargePage'; import UnlockPdfForms from '../tools/UnlockPdfForms'; import RemoveCertificateSign from '../tools/RemoveCertificateSign'; + // Hook to get the translated tool registry export function useFlatToolRegistry(): ToolRegistry { const { t } = useTranslation(); @@ -236,11 +238,13 @@ export function useFlatToolRegistry(): ToolRegistry { "single-large-page": { icon: looks_one, name: t("home.PdfToSinglePage.title", "PDF to Single Large Page"), - component: null, + component: SingleLargePage, view: "format", description: t("home.PdfToSinglePage.desc", "Merges all PDF pages into one large single page"), category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + subcategory: SubcategoryId.PAGE_FORMATTING, + maxFiles: -1, + endpoints: ["pdf-to-single-page"] }, "add-attachments": { icon: attachment, diff --git a/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts new file mode 100644 index 000000000..e73944864 --- /dev/null +++ b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts @@ -0,0 +1,23 @@ +import { useTranslation } from 'react-i18next'; +import { useToolOperation } from '../shared/useToolOperation'; +import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; +import { SingleLargePageParameters } from './useSingleLargePageParameters'; + +export const useSingleLargePageOperation = () => { + const { t } = useTranslation(); + + const buildFormData = (parameters: SingleLargePageParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + return formData; + }; + + return useToolOperation({ + operationType: 'singleLargePage', + endpoint: '/api/v1/general/pdf-to-single-page', + buildFormData, + filePrefix: t('pdfToSinglePage.filenamePrefix', 'single_page') + '_', + multiFileEndpoint: false, + getErrorMessage: createStandardErrorHandler(t('pdfToSinglePage.error.failed', 'An error occurred while converting to single page.')) + }); +}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/singleLargePage/useSingleLargePageParameters.ts b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageParameters.ts new file mode 100644 index 000000000..df401b1a4 --- /dev/null +++ b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageParameters.ts @@ -0,0 +1,19 @@ +import { BaseParameters } from '../../../types/parameters'; +import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters'; + +export interface SingleLargePageParameters extends BaseParameters { + // Extends BaseParameters - ready for future parameter additions if needed +} + +export const defaultParameters: SingleLargePageParameters = { + // No parameters needed +}; + +export type SingleLargePageParametersHook = BaseParametersHook; + +export const useSingleLargePageParameters = (): SingleLargePageParametersHook => { + return useBaseParameters({ + defaultParameters, + endpointName: 'pdf-to-single-page', + }); +}; \ No newline at end of file diff --git a/frontend/src/tools/SingleLargePage.tsx b/frontend/src/tools/SingleLargePage.tsx new file mode 100644 index 000000000..0c4fb96db --- /dev/null +++ b/frontend/src/tools/SingleLargePage.tsx @@ -0,0 +1,80 @@ +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useEndpointEnabled } from "../hooks/useEndpointConfig"; +import { useFileContext } from "../contexts/FileContext"; +import { useToolFileSelection } from "../contexts/FileSelectionContext"; + +import { createToolFlow } from "../components/tools/shared/createToolFlow"; + +import { useSingleLargePageParameters } from "../hooks/tools/singleLargePage/useSingleLargePageParameters"; +import { useSingleLargePageOperation } from "../hooks/tools/singleLargePage/useSingleLargePageOperation"; +import { BaseToolProps } from "../types/tool"; + +const SingleLargePage = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { + const { t } = useTranslation(); + const { setCurrentMode } = useFileContext(); + const { selectedFiles } = useToolFileSelection(); + + const singleLargePageParams = useSingleLargePageParameters(); + const singleLargePageOperation = useSingleLargePageOperation(); + + // Endpoint validation + const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(singleLargePageParams.getEndpointName()); + + useEffect(() => { + singleLargePageOperation.resetResults(); + onPreviewFile?.(null); + }, [singleLargePageParams.parameters]); + + const handleConvert = async () => { + try { + await singleLargePageOperation.executeOperation(singleLargePageParams.parameters, selectedFiles); + if (singleLargePageOperation.files && onComplete) { + onComplete(singleLargePageOperation.files); + } + } catch (error) { + if (onError) { + onError(error instanceof Error ? error.message : t("pdfToSinglePage.error.failed", "Single large page operation failed")); + } + } + }; + + const handleThumbnailClick = (file: File) => { + onPreviewFile?.(file); + sessionStorage.setItem("previousMode", "single-large-page"); + setCurrentMode("viewer"); + }; + + const handleSettingsReset = () => { + singleLargePageOperation.resetResults(); + onPreviewFile?.(null); + setCurrentMode("single-large-page"); + }; + + const hasFiles = selectedFiles.length > 0; + const hasResults = singleLargePageOperation.files.length > 0 || singleLargePageOperation.downloadUrl !== null; + + return createToolFlow({ + files: { + selectedFiles, + isCollapsed: hasFiles || hasResults, + placeholder: t("pdfToSinglePage.files.placeholder", "Select a PDF file in the main view to get started"), + }, + steps: [], + executeButton: { + text: t("pdfToSinglePage.submit", "Convert To Single Page"), + isVisible: !hasResults, + loadingText: t("loading"), + onClick: handleConvert, + disabled: !singleLargePageParams.validateParameters() || !hasFiles || !endpointEnabled, + }, + review: { + isVisible: hasResults, + operation: singleLargePageOperation, + title: t("pdfToSinglePage.results.title", "Single Page Results"), + onFileClick: handleThumbnailClick, + }, + }); +}; + +export default SingleLargePage; \ No newline at end of file diff --git a/frontend/src/types/fileContext.ts b/frontend/src/types/fileContext.ts index 6e2a2ef93..d9dde75b7 100644 --- a/frontend/src/types/fileContext.ts +++ b/frontend/src/types/fileContext.ts @@ -19,6 +19,7 @@ export type ModeType = | 'changePermissions' | 'watermark' | 'removePassword' + | 'single-large-page' | 'repair' | 'unlockPdfForms' | 'removeCertificateSign';