Zip in common area

This commit is contained in:
Connor Yoh 2025-10-02 16:05:20 +01:00
parent 9a0503b33b
commit 13daa069ad
3 changed files with 96 additions and 21 deletions

View File

@ -3,7 +3,7 @@ import {
Text, Center, Box, LoadingOverlay, Stack, Group
} from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { useFileSelection, useFileState, useFileManagement } from '../../contexts/FileContext';
import { useFileSelection, useFileState, useFileManagement, useFileActions } from '../../contexts/FileContext';
import { useNavigationActions } from '../../contexts/NavigationContext';
import { zipFileService } from '../../services/zipFileService';
import { detectFileExtension } from '../../utils/fileUtils';
@ -37,6 +37,7 @@ const FileEditor = ({
// Use optimized FileContext hooks
const { state, selectors } = useFileState();
const { addFiles, removeFiles, reorderFiles } = useFileManagement();
const { actions } = useFileActions();
// Extract needed values from state (memoized to prevent infinite loops)
const activeStirlingFileStubs = useMemo(() => selectors.getStirlingFileStubs(), [selectors.getFilesSignature()]);
@ -314,19 +315,19 @@ const FileEditor = ({
const file = record ? selectors.getFile(record.id) : null;
if (record && file) {
try {
// Extract files from the ZIP
const extractionResult = await zipFileService.extractPdfFiles(file);
// Extract and store files using shared service method
const result = await zipFileService.extractAndStoreFilesWithHistory(file, record);
if (extractionResult.success && extractionResult.extractedFiles.length > 0) {
// Add extracted files to FileContext
await addFiles(extractionResult.extractedFiles);
if (result.success && result.extractedStubs.length > 0) {
// Add extracted file stubs to FileContext
await actions.addStirlingFileStubs(result.extractedStubs);
// Remove the original ZIP file
removeFiles([fileId], false);
alert({
alertType: 'success',
title: `Extracted ${extractionResult.extractedFiles.length} file(s) from ${file.name}`,
title: `Extracted ${result.extractedStubs.length} file(s) from ${file.name}`,
expandable: false,
durationMs: 3500
});
@ -334,7 +335,8 @@ const FileEditor = ({
alert({
alertType: 'error',
title: `Failed to extract files from ${file.name}`,
expandable: false,
body: result.errors.join('\n'),
expandable: true,
durationMs: 3500
});
}
@ -348,7 +350,7 @@ const FileEditor = ({
});
}
}
}, [activeStirlingFileStubs, selectors, addFiles, removeFiles]);
}, [activeStirlingFileStubs, selectors, actions, removeFiles]);
const handleViewFile = useCallback((fileId: FileId) => {
const record = activeStirlingFileStubs.find(r => r.id === fileId);

View File

@ -554,23 +554,21 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
return;
}
// Extract files from the ZIP
const extractionResult = await zipFileService.extractPdfFiles(stirlingFile);
// Extract and store files using shared service method
const result = await zipFileService.extractAndStoreFilesWithHistory(stirlingFile, file);
if (extractionResult.success && extractionResult.extractedFiles.length > 0) {
// Add extracted files to the file manager
onNewFilesSelect(extractionResult.extractedFiles);
if (result.success) {
// Refresh file manager to show new files
await refreshRecentFiles();
}
// Optionally remove the original ZIP file
const fileIndex = filteredFiles.findIndex(f => f.id === file.id);
if (fileIndex !== -1) {
await handleFileRemove(fileIndex);
}
if (result.errors.length > 0) {
console.error('Errors during unzip:', result.errors);
}
} catch (error) {
console.error('Failed to unzip file:', error);
}
}, [onNewFilesSelect, filteredFiles, handleFileRemove]);
}, [refreshRecentFiles]);
// Cleanup blob URLs when component unmounts
useEffect(() => {

View File

@ -1,5 +1,7 @@
import JSZip, { JSZipObject } from 'jszip';
import { StirlingFileStub } from '../types/fileContext';
import { StirlingFileStub, createStirlingFile } from '../types/fileContext';
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
import { fileStorage } from './fileStorage';
// Undocumented interface in JSZip for JSZipObject._data
interface CompressedObject {
@ -440,6 +442,79 @@ export class ZipFileService {
return mimeTypes[ext || ''] || 'application/octet-stream';
}
/**
* Extract PDF files from ZIP and store them in IndexedDB with preserved history metadata
* Used by both FileManager and FileEditor to avoid code duplication
*
* @param zipFile - The ZIP file to extract from
* @param zipStub - The StirlingFileStub for the ZIP (contains metadata to preserve)
* @returns Object with success status, extracted stubs, and any errors
*/
async extractAndStoreFilesWithHistory(
zipFile: File,
zipStub: StirlingFileStub
): Promise<{ success: boolean; extractedStubs: StirlingFileStub[]; errors: string[] }> {
const result = {
success: false,
extractedStubs: [] as StirlingFileStub[],
errors: [] as string[]
};
try {
// Extract PDF files from ZIP
const extractionResult = await this.extractPdfFiles(zipFile);
if (!extractionResult.success || extractionResult.extractedFiles.length === 0) {
result.errors = extractionResult.errors;
return result;
}
// Process each extracted file
for (const extractedFile of extractionResult.extractedFiles) {
try {
// Generate thumbnail
const thumbnail = await generateThumbnailForFile(extractedFile);
// Create StirlingFile
const newStirlingFile = createStirlingFile(extractedFile);
// Create StirlingFileStub with ZIP's history metadata
const stub: StirlingFileStub = {
id: newStirlingFile.fileId,
name: extractedFile.name,
size: extractedFile.size,
type: extractedFile.type,
lastModified: extractedFile.lastModified,
quickKey: newStirlingFile.quickKey,
createdAt: Date.now(),
isLeaf: true,
// Preserve ZIP's history - unzipping is NOT a tool operation
originalFileId: zipStub.originalFileId,
parentFileId: zipStub.parentFileId,
versionNumber: zipStub.versionNumber,
toolHistory: zipStub.toolHistory || [],
thumbnailUrl: thumbnail
};
// Store in IndexedDB
await fileStorage.storeStirlingFile(newStirlingFile, stub);
result.extractedStubs.push(stub);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
result.errors.push(`Failed to process "${extractedFile.name}": ${errorMessage}`);
}
}
result.success = result.extractedStubs.length > 0;
return result;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
result.errors.push(`Failed to extract ZIP file: ${errorMessage}`);
return result;
}
}
}
// Export singleton instance