V2 PDF to large page (#4236)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## 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.
This commit is contained in:
Anthony Stirling 2025-08-20 11:38:41 +01:00 committed by GitHub
parent 857b2b5c53
commit d1cb3f0b30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 180 additions and 4 deletions

View File

@ -1618,7 +1618,18 @@
"pdfToSinglePage": { "pdfToSinglePage": {
"title": "PDF To Single Page", "title": "PDF To Single Page",
"header": "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": { "pageExtracter": {
"title": "Extract Pages", "title": "Extract Pages",

View File

@ -1345,7 +1345,18 @@
"pdfToSinglePage": { "pdfToSinglePage": {
"title": "PDF To Single Page", "title": "PDF To Single Page",
"header": "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": { "pageExtracter": {
"title": "Extract Pages", "title": "Extract Pages",

View File

@ -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: <K extends keyof SingleLargePageParameters>(parameter: K, value: SingleLargePageParameters[K]) => void;
disabled?: boolean;
}
const SingleLargePageSettings: React.FC<SingleLargePageSettingsProps> = ({
parameters,
onParameterChange,
disabled = false
}) => {
const { t } = useTranslation();
return (
<div className="single-large-page-settings">
<p className="text-muted">
{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.')}
</p>
</div>
);
};
export default SingleLargePageSettings;

View File

@ -11,10 +11,12 @@ import RemovePassword from '../tools/RemovePassword';
import { SubcategoryId, ToolCategory, ToolRegistry } from './toolsTaxonomy'; import { SubcategoryId, ToolCategory, ToolRegistry } from './toolsTaxonomy';
import AddWatermark from '../tools/AddWatermark'; import AddWatermark from '../tools/AddWatermark';
import Repair from '../tools/Repair'; import Repair from '../tools/Repair';
import SingleLargePage from '../tools/SingleLargePage';
import UnlockPdfForms from '../tools/UnlockPdfForms'; import UnlockPdfForms from '../tools/UnlockPdfForms';
import RemoveCertificateSign from '../tools/RemoveCertificateSign'; import RemoveCertificateSign from '../tools/RemoveCertificateSign';
// Hook to get the translated tool registry // Hook to get the translated tool registry
export function useFlatToolRegistry(): ToolRegistry { export function useFlatToolRegistry(): ToolRegistry {
const { t } = useTranslation(); const { t } = useTranslation();
@ -236,11 +238,13 @@ export function useFlatToolRegistry(): ToolRegistry {
"single-large-page": { "single-large-page": {
icon: <span className="material-symbols-rounded">looks_one</span>, icon: <span className="material-symbols-rounded">looks_one</span>,
name: t("home.PdfToSinglePage.title", "PDF to Single Large Page"), name: t("home.PdfToSinglePage.title", "PDF to Single Large Page"),
component: null, component: SingleLargePage,
view: "format", view: "format",
description: t("home.PdfToSinglePage.desc", "Merges all PDF pages into one large single page"), description: t("home.PdfToSinglePage.desc", "Merges all PDF pages into one large single page"),
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.PAGE_FORMATTING subcategory: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1,
endpoints: ["pdf-to-single-page"]
}, },
"add-attachments": { "add-attachments": {
icon: <span className="material-symbols-rounded">attachment</span>, icon: <span className="material-symbols-rounded">attachment</span>,

View File

@ -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<SingleLargePageParameters>({
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.'))
});
};

View File

@ -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<SingleLargePageParameters>;
export const useSingleLargePageParameters = (): SingleLargePageParametersHook => {
return useBaseParameters({
defaultParameters,
endpointName: 'pdf-to-single-page',
});
};

View File

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

View File

@ -19,6 +19,7 @@ export type ModeType =
| 'changePermissions' | 'changePermissions'
| 'watermark' | 'watermark'
| 'removePassword' | 'removePassword'
| 'single-large-page'
| 'repair' | 'repair'
| 'unlockPdfForms' | 'unlockPdfForms'
| 'removeCertificateSign'; | 'removeCertificateSign';