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';