mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
feat: Add React-based extract-images tool (#4501)
This commit is contained in:
parent
18fa16f08e
commit
9758e871d4
@ -1595,7 +1595,13 @@
|
||||
"header": "Extract Images",
|
||||
"selectText": "Select image format to convert extracted images to",
|
||||
"allowDuplicates": "Save duplicate images",
|
||||
"submit": "Extract"
|
||||
"submit": "Extract",
|
||||
"settings": {
|
||||
"title": "Settings"
|
||||
},
|
||||
"error": {
|
||||
"failed": "An error occurred while extracting images from the PDF."
|
||||
}
|
||||
},
|
||||
"pdfToPDFA": {
|
||||
"tags": "archive,long-term,standard,conversion,storage,preservation",
|
||||
|
@ -1056,7 +1056,13 @@
|
||||
"header": "Extract Images",
|
||||
"selectText": "Select image format to convert extracted images to",
|
||||
"allowDuplicates": "Save duplicate images",
|
||||
"submit": "Extract"
|
||||
"submit": "Extract",
|
||||
"settings": {
|
||||
"title": "Settings"
|
||||
},
|
||||
"error": {
|
||||
"failed": "An error occurred while extracting images from the PDF."
|
||||
}
|
||||
},
|
||||
"pdfToPDFA": {
|
||||
"tags": "archive,long-term,standard,conversion,storage,preservation",
|
||||
|
@ -0,0 +1,46 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Stack, Select, Checkbox } from '@mantine/core';
|
||||
import { ExtractImagesParameters } from '../../../hooks/tools/extractImages/useExtractImagesParameters';
|
||||
|
||||
interface ExtractImagesSettingsProps {
|
||||
parameters: ExtractImagesParameters;
|
||||
onParameterChange: <K extends keyof ExtractImagesParameters>(key: K, value: ExtractImagesParameters[K]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const ExtractImagesSettings = ({
|
||||
parameters,
|
||||
onParameterChange,
|
||||
disabled = false
|
||||
}: ExtractImagesSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Select
|
||||
label={t('extractImages.selectText', 'Output Format')}
|
||||
value={parameters.format}
|
||||
onChange={(value) => {
|
||||
const allowedFormats = ['png', 'jpg', 'gif'] as const;
|
||||
const format = allowedFormats.includes(value as any) ? (value as typeof allowedFormats[number]) : 'png';
|
||||
onParameterChange('format', format);
|
||||
}}
|
||||
data={[
|
||||
{ value: 'png', label: 'PNG' },
|
||||
{ value: 'jpg', label: 'JPG' },
|
||||
{ value: 'gif', label: 'GIF' },
|
||||
]}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label={t('extractImages.allowDuplicates', 'Allow Duplicate Images')}
|
||||
checked={parameters.allowDuplicates}
|
||||
onChange={(event) => onParameterChange('allowDuplicates', event.currentTarget.checked)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtractImagesSettings;
|
@ -50,6 +50,7 @@ import { redactOperationConfig } from "../hooks/tools/redact/useRedactOperation"
|
||||
import { rotateOperationConfig } from "../hooks/tools/rotate/useRotateOperation";
|
||||
import { changeMetadataOperationConfig } from "../hooks/tools/changeMetadata/useChangeMetadataOperation";
|
||||
import { cropOperationConfig } from "../hooks/tools/crop/useCropOperation";
|
||||
import { extractImagesOperationConfig } from "../hooks/tools/extractImages/useExtractImagesOperation";
|
||||
import { replaceColorOperationConfig } from "../hooks/tools/replaceColor/useReplaceColorOperation";
|
||||
import CompressSettings from "../components/tools/compress/CompressSettings";
|
||||
import SplitSettings from "../components/tools/split/SplitSettings";
|
||||
@ -79,6 +80,8 @@ import AdjustPageScaleSettings from "../components/tools/adjustPageScale/AdjustP
|
||||
import ScannerImageSplitSettings from "../components/tools/scannerImageSplit/ScannerImageSplitSettings";
|
||||
import ChangeMetadataSingleStep from "../components/tools/changeMetadata/ChangeMetadataSingleStep";
|
||||
import CropSettings from "../components/tools/crop/CropSettings";
|
||||
import ExtractImages from "../tools/ExtractImages";
|
||||
import ExtractImagesSettings from "../components/tools/extractImages/ExtractImagesSettings";
|
||||
import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings";
|
||||
|
||||
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
||||
@ -478,12 +481,16 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
synonyms: getSynonyms(t, "extractPages")
|
||||
},
|
||||
extractImages: {
|
||||
icon: <LocalIcon icon="filter-alt" width="1.5rem" height="1.5rem" />,
|
||||
icon: <LocalIcon icon="photo-library-rounded" width="1.5rem" height="1.5rem" />,
|
||||
name: t("home.extractImages.title", "Extract Images"),
|
||||
component: null,
|
||||
component: ExtractImages,
|
||||
description: t("home.extractImages.desc", "Extract images from PDF documents"),
|
||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||
subcategoryId: SubcategoryId.EXTRACTION,
|
||||
maxFiles: -1,
|
||||
endpoints: ["extract-images"],
|
||||
operationConfig: extractImagesOperationConfig,
|
||||
settingsComponent: ExtractImagesSettings,
|
||||
synonyms: getSynonyms(t, "extractImages")
|
||||
},
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation, ToolType } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
import { ExtractImagesParameters, defaultParameters } from './useExtractImagesParameters';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
// Static configuration that can be used by both the hook and automation executor
|
||||
export const buildExtractImagesFormData = (parameters: ExtractImagesParameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
formData.append("format", parameters.format);
|
||||
formData.append("allowDuplicates", parameters.allowDuplicates.toString());
|
||||
return formData;
|
||||
};
|
||||
|
||||
// Response handler for extract-images which returns a ZIP file
|
||||
const extractImagesResponseHandler = async (responseData: Blob, _originalFiles: File[]): Promise<File[]> => {
|
||||
const zip = new JSZip();
|
||||
const zipContent = await zip.loadAsync(responseData);
|
||||
const extractedFiles: File[] = [];
|
||||
|
||||
for (const [filename, file] of Object.entries(zipContent.files)) {
|
||||
if (!file.dir) {
|
||||
const blob = await file.async('blob');
|
||||
const extractedFile = new File([blob], filename, { type: blob.type });
|
||||
extractedFiles.push(extractedFile);
|
||||
}
|
||||
}
|
||||
|
||||
return extractedFiles;
|
||||
};
|
||||
|
||||
// Static configuration object
|
||||
export const extractImagesOperationConfig = {
|
||||
toolType: ToolType.singleFile,
|
||||
buildFormData: buildExtractImagesFormData,
|
||||
operationType: 'extractImages',
|
||||
endpoint: '/api/v1/misc/extract-images',
|
||||
defaultParameters,
|
||||
// Extract-images returns a ZIP file containing multiple image files
|
||||
responseHandler: extractImagesResponseHandler,
|
||||
} as const;
|
||||
|
||||
export const useExtractImagesOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useToolOperation<ExtractImagesParameters>({
|
||||
...extractImagesOperationConfig,
|
||||
getErrorMessage: createStandardErrorHandler(t('extractImages.error.failed', 'An error occurred while extracting images from the PDF.'))
|
||||
});
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
import { useBaseParameters } from '../shared/useBaseParameters';
|
||||
|
||||
export interface ExtractImagesParameters {
|
||||
format: 'png' | 'jpg' | 'gif';
|
||||
allowDuplicates: boolean;
|
||||
}
|
||||
|
||||
export const defaultParameters: ExtractImagesParameters = {
|
||||
format: 'png',
|
||||
allowDuplicates: false,
|
||||
};
|
||||
|
||||
export const useExtractImagesParameters = () => {
|
||||
return useBaseParameters<ExtractImagesParameters>({
|
||||
defaultParameters,
|
||||
endpointName: 'extract-images',
|
||||
validateFn: () => true, // All parameters have valid defaults
|
||||
});
|
||||
};
|
55
frontend/src/tools/ExtractImages.tsx
Normal file
55
frontend/src/tools/ExtractImages.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
import ExtractImagesSettings from "../components/tools/extractImages/ExtractImagesSettings";
|
||||
import { useExtractImagesParameters } from "../hooks/tools/extractImages/useExtractImagesParameters";
|
||||
import { useExtractImagesOperation } from "../hooks/tools/extractImages/useExtractImagesOperation";
|
||||
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
||||
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||
|
||||
const ExtractImages = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const base = useBaseTool(
|
||||
'extractImages',
|
||||
useExtractImagesParameters,
|
||||
useExtractImagesOperation,
|
||||
props
|
||||
);
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles: base.selectedFiles,
|
||||
isCollapsed: base.hasResults,
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
title: t("extractImages.settings.title", "Settings"),
|
||||
isCollapsed: base.settingsCollapsed,
|
||||
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
||||
content: (
|
||||
<ExtractImagesSettings
|
||||
parameters={base.params.parameters}
|
||||
onParameterChange={base.params.updateParameter}
|
||||
disabled={base.endpointLoading}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
executeButton: {
|
||||
text: t("extractImages.submit", "Extract Images"),
|
||||
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("extractImages.title", "Extracted Images"),
|
||||
onFileClick: base.handleThumbnailClick,
|
||||
onUndo: base.handleUndo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default ExtractImages as ToolComponent;
|
Loading…
Reference in New Issue
Block a user