mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Reorder files, filecontext in fileeditor
This commit is contained in:
parent
a260d72925
commit
f1246e3ab0
@ -17,15 +17,6 @@ import FileThumbnail from '../pageEditor/FileThumbnail';
|
|||||||
import FilePickerModal from '../shared/FilePickerModal';
|
import FilePickerModal from '../shared/FilePickerModal';
|
||||||
import SkeletonLoader from '../shared/SkeletonLoader';
|
import SkeletonLoader from '../shared/SkeletonLoader';
|
||||||
|
|
||||||
interface FileItem {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
pageCount: number;
|
|
||||||
thumbnail: string;
|
|
||||||
size: number;
|
|
||||||
file: File;
|
|
||||||
splitBefore?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FileEditorProps {
|
interface FileEditorProps {
|
||||||
onOpenPageEditor?: (file: File) => void;
|
onOpenPageEditor?: (file: File) => void;
|
||||||
@ -54,7 +45,7 @@ const FileEditor = ({
|
|||||||
|
|
||||||
// Use optimized FileContext hooks
|
// Use optimized FileContext hooks
|
||||||
const { state, selectors } = useFileState();
|
const { state, selectors } = useFileState();
|
||||||
const { addFiles, removeFiles } = useFileManagement();
|
const { addFiles, removeFiles, reorderFiles } = useFileManagement();
|
||||||
const processedFiles = useProcessedFiles(); // Now gets real processed files
|
const processedFiles = useProcessedFiles(); // Now gets real processed files
|
||||||
|
|
||||||
// Extract needed values from state (memoized to prevent infinite loops)
|
// Extract needed values from state (memoized to prevent infinite loops)
|
||||||
@ -86,7 +77,6 @@ const FileEditor = ({
|
|||||||
|
|
||||||
const setCurrentView = (mode: any) => {
|
const setCurrentView = (mode: any) => {
|
||||||
// Will be handled by parent component actions
|
// Will be handled by parent component actions
|
||||||
console.log('FileEditor setCurrentView called with:', mode);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get tool file selection context (replaces FileSelectionContext)
|
// Get tool file selection context (replaces FileSelectionContext)
|
||||||
@ -97,10 +87,8 @@ const FileEditor = ({
|
|||||||
isToolMode
|
isToolMode
|
||||||
} = useToolFileSelection();
|
} = useToolFileSelection();
|
||||||
|
|
||||||
const [files, setFiles] = useState<FileItem[]>([]);
|
|
||||||
const [status, setStatus] = useState<string | null>(null);
|
const [status, setStatus] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [localLoading, setLocalLoading] = useState(false);
|
|
||||||
const [selectionMode, setSelectionMode] = useState(toolMode);
|
const [selectionMode, setSelectionMode] = useState(toolMode);
|
||||||
|
|
||||||
// Enable selection mode automatically in tool mode
|
// Enable selection mode automatically in tool mode
|
||||||
@ -110,7 +98,6 @@ const FileEditor = ({
|
|||||||
}
|
}
|
||||||
}, [toolMode]);
|
}, [toolMode]);
|
||||||
const [showFilePickerModal, setShowFilePickerModal] = useState(false);
|
const [showFilePickerModal, setShowFilePickerModal] = useState(false);
|
||||||
const [conversionProgress, setConversionProgress] = useState(0);
|
|
||||||
const [zipExtractionProgress, setZipExtractionProgress] = useState<{
|
const [zipExtractionProgress, setZipExtractionProgress] = useState<{
|
||||||
isExtracting: boolean;
|
isExtracting: boolean;
|
||||||
currentFile: string;
|
currentFile: string;
|
||||||
@ -124,118 +111,30 @@ const FileEditor = ({
|
|||||||
extractedCount: 0,
|
extractedCount: 0,
|
||||||
totalFiles: 0
|
totalFiles: 0
|
||||||
});
|
});
|
||||||
const lastActiveFilesRef = useRef<string[]>([]);
|
|
||||||
const lastProcessedFilesRef = useRef<number>(0);
|
|
||||||
|
|
||||||
// Get selected file IDs from context (defensive programming)
|
// Get selected file IDs from context (defensive programming)
|
||||||
const contextSelectedIds = Array.isArray(selectedFileIds) ? selectedFileIds : [];
|
const contextSelectedIds = Array.isArray(selectedFileIds) ? selectedFileIds : [];
|
||||||
|
|
||||||
// Create refs for frequently changing values to stabilize callbacks
|
// Create refs for frequently changing values to stabilize callbacks
|
||||||
const contextSelectedIdsRef = useRef<string[]>([]);
|
const contextSelectedIdsRef = useRef<string[]>([]);
|
||||||
const filesDataRef = useRef<any[]>([]);
|
|
||||||
contextSelectedIdsRef.current = contextSelectedIds;
|
contextSelectedIdsRef.current = contextSelectedIds;
|
||||||
filesDataRef.current = files;
|
|
||||||
|
|
||||||
// Map context selections to local file IDs for UI display
|
// Use activeFileRecords directly - no conversion needed
|
||||||
const localSelectedIds = files
|
const localSelectedIds = contextSelectedIds;
|
||||||
.filter(file => {
|
|
||||||
// file.id is already the correct UUID from FileContext
|
|
||||||
return contextSelectedIds.includes(file.id);
|
|
||||||
})
|
|
||||||
.map(file => file.id);
|
|
||||||
|
|
||||||
// Convert shared files to FileEditor format
|
// Helper to convert FileRecord to FileThumbnail format
|
||||||
const convertToFileItem = useCallback(async (sharedFile: any): Promise<FileItem> => {
|
const recordToFileItem = useCallback((record: any) => {
|
||||||
// Use processed data if available, otherwise fallback to legacy approach
|
const file = selectors.getFile(record.id);
|
||||||
const thumbnail = sharedFile.thumbnail || sharedFile.thumbnailUrl ||
|
if (!file) return null;
|
||||||
(await generateThumbnailForFile(sharedFile.file || sharedFile));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: sharedFile.id || `file-${Date.now()}-${Math.random()}`,
|
id: record.id,
|
||||||
name: (sharedFile.file?.name || sharedFile.name || 'unknown'),
|
|
||||||
pageCount: sharedFile.processedFile?.totalPages || sharedFile.pageCount || 1,
|
|
||||||
thumbnail: thumbnail || '',
|
|
||||||
size: sharedFile.file?.size || sharedFile.size || 0,
|
|
||||||
file: sharedFile.file || sharedFile,
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Convert activeFiles to FileItem format using context (async to avoid blocking)
|
|
||||||
useEffect(() => {
|
|
||||||
// Check if the actual content has changed, not just references
|
|
||||||
const currentActiveFileIds = activeFileRecords.map(r => r.id);
|
|
||||||
const currentProcessedFilesSize = processedFiles.processedFiles.size;
|
|
||||||
|
|
||||||
const activeFilesChanged = JSON.stringify(currentActiveFileIds) !== JSON.stringify(lastActiveFilesRef.current);
|
|
||||||
const processedFilesChanged = currentProcessedFilesSize !== lastProcessedFilesRef.current;
|
|
||||||
|
|
||||||
if (!activeFilesChanged && !processedFilesChanged) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Update refs
|
|
||||||
lastActiveFilesRef.current = currentActiveFileIds;
|
|
||||||
lastProcessedFilesRef.current = currentProcessedFilesSize;
|
|
||||||
|
|
||||||
|
|
||||||
const convertActiveFiles = async () => {
|
|
||||||
|
|
||||||
if (activeFileRecords.length > 0) {
|
|
||||||
setLocalLoading(true);
|
|
||||||
try {
|
|
||||||
// Process files in chunks to avoid blocking UI
|
|
||||||
const convertedFiles: FileItem[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < activeFileRecords.length; i++) {
|
|
||||||
const record = activeFileRecords[i];
|
|
||||||
const file = selectors.getFile(record.id);
|
|
||||||
|
|
||||||
if (!file) continue; // Skip if file not found
|
|
||||||
|
|
||||||
// Use processed data from centralized file processing service
|
|
||||||
const thumbnail = record.thumbnailUrl; // Already processed by FileProcessingService
|
|
||||||
const pageCount = record.processedFile?.totalPages || 1; // Use processed page count
|
|
||||||
|
|
||||||
console.log(`📄 FileEditor: Using processed data for ${file.name}: ${pageCount} pages, thumbnail: ${!!thumbnail}`);
|
|
||||||
|
|
||||||
const convertedFile = {
|
|
||||||
id: record.id, // Use the record's UUID from FileContext
|
|
||||||
name: file.name,
|
name: file.name,
|
||||||
pageCount: pageCount,
|
pageCount: record.processedFile?.totalPages || 1,
|
||||||
thumbnail: thumbnail || '',
|
thumbnail: record.thumbnailUrl || '',
|
||||||
size: file.size,
|
size: file.size,
|
||||||
file,
|
file: file
|
||||||
};
|
};
|
||||||
|
}, [selectors]);
|
||||||
convertedFiles.push(convertedFile);
|
|
||||||
|
|
||||||
// Update progress
|
|
||||||
setConversionProgress(((i + 1) / activeFileRecords.length) * 100);
|
|
||||||
|
|
||||||
// Yield to main thread between files
|
|
||||||
if (i < activeFileRecords.length - 1) {
|
|
||||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setFiles(convertedFiles);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error converting active files:', err);
|
|
||||||
} finally {
|
|
||||||
setLocalLoading(false);
|
|
||||||
setConversionProgress(0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setFiles([]);
|
|
||||||
setLocalLoading(false);
|
|
||||||
setConversionProgress(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
convertActiveFiles();
|
|
||||||
}, [activeFileRecords, processedFiles, selectors]);
|
|
||||||
|
|
||||||
|
|
||||||
// Process uploaded files using context
|
// Process uploaded files using context
|
||||||
@ -316,7 +215,6 @@ const FileEditor = ({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ZIP doesn't contain PDFs or is invalid - treat as regular file
|
// ZIP doesn't contain PDFs or is invalid - treat as regular file
|
||||||
console.log(`Adding ZIP file as regular file: ${file.name} (no PDFs found)`);
|
|
||||||
allExtractedFiles.push(file);
|
allExtractedFiles.push(file);
|
||||||
}
|
}
|
||||||
} catch (zipError) {
|
} catch (zipError) {
|
||||||
@ -330,7 +228,6 @@ const FileEditor = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`Adding none PDF file: ${file.name} (${file.type})`);
|
|
||||||
allExtractedFiles.push(file);
|
allExtractedFiles.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -382,8 +279,8 @@ const FileEditor = ({
|
|||||||
}, [addFiles]);
|
}, [addFiles]);
|
||||||
|
|
||||||
const selectAll = useCallback(() => {
|
const selectAll = useCallback(() => {
|
||||||
setContextSelectedFiles(files.map(f => f.id)); // Use FileEditor file IDs which are now correct UUIDs
|
setContextSelectedFiles(activeFileRecords.map(r => r.id)); // Use FileRecord IDs directly
|
||||||
}, [files, setContextSelectedFiles]);
|
}, [activeFileRecords, setContextSelectedFiles]);
|
||||||
|
|
||||||
const deselectAll = useCallback(() => setContextSelectedFiles([]), [setContextSelectedFiles]);
|
const deselectAll = useCallback(() => setContextSelectedFiles([]), [setContextSelectedFiles]);
|
||||||
|
|
||||||
@ -398,11 +295,10 @@ const FileEditor = ({
|
|||||||
}, [activeFileRecords, removeFiles, setContextSelectedFiles]);
|
}, [activeFileRecords, removeFiles, setContextSelectedFiles]);
|
||||||
|
|
||||||
const toggleFile = useCallback((fileId: string) => {
|
const toggleFile = useCallback((fileId: string) => {
|
||||||
const currentFiles = filesDataRef.current;
|
|
||||||
const currentSelectedIds = contextSelectedIdsRef.current;
|
const currentSelectedIds = contextSelectedIdsRef.current;
|
||||||
|
|
||||||
const targetFile = currentFiles.find(f => f.id === fileId);
|
const targetRecord = activeFileRecords.find(r => r.id === fileId);
|
||||||
if (!targetFile) return;
|
if (!targetRecord) return;
|
||||||
|
|
||||||
const contextFileId = fileId; // No need to create a new ID
|
const contextFileId = fileId; // No need to create a new ID
|
||||||
const isSelected = currentSelectedIds.includes(contextFileId);
|
const isSelected = currentSelectedIds.includes(contextFileId);
|
||||||
@ -433,7 +329,7 @@ const FileEditor = ({
|
|||||||
if (isToolMode || toolMode) {
|
if (isToolMode || toolMode) {
|
||||||
setToolSelectedFiles(newSelection);
|
setToolSelectedFiles(newSelection);
|
||||||
}
|
}
|
||||||
}, [setContextSelectedFiles, maxFiles, setStatus, isToolMode, toolMode, setToolSelectedFiles]); // Removed changing dependencies
|
}, [setContextSelectedFiles, maxFiles, setStatus, isToolMode, toolMode, setToolSelectedFiles, activeFileRecords]);
|
||||||
|
|
||||||
const toggleSelectionMode = useCallback(() => {
|
const toggleSelectionMode = useCallback(() => {
|
||||||
setSelectionMode(prev => {
|
setSelectionMode(prev => {
|
||||||
@ -447,70 +343,71 @@ const FileEditor = ({
|
|||||||
|
|
||||||
// File reordering handler for drag and drop
|
// File reordering handler for drag and drop
|
||||||
const handleReorderFiles = useCallback((sourceFileId: string, targetFileId: string, selectedFileIds: string[]) => {
|
const handleReorderFiles = useCallback((sourceFileId: string, targetFileId: string, selectedFileIds: string[]) => {
|
||||||
setFiles(prevFiles => {
|
const currentIds = activeFileRecords.map(r => r.id);
|
||||||
const newFiles = [...prevFiles];
|
|
||||||
|
|
||||||
// Find original source and target indices
|
// Find indices
|
||||||
const sourceIndex = newFiles.findIndex(f => f.id === sourceFileId);
|
const sourceIndex = currentIds.findIndex(id => id === sourceFileId);
|
||||||
const targetIndex = newFiles.findIndex(f => f.id === targetFileId);
|
const targetIndex = currentIds.findIndex(id => id === targetFileId);
|
||||||
|
|
||||||
if (sourceIndex === -1 || targetIndex === -1) {
|
if (sourceIndex === -1 || targetIndex === -1) {
|
||||||
console.warn('Could not find source or target file for reordering');
|
console.warn('Could not find source or target file for reordering');
|
||||||
return prevFiles;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle multi-file selection reordering
|
// Handle multi-file selection reordering
|
||||||
const filesToMove = selectedFileIds.length > 1
|
const filesToMove = selectedFileIds.length > 1
|
||||||
? selectedFileIds.map(id => newFiles.find(f => f.id === id)!).filter(Boolean)
|
? selectedFileIds.filter(id => currentIds.includes(id))
|
||||||
: [newFiles[sourceIndex]];
|
: [sourceFileId];
|
||||||
|
|
||||||
// Calculate the correct target position before removing files
|
// Create new order
|
||||||
let insertIndex = targetIndex;
|
const newOrder = [...currentIds];
|
||||||
|
|
||||||
// If we're moving forward (right), we need to adjust for the files we're removing
|
|
||||||
const sourceIndices = filesToMove.map(f => newFiles.findIndex(nf => nf.id === f.id));
|
|
||||||
const minSourceIndex = Math.min(...sourceIndices);
|
|
||||||
|
|
||||||
if (minSourceIndex < targetIndex) {
|
|
||||||
// Moving forward: target moves left by the number of files we're removing before it
|
|
||||||
const filesBeforeTarget = sourceIndices.filter(idx => idx < targetIndex).length;
|
|
||||||
insertIndex = targetIndex - filesBeforeTarget + 1; // +1 to insert after target
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove files to move from their current positions (in reverse order to maintain indices)
|
// Remove files to move from their current positions (in reverse order to maintain indices)
|
||||||
sourceIndices
|
const sourceIndices = filesToMove.map(id => newOrder.findIndex(nId => nId === id))
|
||||||
.sort((a, b) => b - a) // Sort descending to remove from end first
|
.sort((a, b) => b - a); // Sort descending
|
||||||
.forEach(index => {
|
|
||||||
newFiles.splice(index, 1);
|
sourceIndices.forEach(index => {
|
||||||
|
newOrder.splice(index, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Calculate insertion index after removals
|
||||||
|
let insertIndex = newOrder.findIndex(id => id === targetFileId);
|
||||||
|
if (insertIndex !== -1) {
|
||||||
|
// Determine if moving forward or backward
|
||||||
|
const isMovingForward = sourceIndex < targetIndex;
|
||||||
|
if (isMovingForward) {
|
||||||
|
// Moving forward: insert after target
|
||||||
|
insertIndex += 1;
|
||||||
|
} else {
|
||||||
|
// Moving backward: insert before target (insertIndex already correct)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Target was moved, insert at end
|
||||||
|
insertIndex = newOrder.length;
|
||||||
|
}
|
||||||
|
|
||||||
// Insert files at the calculated position
|
// Insert files at the calculated position
|
||||||
newFiles.splice(insertIndex, 0, ...filesToMove);
|
newOrder.splice(insertIndex, 0, ...filesToMove);
|
||||||
|
|
||||||
|
// Update file order
|
||||||
|
reorderFiles(newOrder);
|
||||||
|
|
||||||
// Update status
|
// Update status
|
||||||
const moveCount = filesToMove.length;
|
const moveCount = filesToMove.length;
|
||||||
setStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`);
|
setStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`);
|
||||||
|
}, [activeFileRecords, reorderFiles, setStatus]);
|
||||||
return newFiles;
|
|
||||||
});
|
|
||||||
}, [setStatus]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// File operations using context
|
// File operations using context
|
||||||
const handleDeleteFile = useCallback((fileId: string) => {
|
const handleDeleteFile = useCallback((fileId: string) => {
|
||||||
console.log('handleDeleteFile called with fileId:', fileId);
|
const record = activeFileRecords.find(r => r.id === fileId);
|
||||||
const file = files.find(f => f.id === fileId);
|
const file = record ? selectors.getFile(record.id) : null;
|
||||||
console.log('Found file:', file);
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
console.log('Attempting to remove file:', file.name);
|
|
||||||
console.log('Actual file object:', file.file);
|
|
||||||
console.log('Actual file.file.name:', file.file.name);
|
|
||||||
|
|
||||||
|
if (record && file) {
|
||||||
// Record close operation
|
// Record close operation
|
||||||
const fileName = file.file.name;
|
const fileName = file.name;
|
||||||
const contextFileId = file.id; // Use the correct file ID (UUID from FileContext)
|
const contextFileId = record.id;
|
||||||
const operationId = `close-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
const operationId = `close-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
const operation: FileOperation = {
|
const operation: FileOperation = {
|
||||||
id: operationId,
|
id: operationId,
|
||||||
@ -520,7 +417,7 @@ const FileEditor = ({
|
|||||||
status: 'pending',
|
status: 'pending',
|
||||||
metadata: {
|
metadata: {
|
||||||
originalFileName: fileName,
|
originalFileName: fileName,
|
||||||
fileSize: file.size,
|
fileSize: record.size,
|
||||||
parameters: {
|
parameters: {
|
||||||
action: 'close',
|
action: 'close',
|
||||||
reason: 'user_request'
|
reason: 'user_request'
|
||||||
@ -529,7 +426,6 @@ const FileEditor = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Remove file from context but keep in storage (close, don't delete)
|
// Remove file from context but keep in storage (close, don't delete)
|
||||||
console.log('Calling removeFiles with:', [contextFileId]);
|
|
||||||
removeFiles([contextFileId], false);
|
removeFiles([contextFileId], false);
|
||||||
|
|
||||||
// Remove from context selections
|
// Remove from context selections
|
||||||
@ -537,55 +433,48 @@ const FileEditor = ({
|
|||||||
const safePrev = Array.isArray(prev) ? prev : [];
|
const safePrev = Array.isArray(prev) ? prev : [];
|
||||||
return safePrev.filter(id => id !== contextFileId);
|
return safePrev.filter(id => id !== contextFileId);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
console.log('File not found for fileId:', fileId);
|
|
||||||
}
|
}
|
||||||
}, [files, removeFiles, setContextSelectedFiles]);
|
}, [activeFileRecords, selectors, removeFiles, setContextSelectedFiles]);
|
||||||
|
|
||||||
const handleViewFile = useCallback((fileId: string) => {
|
const handleViewFile = useCallback((fileId: string) => {
|
||||||
const file = files.find(f => f.id === fileId);
|
const record = activeFileRecords.find(r => r.id === fileId);
|
||||||
if (file) {
|
if (record) {
|
||||||
// Set the file as selected in context and switch to viewer for preview
|
// Set the file as selected in context and switch to viewer for preview
|
||||||
const contextFileId = file.id; // Use the correct file ID (UUID from FileContext)
|
setContextSelectedFiles([fileId]);
|
||||||
setContextSelectedFiles([contextFileId]);
|
|
||||||
setCurrentView('viewer');
|
setCurrentView('viewer');
|
||||||
}
|
}
|
||||||
}, [files, setContextSelectedFiles, setCurrentView]);
|
}, [activeFileRecords, setContextSelectedFiles, setCurrentView]);
|
||||||
|
|
||||||
const handleMergeFromHere = useCallback((fileId: string) => {
|
const handleMergeFromHere = useCallback((fileId: string) => {
|
||||||
const startIndex = files.findIndex(f => f.id === fileId);
|
const startIndex = activeFileRecords.findIndex(r => r.id === fileId);
|
||||||
if (startIndex === -1) return;
|
if (startIndex === -1) return;
|
||||||
|
|
||||||
const filesToMerge = files.slice(startIndex).map(f => f.file);
|
const recordsToMerge = activeFileRecords.slice(startIndex);
|
||||||
|
const filesToMerge = recordsToMerge.map(r => selectors.getFile(r.id)).filter(Boolean) as File[];
|
||||||
if (onMergeFiles) {
|
if (onMergeFiles) {
|
||||||
onMergeFiles(filesToMerge);
|
onMergeFiles(filesToMerge);
|
||||||
}
|
}
|
||||||
}, [files, onMergeFiles]);
|
}, [activeFileRecords, selectors, onMergeFiles]);
|
||||||
|
|
||||||
const handleSplitFile = useCallback((fileId: string) => {
|
const handleSplitFile = useCallback((fileId: string) => {
|
||||||
const file = files.find(f => f.id === fileId);
|
const file = selectors.getFile(fileId);
|
||||||
if (file && onOpenPageEditor) {
|
if (file && onOpenPageEditor) {
|
||||||
onOpenPageEditor(file.file);
|
onOpenPageEditor(file);
|
||||||
}
|
}
|
||||||
}, [files, onOpenPageEditor]);
|
}, [selectors, onOpenPageEditor]);
|
||||||
|
|
||||||
const handleLoadFromStorage = useCallback(async (selectedFiles: any[]) => {
|
const handleLoadFromStorage = useCallback(async (selectedFiles: any[]) => {
|
||||||
if (selectedFiles.length === 0) return;
|
if (selectedFiles.length === 0) return;
|
||||||
|
|
||||||
setLocalLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const convertedFiles = await Promise.all(
|
// Use FileContext to handle loading stored files
|
||||||
selectedFiles.map(convertToFileItem)
|
// The files are already in FileContext, just need to add them to active files
|
||||||
);
|
|
||||||
setFiles(prev => [...prev, ...convertedFiles]);
|
|
||||||
setStatus(`Loaded ${selectedFiles.length} files from storage`);
|
setStatus(`Loaded ${selectedFiles.length} files from storage`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading files from storage:', err);
|
console.error('Error loading files from storage:', err);
|
||||||
setError('Failed to load some files from storage');
|
setError('Failed to load some files from storage');
|
||||||
} finally {
|
|
||||||
setLocalLoading(false);
|
|
||||||
}
|
}
|
||||||
}, [convertToFileItem]);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -619,7 +508,7 @@ const FileEditor = ({
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
|
||||||
{files.length === 0 && !localLoading && !zipExtractionProgress.isExtracting ? (
|
{activeFileRecords.length === 0 && !zipExtractionProgress.isExtracting ? (
|
||||||
<Center h="60vh">
|
<Center h="60vh">
|
||||||
<Stack align="center" gap="md">
|
<Stack align="center" gap="md">
|
||||||
<Text size="lg" c="dimmed">📁</Text>
|
<Text size="lg" c="dimmed">📁</Text>
|
||||||
@ -627,7 +516,7 @@ const FileEditor = ({
|
|||||||
<Text size="sm" c="dimmed">Upload PDF files, ZIP archives, or load from storage to get started</Text>
|
<Text size="sm" c="dimmed">Upload PDF files, ZIP archives, or load from storage to get started</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
) : files.length === 0 && (localLoading || zipExtractionProgress.isExtracting) ? (
|
) : activeFileRecords.length === 0 && zipExtractionProgress.isExtracting ? (
|
||||||
<Box>
|
<Box>
|
||||||
<SkeletonLoader type="controls" />
|
<SkeletonLoader type="controls" />
|
||||||
|
|
||||||
@ -661,29 +550,6 @@ const FileEditor = ({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Processing indicator */}
|
|
||||||
{localLoading && (
|
|
||||||
<Box mb="md" p="sm" style={{ backgroundColor: 'var(--mantine-color-blue-0)', borderRadius: 8 }}>
|
|
||||||
<Group justify="space-between" mb="xs">
|
|
||||||
<Text size="sm" fw={500}>Loading files...</Text>
|
|
||||||
<Text size="sm" c="dimmed">{Math.round(conversionProgress)}%</Text>
|
|
||||||
</Group>
|
|
||||||
<div style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '4px',
|
|
||||||
backgroundColor: 'var(--mantine-color-gray-2)',
|
|
||||||
borderRadius: '2px',
|
|
||||||
overflow: 'hidden'
|
|
||||||
}}>
|
|
||||||
<div style={{
|
|
||||||
width: `${Math.round(conversionProgress)}%`,
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: 'var(--mantine-color-blue-6)',
|
|
||||||
transition: 'width 0.3s ease'
|
|
||||||
}} />
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SkeletonLoader type="fileGrid" count={6} />
|
<SkeletonLoader type="fileGrid" count={6} />
|
||||||
</Box>
|
</Box>
|
||||||
@ -697,12 +563,16 @@ const FileEditor = ({
|
|||||||
pointerEvents: 'auto'
|
pointerEvents: 'auto'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{files.map((file, index) => (
|
{activeFileRecords.map((record, index) => {
|
||||||
|
const fileItem = recordToFileItem(record);
|
||||||
|
if (!fileItem) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
<FileThumbnail
|
<FileThumbnail
|
||||||
key={file.id}
|
key={record.id}
|
||||||
file={file}
|
file={fileItem}
|
||||||
index={index}
|
index={index}
|
||||||
totalFiles={files.length}
|
totalFiles={activeFileRecords.length}
|
||||||
selectedFiles={localSelectedIds}
|
selectedFiles={localSelectedIds}
|
||||||
selectionMode={selectionMode}
|
selectionMode={selectionMode}
|
||||||
onToggleFile={toggleFile}
|
onToggleFile={toggleFile}
|
||||||
@ -711,9 +581,10 @@ const FileEditor = ({
|
|||||||
onSetStatus={setStatus}
|
onSetStatus={setStatus}
|
||||||
onReorderFiles={handleReorderFiles}
|
onReorderFiles={handleReorderFiles}
|
||||||
toolMode={toolMode}
|
toolMode={toolMode}
|
||||||
isSupported={isFileSupported(file.name)}
|
isSupported={isFileSupported(fileItem.name)}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -159,6 +159,9 @@ function FileContextInner({
|
|||||||
},
|
},
|
||||||
updateFileRecord: (fileId: FileId, updates: Partial<FileRecord>) =>
|
updateFileRecord: (fileId: FileId, updates: Partial<FileRecord>) =>
|
||||||
lifecycleManager.updateFileRecord(fileId, updates, stateRef),
|
lifecycleManager.updateFileRecord(fileId, updates, stateRef),
|
||||||
|
reorderFiles: (orderedFileIds: FileId[]) => {
|
||||||
|
dispatch({ type: 'REORDER_FILES', payload: { orderedFileIds } });
|
||||||
|
},
|
||||||
clearAllFiles: async () => {
|
clearAllFiles: async () => {
|
||||||
lifecycleManager.cleanupAllFiles();
|
lifecycleManager.cleanupAllFiles();
|
||||||
filesRef.current.clear();
|
filesRef.current.clear();
|
||||||
|
@ -99,6 +99,21 @@ export function fileContextReducer(state: FileContextState, action: FileContextA
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'REORDER_FILES': {
|
||||||
|
const { orderedFileIds } = action.payload;
|
||||||
|
|
||||||
|
// Validate that all IDs exist in current state
|
||||||
|
const validIds = orderedFileIds.filter(id => state.files.byId[id]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
files: {
|
||||||
|
...state.files,
|
||||||
|
ids: validIds
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_SELECTED_FILES': {
|
case 'SET_SELECTED_FILES': {
|
||||||
const { fileIds } = action.payload;
|
const { fileIds } = action.payload;
|
||||||
return {
|
return {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* New performant file hooks - Clean API without legacy compatibility
|
* Performant file hooks - Clean API using FileContext
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useContext, useMemo } from 'react';
|
import { useContext, useMemo } from 'react';
|
||||||
@ -79,7 +79,8 @@ export function useFileManagement() {
|
|||||||
addFiles: actions.addFiles,
|
addFiles: actions.addFiles,
|
||||||
removeFiles: actions.removeFiles,
|
removeFiles: actions.removeFiles,
|
||||||
clearAllFiles: actions.clearAllFiles,
|
clearAllFiles: actions.clearAllFiles,
|
||||||
updateFileRecord: actions.updateFileRecord
|
updateFileRecord: actions.updateFileRecord,
|
||||||
|
reorderFiles: actions.reorderFiles
|
||||||
}), [actions]);
|
}), [actions]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,9 +157,9 @@ export function useFileContext() {
|
|||||||
// File management
|
// File management
|
||||||
addFiles: actions.addFiles,
|
addFiles: actions.addFiles,
|
||||||
consumeFiles: actions.consumeFiles,
|
consumeFiles: actions.consumeFiles,
|
||||||
recordOperation: (fileId: string, operation: any) => {}, // TODO: Implement operation tracking
|
recordOperation: (fileId: string, operation: any) => {}, // Operation tracking not implemented
|
||||||
markOperationApplied: (fileId: string, operationId: string) => {}, // TODO: Implement operation tracking
|
markOperationApplied: (fileId: string, operationId: string) => {}, // Operation tracking not implemented
|
||||||
markOperationFailed: (fileId: string, operationId: string, error: string) => {}, // TODO: Implement operation tracking
|
markOperationFailed: (fileId: string, operationId: string, error: string) => {}, // Operation tracking not implemented
|
||||||
|
|
||||||
// File ID lookup
|
// File ID lookup
|
||||||
findFileId: (file: File) => {
|
findFileId: (file: File) => {
|
||||||
@ -241,16 +242,12 @@ export function useProcessedFiles() {
|
|||||||
);
|
);
|
||||||
return !!record?.processedFile;
|
return !!record?.processedFile;
|
||||||
},
|
},
|
||||||
set: () => {
|
// Removed deprecated set method
|
||||||
console.warn('processedFiles.set is deprecated - use FileRecord updates instead');
|
|
||||||
}
|
|
||||||
}), [state.files.byId, state.files.ids.length]);
|
}), [state.files.byId, state.files.ids.length]);
|
||||||
|
|
||||||
return useMemo(() => ({
|
return useMemo(() => ({
|
||||||
processedFiles: compatibilityMap,
|
processedFiles: compatibilityMap,
|
||||||
getProcessedFile: (file: File) => compatibilityMap.get(file),
|
getProcessedFile: (file: File) => compatibilityMap.get(file),
|
||||||
updateProcessedFile: () => {
|
// Removed deprecated updateProcessedFile method
|
||||||
console.warn('updateProcessedFile is deprecated - processed files are now stored in FileRecord');
|
|
||||||
}
|
|
||||||
}), [compatibilityMap]);
|
}), [compatibilityMap]);
|
||||||
}
|
}
|
@ -26,8 +26,6 @@ export interface FileMetadata {
|
|||||||
lastModified: number;
|
lastModified: number;
|
||||||
thumbnail?: string;
|
thumbnail?: string;
|
||||||
isDraft?: boolean; // Marks files as draft versions
|
isDraft?: boolean; // Marks files as draft versions
|
||||||
/** @deprecated Legacy compatibility - will be removed */
|
|
||||||
storedInIndexedDB?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageConfig {
|
export interface StorageConfig {
|
||||||
|
@ -196,6 +196,7 @@ export type FileContextAction =
|
|||||||
| { type: 'ADD_FILES'; payload: { fileRecords: FileRecord[] } }
|
| { type: 'ADD_FILES'; payload: { fileRecords: FileRecord[] } }
|
||||||
| { type: 'REMOVE_FILES'; payload: { fileIds: FileId[] } }
|
| { type: 'REMOVE_FILES'; payload: { fileIds: FileId[] } }
|
||||||
| { type: 'UPDATE_FILE_RECORD'; payload: { id: FileId; updates: Partial<FileRecord> } }
|
| { type: 'UPDATE_FILE_RECORD'; payload: { id: FileId; updates: Partial<FileRecord> } }
|
||||||
|
| { type: 'REORDER_FILES'; payload: { orderedFileIds: FileId[] } }
|
||||||
|
|
||||||
// Pinned files actions
|
// Pinned files actions
|
||||||
| { type: 'PIN_FILE'; payload: { fileId: FileId } }
|
| { type: 'PIN_FILE'; payload: { fileId: FileId } }
|
||||||
@ -221,6 +222,7 @@ export interface FileContextActions {
|
|||||||
addStoredFiles: (filesWithMetadata: Array<{ file: File; originalId: FileId; metadata: FileMetadata }>) => Promise<File[]>;
|
addStoredFiles: (filesWithMetadata: Array<{ file: File; originalId: FileId; metadata: FileMetadata }>) => Promise<File[]>;
|
||||||
removeFiles: (fileIds: FileId[], deleteFromStorage?: boolean) => Promise<void>;
|
removeFiles: (fileIds: FileId[], deleteFromStorage?: boolean) => Promise<void>;
|
||||||
updateFileRecord: (id: FileId, updates: Partial<FileRecord>) => void;
|
updateFileRecord: (id: FileId, updates: Partial<FileRecord>) => void;
|
||||||
|
reorderFiles: (orderedFileIds: FileId[]) => void;
|
||||||
clearAllFiles: () => Promise<void>;
|
clearAllFiles: () => Promise<void>;
|
||||||
|
|
||||||
// File pinning
|
// File pinning
|
||||||
|
Loading…
x
Reference in New Issue
Block a user