add multi page layout tool

This commit is contained in:
EthanHealy01 2025-09-26 01:53:34 +01:00
parent fd52dc0226
commit 37f2341e0f
6 changed files with 217 additions and 2 deletions

View File

@ -0,0 +1,62 @@
import React from 'react';
import { Divider, Select, Stack, Switch } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { PageLayoutParameters } from '../../../hooks/tools/pageLayout/usePageLayoutParameters';
import { getPagesPerSheetOptions } from './constants';
export default function PageLayoutSettings({
parameters,
onParameterChange,
disabled,
}: {
parameters: PageLayoutParameters;
onParameterChange: <K extends keyof PageLayoutParameters>(
key: K,
value: PageLayoutParameters[K]
) => void;
disabled?: boolean;
}) {
const { t } = useTranslation();
const options = getPagesPerSheetOptions(t);
const selected = options.find((o) => o.value === parameters.pagesPerSheet) || options[0];
return (
<Stack gap="sm">
<Select
label={t('pageLayout.pagesPerSheet', 'Pages per sheet:')}
data={options.map(o => ({ value: String(o.value), label: o.label }))}
value={String(parameters.pagesPerSheet)}
onChange={(v) => onParameterChange('pagesPerSheet', Number(v))}
disabled={disabled}
/>
{selected && (
<div
style={{
backgroundColor: 'var(--information-text-bg)',
color: 'var(--information-text-color)',
padding: '8px 12px',
borderRadius: '8px',
marginTop: '4px',
fontSize: '0.75rem',
textAlign: 'center',
}}
>
{selected.description}
</div>
)}
<Divider />
<Switch
checked={parameters.addBorder}
onChange={(e) => onParameterChange('addBorder', e.currentTarget.checked)}
label={t('pageLayout.addBorder', 'Add Borders')}
disabled={disabled}
/>
</Stack>
);
}

View File

@ -0,0 +1,37 @@
import { TFunction } from 'i18next';
export type PagesPerSheetOption = {
value: number;
label: string;
description: string;
};
export const getPagesPerSheetOptions = (t: TFunction): PagesPerSheetOption[] => [
{
value: 2,
label: '2',
description: t('pageLayout.desc.2', 'Place 2 pages side-by-side on a single sheet.')
},
{
value: 3,
label: '3',
description: t('pageLayout.desc.3', 'Place 3 pages on a single sheet in a single row.')
},
{
value: 4,
label: '4',
description: t('pageLayout.desc.4', 'Place 4 pages on a single sheet (2 × 2 grid).')
},
{
value: 9,
label: '9',
description: t('pageLayout.desc.9', 'Place 9 pages on a single sheet (3 × 3 grid).')
},
{
value: 16,
label: '16',
description: t('pageLayout.desc.16', 'Place 16 pages on a single sheet (4 × 4 grid).')
},
];

View File

@ -19,6 +19,7 @@ import Merge from '../tools/Merge';
import Repair from "../tools/Repair"; import Repair from "../tools/Repair";
import AutoRename from "../tools/AutoRename"; import AutoRename from "../tools/AutoRename";
import SingleLargePage from "../tools/SingleLargePage"; import SingleLargePage from "../tools/SingleLargePage";
import PageLayout from "../tools/PageLayout";
import UnlockPdfForms from "../tools/UnlockPdfForms"; import UnlockPdfForms from "../tools/UnlockPdfForms";
import RemoveCertificateSign from "../tools/RemoveCertificateSign"; import RemoveCertificateSign from "../tools/RemoveCertificateSign";
import CertSign from "../tools/CertSign"; import CertSign from "../tools/CertSign";
@ -417,11 +418,13 @@ export function useFlatToolRegistry(): ToolRegistry {
pageLayout: { pageLayout: {
icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />, icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pageLayout.title", "Multi-Page Layout"), name: t("home.pageLayout.title", "Multi-Page Layout"),
component: null, component: PageLayout,
description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"), description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"),
categoryId: ToolCategoryId.STANDARD_TOOLS, categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING, subcategoryId: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1,
endpoints: ["multi-page-layout"],
settingsComponent: React.lazy(() => import("../components/tools/pageLayout/PageLayoutSettings")),
synonyms: getSynonyms(t, "pageLayout") synonyms: getSynonyms(t, "pageLayout")
}, },
bookletImposition: { bookletImposition: {

View File

@ -0,0 +1,33 @@
import { useTranslation } from 'react-i18next';
import { ToolType, useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { PageLayoutParameters, defaultParameters } from './usePageLayoutParameters';
export const buildPageLayoutFormData = (parameters: PageLayoutParameters, file: File): FormData => {
const formData = new FormData();
formData.append('fileInput', file);
formData.append('pagesPerSheet', String(parameters.pagesPerSheet));
formData.append('addBorder', String(parameters.addBorder));
return formData;
};
export const pageLayoutOperationConfig = {
toolType: ToolType.singleFile,
buildFormData: buildPageLayoutFormData,
operationType: 'pageLayout',
endpoint: '/api/v1/general/multi-page-layout',
defaultParameters,
} as const;
export const usePageLayoutOperation = () => {
const { t } = useTranslation();
return useToolOperation<PageLayoutParameters>({
...pageLayoutOperationConfig,
getErrorMessage: createStandardErrorHandler(
t('pageLayout.error.failed', 'An error occurred while creating the multi-page layout.')
)
});
};

View File

@ -0,0 +1,23 @@
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface PageLayoutParameters extends BaseParameters {
pagesPerSheet: number;
addBorder: boolean;
}
export const defaultParameters: PageLayoutParameters = {
pagesPerSheet: 4,
addBorder: false,
};
export type PageLayoutParametersHook = BaseParametersHook<PageLayoutParameters>;
export const usePageLayoutParameters = (): PageLayoutParametersHook => {
return useBaseParameters<PageLayoutParameters>({
defaultParameters,
endpointName: 'multi-page-layout',
});
};

View File

@ -0,0 +1,57 @@
import { useTranslation } from 'react-i18next';
import { createToolFlow } from '../components/tools/shared/createToolFlow';
import { useBaseTool } from '../hooks/tools/shared/useBaseTool';
import { BaseToolProps, ToolComponent } from '../types/tool';
import { usePageLayoutParameters } from '../hooks/tools/pageLayout/usePageLayoutParameters';
import { usePageLayoutOperation } from '../hooks/tools/pageLayout/usePageLayoutOperation';
import PageLayoutSettings from '../components/tools/pageLayout/PageLayoutSettings';
const PageLayout = (props: BaseToolProps) => {
const { t } = useTranslation();
const base = useBaseTool(
'pageLayout',
usePageLayoutParameters,
usePageLayoutOperation,
props
);
return createToolFlow({
files: {
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: 'Settings',
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
content: (
<PageLayoutSettings
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t('pageLayout.submit', 'Create Layout'),
isVisible: !base.hasResults,
loadingText: t('loading'),
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: base.hasResults,
operation: base.operation,
title: t('pageLayout.title', 'Multi Page Layout Results'),
onFileClick: base.handleThumbnailClick,
onUndo: base.handleUndo,
},
});
};
export default PageLayout as ToolComponent;