mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
extract pages
This commit is contained in:
parent
4c0c9b28ef
commit
eb813df153
@ -10,7 +10,7 @@ import {
|
||||
everyNthExpression,
|
||||
rangeExpression,
|
||||
LogicalOperator,
|
||||
} from '@app/components/pageEditor/bulkSelectionPanel/BulkSelection';
|
||||
} from '@app/utils/bulkselection/selectionBuilders';
|
||||
import SelectPages from '@app/components/pageEditor/bulkSelectionPanel/SelectPages';
|
||||
import OperatorsSection from '@app/components/pageEditor/bulkSelectionPanel/OperatorsSection';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Button, Text, Group, Divider } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import classes from '@app/components/pageEditor/bulkSelectionPanel/BulkSelectionPanel.module.css';
|
||||
import { LogicalOperator } from '@app/components/pageEditor/bulkSelectionPanel/BulkSelection';
|
||||
import { LogicalOperator } from '@app/utils/bulkselection/selectionBuilders';
|
||||
|
||||
interface OperatorsSectionProps {
|
||||
csvInput: string;
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import { Stack, TextInput } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ExtractPagesParameters } from "@app/hooks/tools/extractPages/useExtractPagesParameters";
|
||||
|
||||
interface ExtractPagesSettingsProps {
|
||||
parameters: ExtractPagesParameters;
|
||||
onParameterChange: <K extends keyof ExtractPagesParameters>(key: K, value: ExtractPagesParameters[K]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const ExtractPagesSettings = ({ parameters, onParameterChange, disabled = false }: ExtractPagesSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
onParameterChange('pageNumbers', value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label={t('extractPages.pageNumbers.label', 'Pages to Extract')}
|
||||
value={parameters.pageNumbers || ''}
|
||||
onChange={(event) => handleChange(event.currentTarget.value)}
|
||||
placeholder={t('extractPages.pageNumbers.placeholder', 'e.g., 1,3,5-8 or odd & 1-10')}
|
||||
disabled={disabled}
|
||||
required
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtractPagesSettings;
|
||||
|
||||
|
||||
@ -105,7 +105,10 @@ import AddPageNumbers from "@app/tools/AddPageNumbers";
|
||||
import RemoveAnnotations from "@app/tools/RemoveAnnotations";
|
||||
import PageLayoutSettings from "@app/components/tools/pageLayout/PageLayoutSettings";
|
||||
import ExtractImages from "@app/tools/ExtractImages";
|
||||
import ExtractPages from "@app/tools/ExtractPages";
|
||||
import ExtractImagesSettings from "@app/components/tools/extractImages/ExtractImagesSettings";
|
||||
import ExtractPagesSettings from "@app/components/tools/extractPages/ExtractPagesSettings";
|
||||
import { extractPagesOperationConfig } from "@app/hooks/tools/extractPages/useExtractPagesOperation";
|
||||
import ReplaceColorSettings from "@app/components/tools/replaceColor/ReplaceColorSettings";
|
||||
import AddStampAutomationSettings from "@app/components/tools/addStamp/AddStampAutomationSettings";
|
||||
import CertSignAutomationSettings from "@app/components/tools/certSign/CertSignAutomationSettings";
|
||||
@ -474,12 +477,14 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
|
||||
extractPages: {
|
||||
icon: <LocalIcon icon="upload-rounded" width="1.5rem" height="1.5rem" />,
|
||||
name: t("home.extractPages.title", "Extract Pages"),
|
||||
component: null,
|
||||
component: ExtractPages,
|
||||
description: t("home.extractPages.desc", "Extract specific pages from a PDF document"),
|
||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||
subcategoryId: SubcategoryId.EXTRACTION,
|
||||
synonyms: getSynonyms(t, "extractPages"),
|
||||
automationSettings: null,
|
||||
automationSettings: ExtractPagesSettings,
|
||||
operationConfig: extractPagesOperationConfig,
|
||||
endpoints: ["rearrange-pages"],
|
||||
},
|
||||
extractImages: {
|
||||
icon: <LocalIcon icon="photo-library-rounded" width="1.5rem" height="1.5rem" />,
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolType, useToolOperation } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '@app/utils/toolErrorHandler';
|
||||
import { ExtractPagesParameters, defaultParameters } from '@app/hooks/tools/extractPages/useExtractPagesParameters';
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
import { parseSelection } from '@app/utils/bulkselection/parseSelection';
|
||||
|
||||
// Convert advanced page selection expression into CSV of explicit one-based page numbers
|
||||
async function resolveSelectionToCsv(expression: string, file: File): Promise<string> {
|
||||
// Load PDF to determine max pages
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const pdf = await pdfWorkerManager.createDocument(arrayBuffer, { disableAutoFetch: true, disableStream: true });
|
||||
try {
|
||||
const maxPages = (pdf as any).numPages as number;
|
||||
const pages = parseSelection(expression || '', maxPages);
|
||||
return pages.join(',');
|
||||
} finally {
|
||||
pdfWorkerManager.destroyDocument(pdf);
|
||||
}
|
||||
}
|
||||
|
||||
export const extractPagesOperationConfig = {
|
||||
toolType: ToolType.custom,
|
||||
operationType: 'extractPages',
|
||||
customProcessor: async (parameters: ExtractPagesParameters, files: File[]): Promise<File[]> => {
|
||||
const outputs: File[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
// Resolve selection into CSV acceptable by backend
|
||||
const csv = await resolveSelectionToCsv(parameters.pageNumbers, file);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('fileInput', file);
|
||||
formData.append('pageNumbers', csv);
|
||||
|
||||
const response = await apiClient.post('/api/v1/general/rearrange-pages', formData, { responseType: 'blob' });
|
||||
|
||||
// Name output file with suffix
|
||||
const base = (file.name || 'document.pdf').replace(/\.[^.]+$/, '');
|
||||
const outName = `${base}_extracted_pages.pdf`;
|
||||
const outFile = new File([response.data], outName, { type: 'application/pdf' });
|
||||
outputs.push(outFile);
|
||||
}
|
||||
|
||||
return outputs;
|
||||
},
|
||||
defaultParameters,
|
||||
} as const;
|
||||
|
||||
export const useExtractPagesOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
return useToolOperation<ExtractPagesParameters>({
|
||||
...extractPagesOperationConfig,
|
||||
getErrorMessage: createStandardErrorHandler(t('extractPages.error.failed', 'Failed to extract pages'))
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import { BaseParameters } from '@app/types/parameters';
|
||||
import { useBaseParameters, BaseParametersHook } from '@app/hooks/tools/shared/useBaseParameters';
|
||||
|
||||
export interface ExtractPagesParameters extends BaseParameters {
|
||||
pageNumbers: string;
|
||||
}
|
||||
|
||||
export const defaultParameters: ExtractPagesParameters = {
|
||||
pageNumbers: '',
|
||||
};
|
||||
|
||||
export type ExtractPagesParametersHook = BaseParametersHook<ExtractPagesParameters>;
|
||||
|
||||
export const useExtractPagesParameters = (): ExtractPagesParametersHook => {
|
||||
return useBaseParameters({
|
||||
defaultParameters,
|
||||
endpointName: 'rearrange-pages',
|
||||
validateFn: (p) => (p.pageNumbers || '').trim().length > 0,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
59
frontend/src/core/tools/ExtractPages.tsx
Normal file
59
frontend/src/core/tools/ExtractPages.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
|
||||
import { BaseToolProps, ToolComponent } from "@app/types/tool";
|
||||
import { useBaseTool } from "@app/hooks/tools/shared/useBaseTool";
|
||||
import { useExtractPagesParameters } from "@app/hooks/tools/extractPages/useExtractPagesParameters";
|
||||
import { useExtractPagesOperation } from "@app/hooks/tools/extractPages/useExtractPagesOperation";
|
||||
import ExtractPagesSettings from "@app/components/tools/extractPages/ExtractPagesSettings";
|
||||
|
||||
const ExtractPages = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const base = useBaseTool(
|
||||
'extract-pages',
|
||||
useExtractPagesParameters,
|
||||
useExtractPagesOperation,
|
||||
props
|
||||
);
|
||||
|
||||
const settingsContent = (
|
||||
<ExtractPagesSettings
|
||||
parameters={base.params.parameters}
|
||||
onParameterChange={base.params.updateParameter}
|
||||
disabled={base.endpointLoading}
|
||||
/>
|
||||
);
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles: base.selectedFiles,
|
||||
isCollapsed: base.hasResults,
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
title: t("extractPages.settings.title", "Settings"),
|
||||
isCollapsed: base.settingsCollapsed,
|
||||
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
||||
content: settingsContent,
|
||||
},
|
||||
],
|
||||
executeButton: {
|
||||
text: t("extractPages.submit", "Extract Pages"),
|
||||
loadingText: t("loading"),
|
||||
onClick: base.handleExecute,
|
||||
isVisible: !base.hasResults,
|
||||
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
|
||||
},
|
||||
review: {
|
||||
isVisible: base.hasResults,
|
||||
operation: base.operation,
|
||||
title: t("extractPages.results.title", "Pages Extracted"),
|
||||
onFileClick: base.handleThumbnailClick,
|
||||
onUndo: base.handleUndo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default ExtractPages as ToolComponent;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Pure helper utilities for the BulkSelectionPanel UI
|
||||
// Pure helper utilities for building and manipulating bulk page selection expressions
|
||||
|
||||
export type LogicalOperator = 'and' | 'or' | 'not' | 'even' | 'odd';
|
||||
|
||||
@ -33,7 +33,7 @@ export function insertOperatorSmart(currentInput: string, op: LogicalOperator):
|
||||
}
|
||||
return `${text} or ${op} `;
|
||||
}
|
||||
|
||||
|
||||
if (text.length === 0) return `${op} `;
|
||||
|
||||
// Extract up to the last two operator tokens (words or symbols) from the end
|
||||
Loading…
Reference in New Issue
Block a user