mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 19:10:47 +02:00
Feature/v2/get all info on pdf (#5105)
# Description of Changes - Addition of the get all info on PDF tool --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import { useFileContext } from '@app/contexts/file/fileHooks';
|
||||
import { ToolOperationHook } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import type { StirlingFile } from '@app/types/fileContext';
|
||||
import { extractErrorMessage } from '@app/utils/toolErrorHandler';
|
||||
import {
|
||||
PdfInfoReportEntry,
|
||||
INFO_JSON_FILENAME,
|
||||
} from '@app/types/getPdfInfo';
|
||||
import type { GetPdfInfoParameters } from '@app/hooks/tools/getPdfInfo/useGetPdfInfoParameters';
|
||||
|
||||
export interface GetPdfInfoOperationHook extends ToolOperationHook<GetPdfInfoParameters> {
|
||||
results: PdfInfoReportEntry[];
|
||||
}
|
||||
|
||||
export const useGetPdfInfoOperation = (): GetPdfInfoOperationHook => {
|
||||
const { t } = useTranslation();
|
||||
const { selectors } = useFileContext();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [status, setStatus] = useState('');
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
|
||||
const [downloadFilename, setDownloadFilename] = useState('');
|
||||
const [results, setResults] = useState<PdfInfoReportEntry[]>([]);
|
||||
|
||||
const cancelRequested = useRef(false);
|
||||
const previousUrl = useRef<string | null>(null);
|
||||
|
||||
const cleanupDownloadUrl = useCallback(() => {
|
||||
if (previousUrl.current) {
|
||||
URL.revokeObjectURL(previousUrl.current);
|
||||
previousUrl.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const resetResults = useCallback(() => {
|
||||
cancelRequested.current = false;
|
||||
setResults([]);
|
||||
setFiles([]);
|
||||
cleanupDownloadUrl();
|
||||
setDownloadUrl(null);
|
||||
setDownloadFilename('');
|
||||
setStatus('');
|
||||
setErrorMessage(null);
|
||||
}, [cleanupDownloadUrl]);
|
||||
|
||||
const clearError = useCallback(() => {
|
||||
setErrorMessage(null);
|
||||
}, []);
|
||||
|
||||
const executeOperation = useCallback(
|
||||
async (_params: GetPdfInfoParameters, selectedFiles: StirlingFile[]) => {
|
||||
if (selectedFiles.length === 0) {
|
||||
setErrorMessage(t('noFileSelected', 'No files selected'));
|
||||
return;
|
||||
}
|
||||
|
||||
cancelRequested.current = false;
|
||||
setIsLoading(true);
|
||||
setStatus(t('getPdfInfo.processing', 'Extracting information...'));
|
||||
setErrorMessage(null);
|
||||
setResults([]);
|
||||
setFiles([]);
|
||||
cleanupDownloadUrl();
|
||||
setDownloadUrl(null);
|
||||
setDownloadFilename('');
|
||||
|
||||
try {
|
||||
const aggregated: PdfInfoReportEntry[] = [];
|
||||
const generatedAt = Date.now();
|
||||
|
||||
for (const file of selectedFiles) {
|
||||
if (cancelRequested.current) break;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('fileInput', file);
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/api/v1/security/get-info-on-pdf', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
|
||||
const stub = selectors.getStirlingFileStub(file.fileId);
|
||||
const entry: PdfInfoReportEntry = {
|
||||
fileId: file.fileId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size ?? null,
|
||||
lastModified: file.lastModified ?? null,
|
||||
thumbnailUrl: stub?.thumbnailUrl ?? null,
|
||||
data: response.data ?? {},
|
||||
error: null,
|
||||
summaryGeneratedAt: generatedAt,
|
||||
};
|
||||
aggregated.push(entry);
|
||||
} catch (error) {
|
||||
const stub = selectors.getStirlingFileStub(file.fileId);
|
||||
aggregated.push({
|
||||
fileId: file.fileId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size ?? null,
|
||||
lastModified: file.lastModified ?? null,
|
||||
thumbnailUrl: stub?.thumbnailUrl ?? null,
|
||||
data: {},
|
||||
error: extractErrorMessage(error),
|
||||
summaryGeneratedAt: generatedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancelRequested.current) {
|
||||
setResults(aggregated);
|
||||
if (aggregated.length > 0) {
|
||||
// Build V1-compatible JSON: use backend payloads directly.
|
||||
const payloads = aggregated
|
||||
.filter((e) => !e.error)
|
||||
.map((e) => e.data);
|
||||
const content = payloads.length === 1 ? payloads[0] : payloads;
|
||||
const json = JSON.stringify(content, null, 2);
|
||||
const resultFile = new File([json], INFO_JSON_FILENAME, { type: 'application/json' });
|
||||
setFiles([resultFile]);
|
||||
}
|
||||
|
||||
const anyError = aggregated.some((item) => item.error);
|
||||
if (anyError) {
|
||||
setErrorMessage(t('getPdfInfo.error.partial', 'Some files could not be processed.'));
|
||||
}
|
||||
setStatus(t('getPdfInfo.status.complete', 'Extraction complete'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[getPdfInfo] unexpected failure', e);
|
||||
setErrorMessage(t('getPdfInfo.error.unexpected', 'Unexpected error during extraction.'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[cleanupDownloadUrl, selectors, t]
|
||||
);
|
||||
|
||||
const cancelOperation = useCallback(() => {
|
||||
if (isLoading) {
|
||||
cancelRequested.current = true;
|
||||
setIsLoading(false);
|
||||
setStatus(t('operationCancelled', 'Operation cancelled'));
|
||||
}
|
||||
}, [isLoading, t]);
|
||||
|
||||
const undoOperation = useCallback(async () => {
|
||||
resetResults();
|
||||
}, [resetResults]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanupDownloadUrl();
|
||||
};
|
||||
}, [cleanupDownloadUrl]);
|
||||
|
||||
return useMemo<GetPdfInfoOperationHook>(
|
||||
() => ({
|
||||
files,
|
||||
thumbnails: [],
|
||||
isGeneratingThumbnails: false,
|
||||
downloadUrl,
|
||||
downloadFilename,
|
||||
isLoading,
|
||||
status,
|
||||
errorMessage,
|
||||
progress: null,
|
||||
executeOperation,
|
||||
resetResults,
|
||||
clearError,
|
||||
cancelOperation,
|
||||
undoOperation,
|
||||
results,
|
||||
}),
|
||||
[
|
||||
cancelOperation,
|
||||
clearError,
|
||||
downloadFilename,
|
||||
downloadUrl,
|
||||
errorMessage,
|
||||
executeOperation,
|
||||
files,
|
||||
isLoading,
|
||||
resetResults,
|
||||
results,
|
||||
status,
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { BaseParameters } from '@app/types/parameters';
|
||||
import { useBaseParameters, BaseParametersHook } from '@app/hooks/tools/shared/useBaseParameters';
|
||||
|
||||
export interface GetPdfInfoParameters extends BaseParameters {
|
||||
// No parameters needed
|
||||
}
|
||||
|
||||
export const defaultParameters: GetPdfInfoParameters = {};
|
||||
|
||||
export type GetPdfInfoParametersHook = BaseParametersHook<GetPdfInfoParameters>;
|
||||
|
||||
export const useGetPdfInfoParameters = (): GetPdfInfoParametersHook => {
|
||||
return useBaseParameters({
|
||||
defaultParameters,
|
||||
endpointName: 'get-info-on-pdf',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user