mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
add the reorganize pages tool
This commit is contained in:
parent
fd52dc0226
commit
80d0900757
@ -959,7 +959,7 @@
|
|||||||
"header": "PDF Page Organiser",
|
"header": "PDF Page Organiser",
|
||||||
"submit": "Rearrange Pages",
|
"submit": "Rearrange Pages",
|
||||||
"mode": {
|
"mode": {
|
||||||
"_value": "Mode",
|
"_value": "Organization mode",
|
||||||
"1": "Custom Page Order",
|
"1": "Custom Page Order",
|
||||||
"2": "Reverse Order",
|
"2": "Reverse Order",
|
||||||
"3": "Duplex Sort",
|
"3": "Duplex Sort",
|
||||||
@ -972,6 +972,19 @@
|
|||||||
"10": "Odd-Even Merge",
|
"10": "Odd-Even Merge",
|
||||||
"11": "Duplicate all pages"
|
"11": "Duplicate all pages"
|
||||||
},
|
},
|
||||||
|
"desc": {
|
||||||
|
"CUSTOM": "Use a custom sequence of page numbers or expressions to define a new order.",
|
||||||
|
"REVERSE_ORDER": "Flip the document so the last page becomes first and so on.",
|
||||||
|
"DUPLEX_SORT": "Interleave fronts then backs as if a duplex scanner scanned all fronts, then all backs (1, n, 2, n-1, …).",
|
||||||
|
"BOOKLET_SORT": "Arrange pages for booklet printing (last, first, second, second last, …).",
|
||||||
|
"SIDE_STITCH_BOOKLET_SORT": "Arrange pages for side‑stitch booklet printing (optimised for binding on the side).",
|
||||||
|
"ODD_EVEN_SPLIT": "Split the document into two outputs: all odd pages and all even pages.",
|
||||||
|
"ODD_EVEN_MERGE": "Merge two PDFs by alternating pages: odd from the first, even from the second.",
|
||||||
|
"DUPLICATE": "Duplicate each page according to the custom order count (e.g., 4 duplicates each page 4×).",
|
||||||
|
"REMOVE_FIRST": "Remove the first page from the document.",
|
||||||
|
"REMOVE_LAST": "Remove the last page from the document.",
|
||||||
|
"REMOVE_FIRST_AND_LAST": "Remove both the first and last pages from the document."
|
||||||
|
},
|
||||||
"placeholder": "(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)"
|
"placeholder": "(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)"
|
||||||
},
|
},
|
||||||
"addImage": {
|
"addImage": {
|
||||||
|
@ -769,7 +769,7 @@
|
|||||||
"header": "PDF Page Organizer",
|
"header": "PDF Page Organizer",
|
||||||
"submit": "Rearrange Pages",
|
"submit": "Rearrange Pages",
|
||||||
"mode": {
|
"mode": {
|
||||||
"_value": "Mode",
|
"_value": "Organization mode",
|
||||||
"1": "Custom Page Order",
|
"1": "Custom Page Order",
|
||||||
"2": "Reverse Order",
|
"2": "Reverse Order",
|
||||||
"3": "Duplex Sort",
|
"3": "Duplex Sort",
|
||||||
@ -782,6 +782,19 @@
|
|||||||
"10": "Odd-Even Merge",
|
"10": "Odd-Even Merge",
|
||||||
"11": "Duplicate all pages"
|
"11": "Duplicate all pages"
|
||||||
},
|
},
|
||||||
|
"desc": {
|
||||||
|
"CUSTOM": "Use a custom sequence of page numbers or expressions to define a new order.",
|
||||||
|
"REVERSE_ORDER": "Flip the document so the last page becomes first and so on.",
|
||||||
|
"DUPLEX_SORT": "Interleave fronts then backs as if a duplex scanner scanned all fronts, then all backs (1, n, 2, n-1, …).",
|
||||||
|
"BOOKLET_SORT": "Arrange pages for booklet printing (last, first, second, second last, …).",
|
||||||
|
"SIDE_STITCH_BOOKLET_SORT": "Arrange pages for side‑stitch booklet printing (optimized for binding on the side).",
|
||||||
|
"ODD_EVEN_SPLIT": "Split the document into two outputs: all odd pages and all even pages.",
|
||||||
|
"ODD_EVEN_MERGE": "Merge two PDFs by alternating pages: odd from the first, even from the second.",
|
||||||
|
"DUPLICATE": "Duplicate each page according to the custom order count (e.g., 4 duplicates each page 4×).",
|
||||||
|
"REMOVE_FIRST": "Remove the first page from the document.",
|
||||||
|
"REMOVE_LAST": "Remove the last page from the document.",
|
||||||
|
"REMOVE_FIRST_AND_LAST": "Remove both the first and last pages from the document."
|
||||||
|
},
|
||||||
"placeholder": "(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)"
|
"placeholder": "(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)"
|
||||||
},
|
},
|
||||||
"addImage": {
|
"addImage": {
|
||||||
@ -1852,7 +1865,7 @@
|
|||||||
"title": "How we use Cookies",
|
"title": "How we use Cookies",
|
||||||
"description": {
|
"description": {
|
||||||
"1": "We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.",
|
"1": "We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.",
|
||||||
"2": "If you’d rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
|
"2": "If you'd rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
|
||||||
},
|
},
|
||||||
"acceptAllBtn": "Okay",
|
"acceptAllBtn": "Okay",
|
||||||
"acceptNecessaryBtn": "No Thanks",
|
"acceptNecessaryBtn": "No Thanks",
|
||||||
@ -1876,7 +1889,7 @@
|
|||||||
"1": "Strictly Necessary Cookies",
|
"1": "Strictly Necessary Cookies",
|
||||||
"2": "Always Enabled"
|
"2": "Always Enabled"
|
||||||
},
|
},
|
||||||
"description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can’t be turned off."
|
"description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can't be turned off."
|
||||||
},
|
},
|
||||||
"analytics": {
|
"analytics": {
|
||||||
"title": "Analytics",
|
"title": "Analytics",
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Divider, Select, Stack, TextInput } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ReorganizePagesParameters } from '../../../hooks/tools/reorganizePages/useReorganizePagesParameters';
|
||||||
|
import { getReorganizePagesModeData } from './constants';
|
||||||
|
|
||||||
|
export default function ReorganizePagesSettings({
|
||||||
|
parameters,
|
||||||
|
onParameterChange,
|
||||||
|
disabled,
|
||||||
|
}: {
|
||||||
|
parameters: ReorganizePagesParameters;
|
||||||
|
onParameterChange: <K extends keyof ReorganizePagesParameters>(
|
||||||
|
key: K,
|
||||||
|
value: ReorganizePagesParameters[K]
|
||||||
|
) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const modeData = getReorganizePagesModeData(t);
|
||||||
|
|
||||||
|
const requiresOrder = parameters.customMode === '' || parameters.customMode === 'DUPLICATE';
|
||||||
|
const selectedMode = modeData.find(mode => mode.value === parameters.customMode) || modeData[0];
|
||||||
|
return (
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Select
|
||||||
|
label={t('pdfOrganiser.mode._value', 'Organization mode')}
|
||||||
|
data={modeData}
|
||||||
|
value={parameters.customMode}
|
||||||
|
onChange={(v) => onParameterChange('customMode', v ?? '')}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
{selectedMode && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--information-text-bg)',
|
||||||
|
color: 'var(--information-text-color)',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
marginTop: '4px',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedMode.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{requiresOrder && (
|
||||||
|
<>
|
||||||
|
<Divider/>
|
||||||
|
<TextInput
|
||||||
|
label={t('pageOrderPrompt', 'Page order / ranges')}
|
||||||
|
placeholder={t('pdfOrganiser.placeholder', 'e.g. 1,3,2,4-6')}
|
||||||
|
value={parameters.pageNumbers}
|
||||||
|
onChange={(e) => onParameterChange('pageNumbers', e.currentTarget.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
59
frontend/src/components/tools/reorganizePages/constants.ts
Normal file
59
frontend/src/components/tools/reorganizePages/constants.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { TFunction } from 'i18next';
|
||||||
|
|
||||||
|
export const getReorganizePagesModeData = (t: TFunction) => [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
label: t('pdfOrganiser.mode.1', 'Custom Page Order'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.CUSTOM', 'Use a custom sequence of page numbers or expressions to define a new order.')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'REVERSE_ORDER',
|
||||||
|
label: t('pdfOrganiser.mode.2', 'Reverse Order'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.REVERSE_ORDER', 'Flip the document so the last page becomes first and so on.')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'DUPLEX_SORT',
|
||||||
|
label: t('pdfOrganiser.mode.3', 'Duplex Sort'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.DUPLEX_SORT', 'Interleave fronts then backs as if a duplex scanner scanned all fronts, then all backs (1, n, 2, n-1, …).')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'BOOKLET_SORT',
|
||||||
|
label: t('pdfOrganiser.mode.4', 'Booklet Sort'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.BOOKLET_SORT', 'Arrange pages for booklet printing (last, first, second, second last, …).')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'SIDE_STITCH_BOOKLET_SORT',
|
||||||
|
label: t('pdfOrganiser.mode.5', 'Side Stitch Booklet Sort'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.SIDE_STITCH_BOOKLET_SORT', 'Arrange pages for side‑stitch booklet printing (optimized for binding on the side).')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'ODD_EVEN_SPLIT',
|
||||||
|
label: t('pdfOrganiser.mode.6', 'Odd-Even Split'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.ODD_EVEN_SPLIT', 'Split the document into two outputs: all odd pages and all even pages.')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'ODD_EVEN_MERGE',
|
||||||
|
label: t('pdfOrganiser.mode.10', 'Odd-Even Merge'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.ODD_EVEN_MERGE', 'Merge two PDFs by alternating pages: odd from the first, even from the second.')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'DUPLICATE',
|
||||||
|
label: t('pdfOrganiser.mode.11', 'Duplicate all pages'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.DUPLICATE', 'Duplicate each page according to the custom order count (e.g., 4 duplicates each page 4×).')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'REMOVE_FIRST',
|
||||||
|
label: t('pdfOrganiser.mode.7', 'Remove First'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.REMOVE_FIRST', 'Remove the first page from the document.')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'REMOVE_LAST',
|
||||||
|
label: t('pdfOrganiser.mode.8', 'Remove Last'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.REMOVE_LAST', 'Remove the last page from the document.')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'REMOVE_FIRST_AND_LAST',
|
||||||
|
label: t('pdfOrganiser.mode.9', 'Remove First and Last'),
|
||||||
|
description: t('pdfOrganiser.mode.desc.REMOVE_FIRST_AND_LAST', 'Remove both the first and last pages from the document.')
|
||||||
|
},
|
||||||
|
];
|
@ -10,6 +10,8 @@ import AddPassword from "../tools/AddPassword";
|
|||||||
import ChangePermissions from "../tools/ChangePermissions";
|
import ChangePermissions from "../tools/ChangePermissions";
|
||||||
import RemoveBlanks from "../tools/RemoveBlanks";
|
import RemoveBlanks from "../tools/RemoveBlanks";
|
||||||
import RemovePages from "../tools/RemovePages";
|
import RemovePages from "../tools/RemovePages";
|
||||||
|
import ReorganizePages from "../tools/ReorganizePages";
|
||||||
|
import { reorganizePagesOperationConfig } from "../hooks/tools/reorganizePages/useReorganizePagesOperation";
|
||||||
import RemovePassword from "../tools/RemovePassword";
|
import RemovePassword from "../tools/RemovePassword";
|
||||||
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
||||||
import { getSynonyms } from "../utils/toolSynonyms";
|
import { getSynonyms } from "../utils/toolSynonyms";
|
||||||
@ -381,14 +383,15 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
reorganizePages: {
|
reorganizePages: {
|
||||||
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.reorganizePages.title", "Reorganize Pages"),
|
name: t("home.reorganizePages.title", "Reorganize Pages"),
|
||||||
component: null,
|
component: ReorganizePages,
|
||||||
workbench: "pageEditor",
|
|
||||||
description: t(
|
description: t(
|
||||||
"home.reorganizePages.desc",
|
"home.reorganizePages.desc",
|
||||||
"Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
"Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||||
),
|
),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
|
endpoints: ["rearrange-pages"],
|
||||||
|
operationConfig: reorganizePagesOperationConfig,
|
||||||
synonyms: getSynonyms(t, "reorganizePages")
|
synonyms: getSynonyms(t, "reorganizePages")
|
||||||
},
|
},
|
||||||
scalePages: {
|
scalePages: {
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ToolOperationConfig, ToolType, useToolOperation } from '../shared/useToolOperation';
|
||||||
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
|
import { ReorganizePagesParameters } from './useReorganizePagesParameters';
|
||||||
|
|
||||||
|
const buildFormData = (parameters: ReorganizePagesParameters, file: File): FormData => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fileInput', file);
|
||||||
|
if (parameters.customMode) {
|
||||||
|
formData.append('customMode', parameters.customMode);
|
||||||
|
}
|
||||||
|
if (parameters.pageNumbers) {
|
||||||
|
const cleaned = parameters.pageNumbers.replace(/\s+/g, '');
|
||||||
|
formData.append('pageNumbers', cleaned);
|
||||||
|
}
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reorganizePagesOperationConfig: ToolOperationConfig<ReorganizePagesParameters> = {
|
||||||
|
toolType: ToolType.singleFile,
|
||||||
|
buildFormData,
|
||||||
|
operationType: 'reorganizePages',
|
||||||
|
endpoint: '/api/v1/general/rearrange-pages',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useReorganizePagesOperation = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return useToolOperation<ReorganizePagesParameters>({
|
||||||
|
...reorganizePagesOperationConfig,
|
||||||
|
getErrorMessage: createStandardErrorHandler(
|
||||||
|
t('reorganizePages.error.failed', 'Failed to reorganize pages')
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export interface ReorganizePagesParameters {
|
||||||
|
customMode: string; // empty string means custom order using pageNumbers
|
||||||
|
pageNumbers: string; // e.g. "1,3,2,4-6"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultReorganizePagesParameters: ReorganizePagesParameters = {
|
||||||
|
customMode: '',
|
||||||
|
pageNumbers: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useReorganizePagesParameters = () => {
|
||||||
|
const [parameters, setParameters] = useState<ReorganizePagesParameters>(defaultReorganizePagesParameters);
|
||||||
|
|
||||||
|
const updateParameter = <K extends keyof ReorganizePagesParameters>(
|
||||||
|
key: K,
|
||||||
|
value: ReorganizePagesParameters[K]
|
||||||
|
) => {
|
||||||
|
setParameters(prev => ({ ...prev, [key]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetParameters = () => setParameters(defaultReorganizePagesParameters);
|
||||||
|
|
||||||
|
// If customMode is '' (custom) or 'DUPLICATE', a page order is required; otherwise it's optional/ignored
|
||||||
|
const validateParameters = (): boolean => {
|
||||||
|
const requiresOrder = parameters.customMode === '' || parameters.customMode === 'DUPLICATE';
|
||||||
|
return requiresOrder ? parameters.pageNumbers.trim().length > 0 : true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
parameters,
|
||||||
|
updateParameter,
|
||||||
|
resetParameters,
|
||||||
|
validateParameters,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
104
frontend/src/tools/ReorganizePages.tsx
Normal file
104
frontend/src/tools/ReorganizePages.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
|
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||||
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
|
import { useFileSelection } from "../contexts/FileContext";
|
||||||
|
import { useAccordionSteps } from "../hooks/tools/shared/useAccordionSteps";
|
||||||
|
import ReorganizePagesSettings from "../components/tools/reorganizePages/ReorganizePagesSettings";
|
||||||
|
import { useReorganizePagesParameters } from "../hooks/tools/reorganizePages/useReorganizePagesParameters";
|
||||||
|
import { useReorganizePagesOperation } from "../hooks/tools/reorganizePages/useReorganizePagesOperation";
|
||||||
|
|
||||||
|
const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { selectedFiles } = useFileSelection();
|
||||||
|
|
||||||
|
const params = useReorganizePagesParameters();
|
||||||
|
const operation = useReorganizePagesOperation();
|
||||||
|
|
||||||
|
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("rearrange-pages");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
operation.resetResults();
|
||||||
|
onPreviewFile?.(null);
|
||||||
|
}, [params.parameters]);
|
||||||
|
|
||||||
|
const handleExecute = async () => {
|
||||||
|
try {
|
||||||
|
await operation.executeOperation(params.parameters, selectedFiles);
|
||||||
|
if (operation.files && onComplete) {
|
||||||
|
onComplete(operation.files);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
onError?.(error?.message || t("reorganizePages.error.failed", "Failed to reorganize pages"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasFiles = selectedFiles.length > 0;
|
||||||
|
const hasResults = operation.files.length > 0 || operation.downloadUrl !== null;
|
||||||
|
|
||||||
|
enum Step {
|
||||||
|
NONE = 'none',
|
||||||
|
SETTINGS = 'settings'
|
||||||
|
}
|
||||||
|
|
||||||
|
const accordion = useAccordionSteps<Step>({
|
||||||
|
noneValue: Step.NONE,
|
||||||
|
initialStep: Step.SETTINGS,
|
||||||
|
stateConditions: {
|
||||||
|
hasFiles,
|
||||||
|
hasResults
|
||||||
|
},
|
||||||
|
afterResults: () => {
|
||||||
|
operation.resetResults();
|
||||||
|
onPreviewFile?.(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: t("reorganizePages.settings.title", "Settings"),
|
||||||
|
isCollapsed: accordion.getCollapsedState(Step.SETTINGS),
|
||||||
|
onCollapsedClick: () => accordion.handleStepToggle(Step.SETTINGS),
|
||||||
|
isVisible: true,
|
||||||
|
content: (
|
||||||
|
<ReorganizePagesSettings
|
||||||
|
parameters={params.parameters}
|
||||||
|
onParameterChange={params.updateParameter}
|
||||||
|
disabled={endpointLoading}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return createToolFlow({
|
||||||
|
files: {
|
||||||
|
selectedFiles,
|
||||||
|
isCollapsed: hasResults,
|
||||||
|
},
|
||||||
|
steps,
|
||||||
|
executeButton: {
|
||||||
|
text: t('reorganizePages.submit', 'Reorganize Pages'),
|
||||||
|
isVisible: !hasResults,
|
||||||
|
loadingText: t('loading'),
|
||||||
|
onClick: handleExecute,
|
||||||
|
disabled: !params.validateParameters() || !hasFiles || !endpointEnabled,
|
||||||
|
},
|
||||||
|
review: {
|
||||||
|
isVisible: hasResults,
|
||||||
|
operation: operation,
|
||||||
|
title: t('reorganizePages.results.title', 'Pages Reorganized'),
|
||||||
|
onFileClick: (file) => onPreviewFile?.(file),
|
||||||
|
onUndo: async () => {
|
||||||
|
await operation.undoOperation();
|
||||||
|
onPreviewFile?.(null);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
(ReorganizePages as any).tool = () => useReorganizePagesOperation;
|
||||||
|
|
||||||
|
export default ReorganizePages as ToolComponent;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user