mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
Update to use common hook code
This commit is contained in:
parent
cf2250be86
commit
a65721ceb7
@ -1,237 +0,0 @@
|
|||||||
import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest';
|
|
||||||
import { renderHook, act } from '@testing-library/react';
|
|
||||||
import { useSanitizeOperation } from './useSanitizeOperation';
|
|
||||||
|
|
||||||
// Mock useTranslation
|
|
||||||
vi.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string, fallback?: string, options?: any) => {
|
|
||||||
if (key === 'sanitize.error' && options?.error) {
|
|
||||||
return `Sanitization failed: ${options.error}`;
|
|
||||||
}
|
|
||||||
if (key === 'error.noFilesSelected') {
|
|
||||||
return 'No files selected';
|
|
||||||
}
|
|
||||||
if (key === 'sanitize.error.generic') {
|
|
||||||
return 'Sanitization failed';
|
|
||||||
}
|
|
||||||
return fallback || key;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock FileContext
|
|
||||||
vi.mock('../../../contexts/FileContext', () => ({
|
|
||||||
useFileContext: () => ({
|
|
||||||
recordOperation: vi.fn(),
|
|
||||||
markOperationApplied: vi.fn(),
|
|
||||||
markOperationFailed: vi.fn(),
|
|
||||||
addFiles: vi.fn()
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock fetch
|
|
||||||
const mockFetch = vi.fn();
|
|
||||||
vi.stubGlobal('fetch', mockFetch);
|
|
||||||
|
|
||||||
// Mock URL.createObjectURL and revokeObjectURL
|
|
||||||
const mockCreateObjectURL = vi.fn(() => 'mock-blob-url');
|
|
||||||
const mockRevokeObjectURL = vi.fn();
|
|
||||||
vi.stubGlobal('URL', {
|
|
||||||
createObjectURL: mockCreateObjectURL,
|
|
||||||
revokeObjectURL: mockRevokeObjectURL
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('useSanitizeOperation', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should initialize with default state', () => {
|
|
||||||
const { result } = renderHook(() => useSanitizeOperation());
|
|
||||||
|
|
||||||
expect(result.current.isLoading).toBe(false);
|
|
||||||
expect(result.current.errorMessage).toBe(null);
|
|
||||||
expect(result.current.downloadUrl).toBe(null);
|
|
||||||
expect(result.current.status).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should execute sanitization operation successfully', async () => {
|
|
||||||
const mockBlob = new Blob(['test'], { type: 'application/pdf' });
|
|
||||||
const mockResponse = {
|
|
||||||
ok: true,
|
|
||||||
blob: () => Promise.resolve(mockBlob)
|
|
||||||
};
|
|
||||||
mockFetch.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useSanitizeOperation());
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
removeJavaScript: true,
|
|
||||||
removeEmbeddedFiles: false,
|
|
||||||
removeXMPMetadata: true,
|
|
||||||
removeMetadata: false,
|
|
||||||
removeLinks: false,
|
|
||||||
removeFonts: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.executeOperation(parameters, [testFile]);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockFetch).toHaveBeenCalledWith('/api/v1/security/sanitize-pdf', {
|
|
||||||
method: 'POST',
|
|
||||||
body: expect.any(FormData)
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.isLoading).toBe(false);
|
|
||||||
expect(result.current.downloadUrl).toBe('mock-blob-url');
|
|
||||||
expect(result.current.status).toBe('Sanitization completed successfully');
|
|
||||||
expect(result.current.errorMessage).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle API errors correctly', async () => {
|
|
||||||
const mockResponse = {
|
|
||||||
ok: false,
|
|
||||||
text: () => Promise.resolve('Server error')
|
|
||||||
};
|
|
||||||
mockFetch.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useSanitizeOperation());
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
removeJavaScript: true,
|
|
||||||
removeEmbeddedFiles: true,
|
|
||||||
removeXMPMetadata: false,
|
|
||||||
removeMetadata: false,
|
|
||||||
removeLinks: false,
|
|
||||||
removeFonts: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await expect(result.current.executeOperation(parameters, [testFile]))
|
|
||||||
.rejects.toThrow('Failed to sanitize all files: test.pdf');
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.isLoading).toBe(false);
|
|
||||||
expect(result.current.errorMessage).toBe('Failed to sanitize all files: test.pdf');
|
|
||||||
expect(result.current.downloadUrl).toBe(null);
|
|
||||||
expect(result.current.status).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle no files selected error', async () => {
|
|
||||||
const { result } = renderHook(() => useSanitizeOperation());
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
removeJavaScript: true,
|
|
||||||
removeEmbeddedFiles: true,
|
|
||||||
removeXMPMetadata: false,
|
|
||||||
removeMetadata: false,
|
|
||||||
removeLinks: false,
|
|
||||||
removeFonts: false
|
|
||||||
};
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await expect(result.current.executeOperation(parameters, []))
|
|
||||||
.rejects.toThrow('No files selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockFetch).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should send correct form data to API', async () => {
|
|
||||||
const mockBlob = new Blob(['test'], { type: 'application/pdf' });
|
|
||||||
const mockResponse = {
|
|
||||||
ok: true,
|
|
||||||
blob: () => Promise.resolve(mockBlob)
|
|
||||||
};
|
|
||||||
mockFetch.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useSanitizeOperation());
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
removeJavaScript: true,
|
|
||||||
removeEmbeddedFiles: false,
|
|
||||||
removeXMPMetadata: true,
|
|
||||||
removeMetadata: false,
|
|
||||||
removeLinks: true,
|
|
||||||
removeFonts: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.executeOperation(parameters, [testFile]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const [url, options] = mockFetch.mock.calls[0];
|
|
||||||
expect(url).toBe('/api/v1/security/sanitize-pdf');
|
|
||||||
expect(options.method).toBe('POST');
|
|
||||||
|
|
||||||
const formData = options.body as FormData;
|
|
||||||
expect(formData.get('removeJavaScript')).toBe('true');
|
|
||||||
expect(formData.get('removeEmbeddedFiles')).toBe('false');
|
|
||||||
expect(formData.get('removeXMPMetadata')).toBe('true');
|
|
||||||
expect(formData.get('removeMetadata')).toBe('false');
|
|
||||||
expect(formData.get('removeLinks')).toBe('true');
|
|
||||||
expect(formData.get('removeFonts')).toBe('false');
|
|
||||||
expect(formData.get('fileInput')).toBe(testFile);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should reset results correctly', () => {
|
|
||||||
const { result } = renderHook(() => useSanitizeOperation());
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
result.current.resetResults();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.downloadUrl).toBe(null);
|
|
||||||
expect(result.current.errorMessage).toBe(null);
|
|
||||||
expect(result.current.status).toBe(null);
|
|
||||||
expect(mockRevokeObjectURL).not.toHaveBeenCalled(); // No URL to revoke initially
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should clear error message', async () => {
|
|
||||||
// Mock a failed API response
|
|
||||||
const mockResponse = {
|
|
||||||
ok: false,
|
|
||||||
text: () => Promise.resolve('API Error')
|
|
||||||
};
|
|
||||||
mockFetch.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useSanitizeOperation());
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
removeJavaScript: true,
|
|
||||||
removeEmbeddedFiles: true,
|
|
||||||
removeXMPMetadata: false,
|
|
||||||
removeMetadata: false,
|
|
||||||
removeLinks: false,
|
|
||||||
removeFonts: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
|
|
||||||
|
|
||||||
// Trigger an API error
|
|
||||||
await act(async () => {
|
|
||||||
await expect(result.current.executeOperation(parameters, [testFile]))
|
|
||||||
.rejects.toThrow('Failed to sanitize all files: test.pdf');
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.errorMessage).toBe('Failed to sanitize all files: test.pdf');
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
result.current.clearError();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.errorMessage).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,268 +1,40 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFileContext } from '../../../contexts/FileContext';
|
import { useToolOperation } from '../shared/useToolOperation';
|
||||||
import { FileOperation } from '../../../types/fileContext';
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
import { generateThumbnailForFile } from '../../../utils/thumbnailUtils';
|
|
||||||
import { zipFileService } from '../../../services/zipFileService';
|
|
||||||
import { SanitizeParameters } from './useSanitizeParameters';
|
import { SanitizeParameters } from './useSanitizeParameters';
|
||||||
|
|
||||||
|
const buildFormData = (parameters: SanitizeParameters, file: File): FormData => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fileInput', file);
|
||||||
|
|
||||||
|
// Add parameters
|
||||||
|
formData.append('removeJavaScript', parameters.removeJavaScript.toString());
|
||||||
|
formData.append('removeEmbeddedFiles', parameters.removeEmbeddedFiles.toString());
|
||||||
|
formData.append('removeXMPMetadata', parameters.removeXMPMetadata.toString());
|
||||||
|
formData.append('removeMetadata', parameters.removeMetadata.toString());
|
||||||
|
formData.append('removeLinks', parameters.removeLinks.toString());
|
||||||
|
formData.append('removeFonts', parameters.removeFonts.toString());
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
export const useSanitizeOperation = () => {
|
export const useSanitizeOperation = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
|
||||||
recordOperation,
|
|
||||||
markOperationApplied,
|
|
||||||
markOperationFailed,
|
|
||||||
addFiles
|
|
||||||
} = useFileContext();
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
return useToolOperation<SanitizeParameters>({
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
operationType: 'sanitize',
|
||||||
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
|
endpoint: '/api/v1/security/sanitize-pdf',
|
||||||
const [downloadFilename, setDownloadFilename] = useState<string>('');
|
buildFormData,
|
||||||
const [status, setStatus] = useState<string | null>(null);
|
filePrefix: t('sanitize.filenamePrefix', 'sanitized') + '_',
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
multiFileEndpoint: false, // Individual API calls per file
|
||||||
const [thumbnails, setThumbnails] = useState<string[]>([]);
|
validateParams: (params) => {
|
||||||
const [isGeneratingThumbnails, setIsGeneratingThumbnails] = useState(false);
|
// At least one sanitization option must be selected
|
||||||
|
const hasAnyOption = Object.values(params).some(value => value === true);
|
||||||
const createOperation = useCallback((
|
if (!hasAnyOption) {
|
||||||
parameters: SanitizeParameters,
|
return { valid: false, errors: [t('sanitize.validation.atLeastOne', 'At least one sanitization option must be selected')] };
|
||||||
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 { valid: true };
|
||||||
|
},
|
||||||
return { operation, operationId, fileId };
|
getErrorMessage: createStandardErrorHandler(t('sanitize.error.failed', 'An error occurred while sanitizing the PDF.'))
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const buildFormData = useCallback((parameters: SanitizeParameters, file: File): FormData => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('fileInput', file);
|
|
||||||
|
|
||||||
// Add parameters
|
|
||||||
formData.append('removeJavaScript', parameters.removeJavaScript.toString());
|
|
||||||
formData.append('removeEmbeddedFiles', parameters.removeEmbeddedFiles.toString());
|
|
||||||
formData.append('removeXMPMetadata', parameters.removeXMPMetadata.toString());
|
|
||||||
formData.append('removeMetadata', parameters.removeMetadata.toString());
|
|
||||||
formData.append('removeLinks', parameters.removeLinks.toString());
|
|
||||||
formData.append('removeFonts', parameters.removeFonts.toString());
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const generateSanitizedFileName = (originalFileName: string): string => {
|
|
||||||
const baseName = originalFileName.replace(/\.[^/.]+$/, '');
|
|
||||||
const prefix = t('sanitize.filenamePrefix', 'sanitized');
|
|
||||||
return `${prefix}_${baseName}.pdf`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sanitizeFile = useCallback(async (
|
|
||||||
file: File,
|
|
||||||
parameters: SanitizeParameters,
|
|
||||||
operationId: string,
|
|
||||||
fileId: string
|
|
||||||
): Promise<File | null> => {
|
|
||||||
try {
|
|
||||||
const formData = buildFormData(parameters, file);
|
|
||||||
|
|
||||||
const response = await fetch('/api/v1/security/sanitize-pdf', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
markOperationFailed(fileId, operationId, errorText);
|
|
||||||
console.error(`Error sanitizing file ${file.name}:`, errorText);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = await response.blob();
|
|
||||||
const sanitizedFileName = generateSanitizedFileName(file.name);
|
|
||||||
const sanitizedFile = new File([blob], sanitizedFileName, { type: blob.type });
|
|
||||||
|
|
||||||
markOperationApplied(fileId, operationId);
|
|
||||||
return sanitizedFile;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error sanitizing file ${file.name}:`, error);
|
|
||||||
markOperationFailed(fileId, operationId, error instanceof Error ? error.message : 'Unknown error');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, [buildFormData, markOperationApplied, markOperationFailed]);
|
|
||||||
|
|
||||||
const createDownloadInfo = useCallback(async (results: File[]): Promise<void> => {
|
|
||||||
if (results.length === 1) {
|
|
||||||
const url = window.URL.createObjectURL(results[0]);
|
|
||||||
setDownloadUrl(url);
|
|
||||||
setDownloadFilename(results[0].name);
|
|
||||||
} else {
|
|
||||||
const zipFilename = `${t('sanitize.filenamePrefix', 'sanitized')}_files.zip`;
|
|
||||||
const { zipFile } = await zipFileService.createZipFromFiles(results, zipFilename);
|
|
||||||
const url = window.URL.createObjectURL(zipFile);
|
|
||||||
setDownloadUrl(url);
|
|
||||||
setDownloadFilename(zipFilename);
|
|
||||||
}
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const generateThumbnailsForResults = useCallback(async (results: File[]): Promise<void> => {
|
|
||||||
const thumbnails = await Promise.all(
|
|
||||||
results.map(async (file) => {
|
|
||||||
try {
|
|
||||||
const thumbnail = await generateThumbnailForFile(file);
|
|
||||||
return thumbnail || '';
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Failed to generate thumbnail for ${file.name}:`, error);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setThumbnails(thumbnails);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const processResults = useCallback(async (results: File[]): Promise<void> => {
|
|
||||||
setFiles(results);
|
|
||||||
setIsGeneratingThumbnails(true);
|
|
||||||
|
|
||||||
// Add sanitized files to FileContext for future use
|
|
||||||
await addFiles(results);
|
|
||||||
|
|
||||||
// Create download info - single file or ZIP
|
|
||||||
await createDownloadInfo(results);
|
|
||||||
|
|
||||||
// Generate thumbnails
|
|
||||||
await generateThumbnailsForResults(results);
|
|
||||||
|
|
||||||
setIsGeneratingThumbnails(false);
|
|
||||||
setStatus(results.length === 1
|
|
||||||
? t('sanitize.completed', 'Sanitization completed successfully')
|
|
||||||
: t('sanitize.completedMultiple', 'Sanitized {{count}} files successfully', { count: results.length })
|
|
||||||
);
|
|
||||||
}, [addFiles, createDownloadInfo, generateThumbnailsForResults, t]);
|
|
||||||
|
|
||||||
const executeOperation = useCallback(async (
|
|
||||||
parameters: SanitizeParameters,
|
|
||||||
selectedFiles: File[],
|
|
||||||
) => {
|
|
||||||
if (selectedFiles.length === 0) {
|
|
||||||
throw new Error(t('error.noFilesSelected', 'No files selected'));
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
setErrorMessage(null);
|
|
||||||
setStatus(selectedFiles.length === 1
|
|
||||||
? t('sanitize.processing', 'Sanitizing PDF...')
|
|
||||||
: t('sanitize.processingMultiple', 'Sanitizing {{count}} PDFs...', { count: selectedFiles.length })
|
|
||||||
);
|
|
||||||
|
|
||||||
const results: File[] = [];
|
|
||||||
const failedFiles: string[] = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Process each file separately
|
|
||||||
for (let i = 0; i < selectedFiles.length; i++) {
|
|
||||||
const file = selectedFiles[i];
|
|
||||||
const { operation, operationId, fileId } = createOperation(parameters, [file]);
|
|
||||||
recordOperation(fileId, operation);
|
|
||||||
|
|
||||||
setStatus(selectedFiles.length === 1
|
|
||||||
? t('sanitize.processing', 'Sanitizing PDF...')
|
|
||||||
: t('sanitize.processingFile', 'Processing file {{current}} of {{total}}: {{filename}}', {
|
|
||||||
current: i + 1,
|
|
||||||
total: selectedFiles.length,
|
|
||||||
filename: file.name
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const sanitizedFile = await sanitizeFile(file, parameters, operationId, fileId);
|
|
||||||
|
|
||||||
if (sanitizedFile) {
|
|
||||||
results.push(sanitizedFile);
|
|
||||||
} else {
|
|
||||||
failedFiles.push(file.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failedFiles.length > 0 && results.length === 0) {
|
|
||||||
throw new Error(`Failed to sanitize all files: ${failedFiles.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failedFiles.length > 0) {
|
|
||||||
setStatus(`Sanitized ${results.length}/${selectedFiles.length} files. Failed: ${failedFiles.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.length > 0) {
|
|
||||||
await processResults(results);
|
|
||||||
} else {
|
|
||||||
setErrorMessage(t('sanitize.errorAllFilesFailed', 'All files failed to sanitize'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in sanitization operation:', error);
|
|
||||||
const message = error instanceof Error ? error.message : t('sanitize.error.generic', 'Sanitization failed');
|
|
||||||
setErrorMessage(message);
|
|
||||||
setStatus(null);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}, [t, createOperation, recordOperation, sanitizeFile, processResults]);
|
|
||||||
|
|
||||||
const resetResults = useCallback(() => {
|
|
||||||
if (downloadUrl) {
|
|
||||||
URL.revokeObjectURL(downloadUrl);
|
|
||||||
}
|
|
||||||
setFiles([]);
|
|
||||||
setThumbnails([]);
|
|
||||||
setIsGeneratingThumbnails(false);
|
|
||||||
setDownloadUrl(null);
|
|
||||||
setDownloadFilename('');
|
|
||||||
setErrorMessage(null);
|
|
||||||
setStatus(null);
|
|
||||||
}, [downloadUrl]);
|
|
||||||
|
|
||||||
const clearError = useCallback(() => {
|
|
||||||
setErrorMessage(null);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Cleanup blob URLs on unmount to prevent memory leaks
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (downloadUrl) {
|
|
||||||
URL.revokeObjectURL(downloadUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [downloadUrl]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading,
|
|
||||||
errorMessage,
|
|
||||||
downloadUrl,
|
|
||||||
downloadFilename,
|
|
||||||
status,
|
|
||||||
files,
|
|
||||||
thumbnails,
|
|
||||||
isGeneratingThumbnails,
|
|
||||||
executeOperation,
|
|
||||||
resetResults,
|
|
||||||
clearError,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -186,7 +186,7 @@ export const useToolOperation = <TParams = void>(
|
|||||||
// Individual file processing - separate API call per file
|
// Individual file processing - separate API call per file
|
||||||
const apiCallsConfig: ApiCallsConfig<TParams> = {
|
const apiCallsConfig: ApiCallsConfig<TParams> = {
|
||||||
endpoint: config.endpoint,
|
endpoint: config.endpoint,
|
||||||
buildFormData: (file: File, params: TParams) => (config.buildFormData as any /* FIX ME */)(file, params),
|
buildFormData: (file: File, params: TParams) => (config.buildFormData as (params: TParams, file: File) => FormData /* FIX ME */)(params, file),
|
||||||
filePrefix: config.filePrefix,
|
filePrefix: config.filePrefix,
|
||||||
responseHandler: config.responseHandler
|
responseHandler: config.responseHandler
|
||||||
};
|
};
|
||||||
|
@ -70,7 +70,7 @@ const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolStepContainer>
|
<ToolStepContainer>
|
||||||
<Stack gap="sm" p="sm" style={{ height: '80vh', overflow: 'auto' }}>
|
<Stack gap="sm" p="sm" style={{ height: '100vh', overflow: 'auto' }}>
|
||||||
{/* Files Step */}
|
{/* Files Step */}
|
||||||
<ToolStep
|
<ToolStep
|
||||||
title={t('sanitize.steps.files', 'Files')}
|
title={t('sanitize.steps.files', 'Files')}
|
||||||
|
Loading…
Reference in New Issue
Block a user