mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-08-11 13:48:37 +02:00
Put sanitized files in workspace instead of download
This commit is contained in:
parent
54b3295798
commit
243638d513
@ -1,22 +1,99 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFileContext } from '../../../contexts/FileContext';
|
||||
import { FileOperation } from '../../../types/fileContext';
|
||||
import { generateThumbnailForFile } from '../../../utils/thumbnailUtils';
|
||||
import { SanitizeParameters } from './useSanitizeParameters';
|
||||
|
||||
export const useSanitizeOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
recordOperation,
|
||||
markOperationApplied,
|
||||
markOperationFailed,
|
||||
addFiles
|
||||
} = useFileContext();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
|
||||
const [status, setStatus] = useState<string | null>(null);
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [thumbnails, setThumbnails] = useState<string[]>([]);
|
||||
const [isGeneratingThumbnails, setIsGeneratingThumbnails] = useState(false);
|
||||
|
||||
const createOperation = useCallback((
|
||||
parameters: SanitizeParameters,
|
||||
selectedFiles: File[]
|
||||
): { operation: FileOperation; operationId: string; fileId: string } => {
|
||||
const operationId = `sanitize-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const fileId = selectedFiles[0].name;
|
||||
|
||||
const operation: FileOperation = {
|
||||
id: operationId,
|
||||
type: 'sanitize',
|
||||
timestamp: Date.now(),
|
||||
fileIds: selectedFiles.map(f => f.name),
|
||||
status: 'pending',
|
||||
metadata: {
|
||||
originalFileName: selectedFiles[0].name,
|
||||
parameters: {
|
||||
removeJavaScript: parameters.removeJavaScript,
|
||||
removeEmbeddedFiles: parameters.removeEmbeddedFiles,
|
||||
removeXMPMetadata: parameters.removeXMPMetadata,
|
||||
removeMetadata: parameters.removeMetadata,
|
||||
removeLinks: parameters.removeLinks,
|
||||
removeFonts: parameters.removeFonts,
|
||||
},
|
||||
fileSize: selectedFiles[0].size
|
||||
}
|
||||
};
|
||||
|
||||
return { operation, operationId, fileId };
|
||||
}, []);
|
||||
|
||||
const processResults = useCallback(async (blob: Blob, filename: string) => {
|
||||
try {
|
||||
// Create sanitized file
|
||||
const sanitizedFile = new File([blob], filename, { type: blob.type });
|
||||
|
||||
// Set local state for preview
|
||||
setFiles([sanitizedFile]);
|
||||
setThumbnails([]);
|
||||
setIsGeneratingThumbnails(true);
|
||||
|
||||
// Add sanitized file to FileContext for future use
|
||||
await addFiles([sanitizedFile]);
|
||||
|
||||
// Generate thumbnail for preview
|
||||
try {
|
||||
const thumbnail = await generateThumbnailForFile(sanitizedFile);
|
||||
if (thumbnail) {
|
||||
setThumbnails([thumbnail]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to generate thumbnail for ${filename}:`, error);
|
||||
setThumbnails(['']);
|
||||
}
|
||||
|
||||
setIsGeneratingThumbnails(false);
|
||||
} catch (error) {
|
||||
console.warn('Failed to process sanitization result:', error);
|
||||
}
|
||||
}, [addFiles]);
|
||||
|
||||
const executeOperation = useCallback(async (
|
||||
parameters: SanitizeParameters,
|
||||
selectedFiles: File[]
|
||||
selectedFiles: File[],
|
||||
generateSanitizedFileName: (originalFileName?: string) => string
|
||||
) => {
|
||||
if (selectedFiles.length === 0) {
|
||||
throw new Error(t('error.noFilesSelected', 'No files selected'));
|
||||
}
|
||||
|
||||
const { operation, operationId, fileId } = createOperation(parameters, selectedFiles);
|
||||
recordOperation(fileId, operation);
|
||||
|
||||
setIsLoading(true);
|
||||
setErrorMessage(null);
|
||||
setStatus(t('sanitize.processing', 'Sanitizing PDF...'));
|
||||
@ -24,7 +101,7 @@ export const useSanitizeOperation = () => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('fileInput', selectedFiles[0]);
|
||||
|
||||
|
||||
// Add parameters
|
||||
formData.append('removeJavaScript', parameters.removeJavaScript.toString());
|
||||
formData.append('removeEmbeddedFiles', parameters.removeEmbeddedFiles.toString());
|
||||
@ -40,13 +117,20 @@ export const useSanitizeOperation = () => {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
markOperationFailed(fileId, operationId, errorText);
|
||||
throw new Error(t('sanitize.error', 'Sanitization failed: {{error}}', { error: errorText }));
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const sanitizedFileName = generateSanitizedFileName(selectedFiles[0].name);
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
setDownloadUrl(url);
|
||||
setStatus(t('sanitize.completed', 'Sanitization completed successfully'));
|
||||
|
||||
// Process results and add to workbench
|
||||
await processResults(blob, sanitizedFileName);
|
||||
markOperationApplied(fileId, operationId);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('sanitize.error.generic', 'Sanitization failed');
|
||||
setErrorMessage(message);
|
||||
@ -55,12 +139,15 @@ export const useSanitizeOperation = () => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
}, [t, createOperation, recordOperation, markOperationApplied, markOperationFailed, processResults]);
|
||||
|
||||
const resetResults = useCallback(() => {
|
||||
if (downloadUrl) {
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
setFiles([]);
|
||||
setThumbnails([]);
|
||||
setIsGeneratingThumbnails(false);
|
||||
setDownloadUrl(null);
|
||||
setErrorMessage(null);
|
||||
setStatus(null);
|
||||
@ -70,13 +157,25 @@ export const useSanitizeOperation = () => {
|
||||
setErrorMessage(null);
|
||||
}, []);
|
||||
|
||||
// Cleanup blob URLs on unmount to prevent memory leaks
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (downloadUrl) {
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
};
|
||||
}, [downloadUrl]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
errorMessage,
|
||||
downloadUrl,
|
||||
status,
|
||||
files,
|
||||
thumbnails,
|
||||
isGeneratingThumbnails,
|
||||
executeOperation,
|
||||
resetResults,
|
||||
clearError,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -9,20 +9,23 @@ import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep
|
||||
import OperationButton from "../components/tools/shared/OperationButton";
|
||||
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
||||
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
||||
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
||||
import SanitizeSettings from "../components/tools/sanitize/SanitizeSettings";
|
||||
|
||||
import { useSanitizeParameters } from "../hooks/tools/sanitize/useSanitizeParameters";
|
||||
import { useSanitizeOperation } from "../hooks/tools/sanitize/useSanitizeOperation";
|
||||
import { BaseToolProps } from "../types/tool";
|
||||
import { useFileContext } from "../contexts/FileContext";
|
||||
|
||||
const generateSanitizedFileName = (originalFileName?: string): string => {
|
||||
const baseName = originalFileName?.replace(/\.[^/.]+$/, '') || 'document';
|
||||
return `${baseName}_sanitized.pdf`;
|
||||
return `sanitized_${baseName}.pdf`;
|
||||
};
|
||||
|
||||
const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { selectedFiles } = useToolFileSelection();
|
||||
const { setCurrentMode } = useFileContext();
|
||||
|
||||
const sanitizeParams = useSanitizeParameters();
|
||||
const sanitizeOperation = useSanitizeOperation();
|
||||
@ -41,17 +44,11 @@ const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
try {
|
||||
await sanitizeOperation.executeOperation(
|
||||
sanitizeParams.parameters,
|
||||
selectedFiles
|
||||
selectedFiles,
|
||||
generateSanitizedFileName
|
||||
);
|
||||
if (sanitizeOperation.downloadUrl && onComplete) {
|
||||
// Create a File object from the download URL for completion callback
|
||||
const response = await fetch(sanitizeOperation.downloadUrl);
|
||||
const blob = await response.blob();
|
||||
const sanitizedFileName = generateSanitizedFileName(selectedFiles[0]?.name);
|
||||
const file = new File([blob], sanitizedFileName, {
|
||||
type: 'application/pdf'
|
||||
});
|
||||
onComplete([file]);
|
||||
if (sanitizeOperation.files && onComplete) {
|
||||
onComplete(sanitizeOperation.files);
|
||||
}
|
||||
} catch (error) {
|
||||
if (onError) {
|
||||
@ -65,8 +62,14 @@ const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
onPreviewFile?.(null);
|
||||
};
|
||||
|
||||
const handleThumbnailClick = (file: File) => {
|
||||
onPreviewFile?.(file);
|
||||
sessionStorage.setItem('previousMode', 'sanitize');
|
||||
setCurrentMode('viewer');
|
||||
};
|
||||
|
||||
const hasFiles = selectedFiles.length > 0;
|
||||
const hasResults = sanitizeOperation.downloadUrl !== null;
|
||||
const hasResults = sanitizeOperation.files.length > 0;
|
||||
const filesCollapsed = hasFiles;
|
||||
const settingsCollapsed = hasResults;
|
||||
|
||||
@ -141,6 +144,16 @@ const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
{t("download", "Download")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<ResultsPreview
|
||||
files={sanitizeOperation.files.map((file, index) => ({
|
||||
file,
|
||||
thumbnail: sanitizeOperation.thumbnails[index]
|
||||
}))}
|
||||
onFileClick={handleThumbnailClick}
|
||||
isGeneratingThumbnails={sanitizeOperation.isGeneratingThumbnails}
|
||||
title={t("sanitize.sanitizationResults", "Sanitization Results")}
|
||||
/>
|
||||
</Stack>
|
||||
</ToolStep>
|
||||
</Stack>
|
||||
|
Loading…
Reference in New Issue
Block a user