diff --git a/frontend/src/components/fileManager/FileHistoryGroup.tsx b/frontend/src/components/fileManager/FileHistoryGroup.tsx index 28411079a..0c0f16d65 100644 --- a/frontend/src/components/fileManager/FileHistoryGroup.tsx +++ b/frontend/src/components/fileManager/FileHistoryGroup.tsx @@ -10,7 +10,7 @@ interface FileHistoryGroupProps { isExpanded: boolean; onDownloadSingle: (file: StirlingFileStub) => void; onFileDoubleClick: (file: StirlingFileStub) => void; - onFileRemove: (index: number) => void; + onHistoryFileRemove: (file: StirlingFileStub) => void; isFileSupported: (fileName: string) => boolean; } @@ -20,7 +20,7 @@ const FileHistoryGroup: React.FC = ({ isExpanded, onDownloadSingle, onFileDoubleClick, - onFileRemove, + onHistoryFileRemove, isFileSupported, }) => { const { t } = useTranslation(); @@ -44,14 +44,14 @@ const FileHistoryGroup: React.FC = ({ - {sortedHistory.map((historyFile, index) => ( + {sortedHistory.map((historyFile, _index) => ( {}} // No selection for history files - onRemove={() => onFileRemove(index)} // Pass through remove handler + onRemove={() => onHistoryFileRemove(historyFile)} // Remove specific history file onDownload={() => onDownloadSingle(historyFile)} onDoubleClick={() => onFileDoubleClick(historyFile)} isHistoryFile={true} // This enables "Add to Recents" in menu diff --git a/frontend/src/components/fileManager/FileListArea.tsx b/frontend/src/components/fileManager/FileListArea.tsx index 87684baf0..842d0bf0e 100644 --- a/frontend/src/components/fileManager/FileListArea.tsx +++ b/frontend/src/components/fileManager/FileListArea.tsx @@ -25,6 +25,7 @@ const FileListArea: React.FC = ({ loadedHistoryFiles, onFileSelect, onFileRemove, + onHistoryFileRemove, onFileDoubleClick, onDownloadSingle, isFileSupported, @@ -78,7 +79,7 @@ const FileListArea: React.FC = ({ isExpanded={isExpanded} onDownloadSingle={onDownloadSingle} onFileDoubleClick={onFileDoubleClick} - onFileRemove={onFileRemove} + onHistoryFileRemove={onHistoryFileRemove} isFileSupported={isFileSupported} /> diff --git a/frontend/src/contexts/FileManagerContext.tsx b/frontend/src/contexts/FileManagerContext.tsx index 5499790a6..fba0cec5e 100644 --- a/frontend/src/contexts/FileManagerContext.tsx +++ b/frontend/src/contexts/FileManagerContext.tsx @@ -24,6 +24,7 @@ interface FileManagerContextValue { onLocalFileClick: () => void; onFileSelect: (file: StirlingFileStub, index: number, shiftKey?: boolean) => void; onFileRemove: (index: number) => void; + onHistoryFileRemove: (file: StirlingFileStub) => void; onFileDoubleClick: (file: StirlingFileStub) => void; onOpenFiles: () => void; onSearchChange: (value: string) => void; @@ -172,7 +173,7 @@ export const FileManagerProvider: React.FC = ({ // Helper function to safely determine which files can be deleted const getSafeFilesToDelete = useCallback(( - leafFileIds: string[], + fileIds: string[], allStoredStubs: StirlingFileStub[] ): string[] => { const fileMap = new Map(allStoredStubs.map(f => [f.id as string, f])); @@ -180,7 +181,7 @@ export const FileManagerProvider: React.FC = ({ const filesToPreserve = new Set(); // First, identify all files in the lineages of the leaf files being deleted - for (const leafFileId of leafFileIds) { + for (const leafFileId of fileIds) { const currentFile = fileMap.get(leafFileId); if (!currentFile) continue; @@ -206,7 +207,7 @@ export const FileManagerProvider: React.FC = ({ const fileOriginalId = file.originalFileId || file.id; // If this file is a leaf node (not being deleted) and its lineage overlaps with files we want to delete - if (file.isLeaf !== false && !leafFileIds.includes(file.id)) { + if (file.isLeaf !== false && !fileIds.includes(file.id)) { // Find all files in this preserved lineage const preservedChainFiles = allStoredStubs.filter((chainFile: StirlingFileStub) => (chainFile.originalFileId || chainFile.id) === fileOriginalId @@ -287,6 +288,46 @@ export const FileManagerProvider: React.FC = ({ } }, [filteredFiles, onFileRemove, refreshRecentFiles, getSafeFilesToDelete]); + // Handle deletion of specific history files (not index-based) + const handleHistoryFileRemove = useCallback(async (fileToRemove: StirlingFileStub) => { + const deletedFileId = fileToRemove.id; + + // Clear from expanded state to prevent ghost entries + setExpandedFileIds(prev => { + const newExpanded = new Set(prev); + newExpanded.delete(deletedFileId); + return newExpanded; + }); + + // Clear from history cache - remove all files in the chain + setLoadedHistoryFiles(prev => { + const newCache = new Map(prev); + + // Remove cache entries for all deleted files + newCache.delete(deletedFileId); + + // Also remove deleted files from any other file's history cache + for (const [mainFileId, historyFiles] of newCache.entries()) { + const filteredHistory = historyFiles.filter(histFile => deletedFileId != histFile.id); + if (filteredHistory.length !== historyFiles.length) { + newCache.set(mainFileId, filteredHistory); + } + } + + return newCache; + }); + + // Delete safe files from IndexedDB + try { + await fileStorage.deleteStirlingFile(deletedFileId); + } catch (error) { + console.error('Failed to delete files from chain:', error); + } + + // Refresh to ensure consistent state + await refreshRecentFiles(); + }, [filteredFiles, onFileRemove, refreshRecentFiles, getSafeFilesToDelete]); + const handleFileDoubleClick = useCallback((file: StirlingFileStub) => { if (isFileSupported(file.name)) { onRecentFilesSelected([file]); @@ -540,6 +581,7 @@ export const FileManagerProvider: React.FC = ({ onLocalFileClick: handleLocalFileClick, onFileSelect: handleFileSelect, onFileRemove: handleFileRemove, + onHistoryFileRemove: handleHistoryFileRemove, onFileDoubleClick: handleFileDoubleClick, onOpenFiles: handleOpenFiles, onSearchChange: handleSearchChange,