mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
add multi page layout tool
This commit is contained in:
parent
fd52dc0226
commit
37f2341e0f
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
37
frontend/src/components/tools/pageLayout/constants.ts
Normal file
37
frontend/src/components/tools/pageLayout/constants.ts
Normal 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).')
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
@ -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: {
|
||||||
|
@ -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.')
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -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',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
57
frontend/src/tools/PageLayout.tsx
Normal file
57
frontend/src/tools/PageLayout.tsx
Normal 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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user