mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-18 09:29:24 +00:00
show history
This commit is contained in:
parent
d0c6ae2c31
commit
02740b2741
@ -53,12 +53,10 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
|||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
filteredFiles.map((file, index) => {
|
filteredFiles.map((file, index) => {
|
||||||
// Check if this file is a leaf (appears in group keys) or a history file
|
// Determine if this is a history file based on whether it's in the recent files or loaded as history
|
||||||
const isLeafFile = fileGroups.has(file.id);
|
const isLeafFile = recentFiles.some(rf => rf.id === file.id);
|
||||||
const lineagePath = fileGroups.get(file.id) || [];
|
const isHistoryFile = !isLeafFile; // If not in recent files, it's a loaded history file
|
||||||
const isHistoryFile = !isLeafFile; // If not a leaf, it's a history file
|
const isLatestVersion = isLeafFile; // Only leaf files (from recent files) are latest versions
|
||||||
const isLatestVersion = isLeafFile; // Leaf files are the latest in their branch
|
|
||||||
const hasVersionHistory = lineagePath.length > 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileListItem
|
<FileListItem
|
||||||
@ -71,7 +69,7 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
|||||||
onDownload={() => onDownloadSingle(file)}
|
onDownload={() => onDownloadSingle(file)}
|
||||||
onDoubleClick={() => onFileDoubleClick(file)}
|
onDoubleClick={() => onFileDoubleClick(file)}
|
||||||
isHistoryFile={isHistoryFile}
|
isHistoryFile={isHistoryFile}
|
||||||
isLatestVersion={isLatestVersion && hasVersionHistory}
|
isLatestVersion={isLatestVersion}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Group, Box, Text, ActionIcon, Checkbox, Divider, Menu, Badge, Button } from '@mantine/core';
|
import { Group, Box, Text, ActionIcon, Checkbox, Divider, Menu, Badge, Button, Loader } from '@mantine/core';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import DownloadIcon from '@mui/icons-material/Download';
|
import DownloadIcon from '@mui/icons-material/Download';
|
||||||
@ -38,7 +38,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
|||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { fileGroups, expandedFileIds, onToggleExpansion, onAddToRecents } = useFileManagerContext();
|
const { fileGroups, expandedFileIds, onToggleExpansion, onAddToRecents, isLoadingHistory, getHistoryError } = useFileManagerContext();
|
||||||
|
|
||||||
// Keep item in hovered state if menu is open
|
// Keep item in hovered state if menu is open
|
||||||
const shouldShowHovered = isHovered || isMenuOpen;
|
const shouldShowHovered = isHovered || isMenuOpen;
|
||||||
@ -46,9 +46,13 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
|||||||
// Get version information for this file
|
// Get version information for this file
|
||||||
const leafFileId = isLatestVersion ? file.id : (file.originalFileId || file.id);
|
const leafFileId = isLatestVersion ? file.id : (file.originalFileId || file.id);
|
||||||
const lineagePath = fileGroups.get(leafFileId) || [];
|
const lineagePath = fileGroups.get(leafFileId) || [];
|
||||||
const hasVersionHistory = lineagePath.length > 1;
|
const hasVersionHistory = (file.versionNumber || 0) > 0; // Show history for any processed file (v1+)
|
||||||
const currentVersion = file.versionNumber || 0; // Display original files as v0
|
const currentVersion = file.versionNumber || 0; // Display original files as v0
|
||||||
const isExpanded = expandedFileIds.has(leafFileId);
|
const isExpanded = expandedFileIds.has(leafFileId);
|
||||||
|
|
||||||
|
// Get loading state for this file's history
|
||||||
|
const isLoadingFileHistory = isLoadingHistory(file.id);
|
||||||
|
const historyError = getHistoryError(file.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -91,6 +95,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
|||||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
<Text size="sm" fw={500} truncate style={{ flex: 1 }}>{file.name}</Text>
|
<Text size="sm" fw={500} truncate style={{ flex: 1 }}>{file.name}</Text>
|
||||||
|
{isLoadingFileHistory && <Loader size={14} />}
|
||||||
<Badge size="xs" variant="light" color={currentVersion > 0 ? "blue" : "gray"}>
|
<Badge size="xs" variant="light" color={currentVersion > 0 ? "blue" : "gray"}>
|
||||||
v{currentVersion}
|
v{currentVersion}
|
||||||
</Badge>
|
</Badge>
|
||||||
@ -100,7 +105,7 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
|||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
{getFileSize(file)} • {getFileDate(file)}
|
{getFileSize(file)} • {getFileDate(file)}
|
||||||
{hasVersionHistory && (
|
{hasVersionHistory && (
|
||||||
<Text span c="dimmed"> • {lineagePath.length} versions</Text>
|
<Text span c="dimmed"> • has history</Text>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@ -157,17 +162,30 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
|||||||
{isLatestVersion && hasVersionHistory && (
|
{isLatestVersion && hasVersionHistory && (
|
||||||
<>
|
<>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<HistoryIcon style={{ fontSize: 16 }} />}
|
leftSection={
|
||||||
|
isLoadingFileHistory ?
|
||||||
|
<Loader size={16} /> :
|
||||||
|
<HistoryIcon style={{ fontSize: 16 }} />
|
||||||
|
}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onToggleExpansion(leafFileId);
|
onToggleExpansion(leafFileId);
|
||||||
}}
|
}}
|
||||||
|
disabled={isLoadingFileHistory}
|
||||||
>
|
>
|
||||||
{isExpanded ?
|
{isLoadingFileHistory ?
|
||||||
t('fileManager.hideHistory', 'Hide History') :
|
t('fileManager.loadingHistory', 'Loading History...') :
|
||||||
t('fileManager.showHistory', 'Show History')
|
(isExpanded ?
|
||||||
|
t('fileManager.hideHistory', 'Hide History') :
|
||||||
|
t('fileManager.showHistory', 'Show History')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
{historyError && (
|
||||||
|
<Menu.Item disabled c="red" style={{ fontSize: '12px' }}>
|
||||||
|
{t('fileManager.historyError', 'Error loading history')}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -3,7 +3,8 @@ import { FileMetadata } from '../types/file';
|
|||||||
import { StoredFile, fileStorage } from '../services/fileStorage';
|
import { StoredFile, fileStorage } from '../services/fileStorage';
|
||||||
import { downloadFiles } from '../utils/downloadUtils';
|
import { downloadFiles } from '../utils/downloadUtils';
|
||||||
import { FileId } from '../types/file';
|
import { FileId } from '../types/file';
|
||||||
import { getLatestVersions, groupFilesByOriginal, getVersionHistory } from '../utils/fileHistoryUtils';
|
import { getLatestVersions, groupFilesByOriginal, getVersionHistory, createFileMetadataWithHistory } from '../utils/fileHistoryUtils';
|
||||||
|
import { useMultiFileHistory } from '../hooks/useFileHistory';
|
||||||
|
|
||||||
// Type for the context value - now contains everything directly
|
// Type for the context value - now contains everything directly
|
||||||
interface FileManagerContextValue {
|
interface FileManagerContextValue {
|
||||||
@ -18,6 +19,10 @@ interface FileManagerContextValue {
|
|||||||
expandedFileIds: Set<string>;
|
expandedFileIds: Set<string>;
|
||||||
fileGroups: Map<string, FileMetadata[]>;
|
fileGroups: Map<string, FileMetadata[]>;
|
||||||
|
|
||||||
|
// History loading state
|
||||||
|
isLoadingHistory: (fileId: FileId) => boolean;
|
||||||
|
getHistoryError: (fileId: FileId) => string | null;
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
|
onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
|
||||||
onLocalFileClick: () => void;
|
onLocalFileClick: () => void;
|
||||||
@ -75,11 +80,20 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [lastClickedIndex, setLastClickedIndex] = useState<number | null>(null);
|
const [lastClickedIndex, setLastClickedIndex] = useState<number | null>(null);
|
||||||
const [expandedFileIds, setExpandedFileIds] = useState<Set<string>>(new Set());
|
const [expandedFileIds, setExpandedFileIds] = useState<Set<string>>(new Set());
|
||||||
|
const [loadedHistoryFiles, setLoadedHistoryFiles] = useState<Map<FileId, FileMetadata[]>>(new Map()); // Cache for loaded history
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Track blob URLs for cleanup
|
// Track blob URLs for cleanup
|
||||||
const createdBlobUrls = useRef<Set<string>>(new Set());
|
const createdBlobUrls = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// History loading hook
|
||||||
|
const {
|
||||||
|
loadFileHistory,
|
||||||
|
getHistory,
|
||||||
|
isLoadingHistory,
|
||||||
|
getError: getHistoryError
|
||||||
|
} = useMultiFileHistory();
|
||||||
|
|
||||||
// Computed values (with null safety)
|
// Computed values (with null safety)
|
||||||
const selectedFilesSet = new Set(selectedFileIds);
|
const selectedFilesSet = new Set(selectedFileIds);
|
||||||
|
|
||||||
@ -101,36 +115,24 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
const displayFiles = useMemo(() => {
|
const displayFiles = useMemo(() => {
|
||||||
if (!recentFiles || recentFiles.length === 0) return [];
|
if (!recentFiles || recentFiles.length === 0) return [];
|
||||||
|
|
||||||
const recordsForGrouping = recentFiles.map(file => ({
|
|
||||||
...file,
|
|
||||||
originalFileId: file.originalFileId,
|
|
||||||
versionNumber: file.versionNumber || 0
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Get branch groups (leaf files with their lineage paths)
|
|
||||||
const branchGroups = groupFilesByOriginal(recordsForGrouping);
|
|
||||||
|
|
||||||
// Show only leaf files (end of branches) in main list
|
|
||||||
const expandedFiles = [];
|
const expandedFiles = [];
|
||||||
for (const [leafFileId, lineagePath] of branchGroups) {
|
|
||||||
const leafFile = recentFiles.find(f => f.id === leafFileId);
|
// Since we now only load leaf files, iterate through recent files directly
|
||||||
if (!leafFile) continue;
|
for (const leafFile of recentFiles) {
|
||||||
|
// Add the leaf file (main file shown in list)
|
||||||
// Add the leaf file (shown in main list)
|
|
||||||
expandedFiles.push(leafFile);
|
expandedFiles.push(leafFile);
|
||||||
|
|
||||||
// If expanded, add the lineage history (except the leaf itself)
|
// If expanded, add the loaded history files
|
||||||
if (expandedFileIds.has(leafFileId)) {
|
if (expandedFileIds.has(leafFile.id)) {
|
||||||
const historyFiles = lineagePath
|
const historyFiles = loadedHistoryFiles.get(leafFile.id) || [];
|
||||||
.filter((record: any) => record.id !== leafFileId)
|
// Sort history files by version number (oldest first)
|
||||||
.map((record: any) => recentFiles.find(f => f.id === record.id))
|
const sortedHistory = historyFiles.sort((a, b) => (a.versionNumber || 0) - (b.versionNumber || 0));
|
||||||
.filter((f): f is FileMetadata => f !== undefined);
|
expandedFiles.push(...sortedHistory);
|
||||||
expandedFiles.push(...historyFiles);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return expandedFiles;
|
return expandedFiles;
|
||||||
}, [recentFiles, expandedFileIds, fileGroups]);
|
}, [recentFiles, expandedFileIds, loadedHistoryFiles]);
|
||||||
|
|
||||||
const selectedFiles = selectedFileIds.length === 0 ? [] :
|
const selectedFiles = selectedFileIds.length === 0 ? [] :
|
||||||
displayFiles.filter(file => selectedFilesSet.has(file.id));
|
displayFiles.filter(file => selectedFilesSet.has(file.id));
|
||||||
@ -331,7 +333,10 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggleExpansion = useCallback((fileId: string) => {
|
const handleToggleExpansion = useCallback(async (fileId: string) => {
|
||||||
|
const isCurrentlyExpanded = expandedFileIds.has(fileId);
|
||||||
|
|
||||||
|
// Update expansion state
|
||||||
setExpandedFileIds(prev => {
|
setExpandedFileIds(prev => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
if (newSet.has(fileId)) {
|
if (newSet.has(fileId)) {
|
||||||
@ -341,7 +346,124 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
}
|
}
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
}, []);
|
|
||||||
|
// Load complete history chain if expanding
|
||||||
|
if (!isCurrentlyExpanded) {
|
||||||
|
const currentFileMetadata = recentFiles.find(f => f.id === fileId);
|
||||||
|
if (currentFileMetadata && (currentFileMetadata.versionNumber || 0) > 0) {
|
||||||
|
try {
|
||||||
|
// Load the current file to get its full history
|
||||||
|
const storedFile = await fileStorage.getFile(fileId);
|
||||||
|
if (storedFile) {
|
||||||
|
const file = new File([storedFile.data], storedFile.name, {
|
||||||
|
type: storedFile.type,
|
||||||
|
lastModified: storedFile.lastModified
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the complete history metadata (this will give us original/parent IDs)
|
||||||
|
const historyData = await loadFileHistory(file, fileId);
|
||||||
|
|
||||||
|
if (historyData?.originalFileId) {
|
||||||
|
// Load complete history chain by traversing parent relationships
|
||||||
|
const historyFiles: FileMetadata[] = [];
|
||||||
|
|
||||||
|
// Get all stored files for chain traversal
|
||||||
|
const allStoredMetadata = await fileStorage.getAllFileMetadata();
|
||||||
|
const fileMap = new Map(allStoredMetadata.map(f => [f.id, f]));
|
||||||
|
|
||||||
|
// Build complete chain by following parent relationships backwards
|
||||||
|
const visitedIds = new Set([fileId]); // Don't include the current file
|
||||||
|
const toProcess = [historyData]; // Start with current file's history data
|
||||||
|
|
||||||
|
while (toProcess.length > 0) {
|
||||||
|
const currentHistoryData = toProcess.shift()!;
|
||||||
|
|
||||||
|
// Add original file if we haven't seen it
|
||||||
|
if (currentHistoryData.originalFileId && !visitedIds.has(currentHistoryData.originalFileId)) {
|
||||||
|
visitedIds.add(currentHistoryData.originalFileId);
|
||||||
|
const originalMeta = fileMap.get(currentHistoryData.originalFileId);
|
||||||
|
if (originalMeta) {
|
||||||
|
try {
|
||||||
|
const origStoredFile = await fileStorage.getFile(originalMeta.id);
|
||||||
|
if (origStoredFile) {
|
||||||
|
const origFile = new File([origStoredFile.data], origStoredFile.name, {
|
||||||
|
type: origStoredFile.type,
|
||||||
|
lastModified: origStoredFile.lastModified
|
||||||
|
});
|
||||||
|
const origMetadata = await createFileMetadataWithHistory(origFile, originalMeta.id, originalMeta.thumbnail);
|
||||||
|
historyFiles.push(origMetadata);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load original file ${originalMeta.id}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parent file if we haven't seen it
|
||||||
|
if (currentHistoryData.parentFileId && !visitedIds.has(currentHistoryData.parentFileId)) {
|
||||||
|
visitedIds.add(currentHistoryData.parentFileId);
|
||||||
|
const parentMeta = fileMap.get(currentHistoryData.parentFileId);
|
||||||
|
if (parentMeta) {
|
||||||
|
try {
|
||||||
|
const parentStoredFile = await fileStorage.getFile(parentMeta.id);
|
||||||
|
if (parentStoredFile) {
|
||||||
|
const parentFile = new File([parentStoredFile.data], parentStoredFile.name, {
|
||||||
|
type: parentStoredFile.type,
|
||||||
|
lastModified: parentStoredFile.lastModified
|
||||||
|
});
|
||||||
|
const parentMetadata = await createFileMetadataWithHistory(parentFile, parentMeta.id, parentMeta.thumbnail);
|
||||||
|
historyFiles.push(parentMetadata);
|
||||||
|
|
||||||
|
// Load parent's history to continue the chain
|
||||||
|
const parentHistoryData = await loadFileHistory(parentFile, parentMeta.id);
|
||||||
|
if (parentHistoryData) {
|
||||||
|
toProcess.push(parentHistoryData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load parent file ${parentMeta.id}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also find any files that have the current file as their original (siblings/alternatives)
|
||||||
|
for (const [metaId, meta] of fileMap) {
|
||||||
|
if (!visitedIds.has(metaId) && meta.originalFileId === historyData.originalFileId) {
|
||||||
|
visitedIds.add(metaId);
|
||||||
|
try {
|
||||||
|
const siblingStoredFile = await fileStorage.getFile(meta.id);
|
||||||
|
if (siblingStoredFile) {
|
||||||
|
const siblingFile = new File([siblingStoredFile.data], siblingStoredFile.name, {
|
||||||
|
type: siblingStoredFile.type,
|
||||||
|
lastModified: siblingStoredFile.lastModified
|
||||||
|
});
|
||||||
|
const siblingMetadata = await createFileMetadataWithHistory(siblingFile, meta.id, meta.thumbnail);
|
||||||
|
historyFiles.push(siblingMetadata);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load sibling file ${meta.id}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the loaded history files
|
||||||
|
setLoadedHistoryFiles(prev => new Map(prev.set(fileId, historyFiles)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load history chain for file ${fileId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Clear loaded history when collapsing
|
||||||
|
setLoadedHistoryFiles(prev => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
newMap.delete(fileId);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [expandedFileIds, recentFiles, loadFileHistory]);
|
||||||
|
|
||||||
const handleAddToRecents = useCallback(async (file: FileMetadata) => {
|
const handleAddToRecents = useCallback(async (file: FileMetadata) => {
|
||||||
try {
|
try {
|
||||||
@ -399,6 +521,10 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
expandedFileIds,
|
expandedFileIds,
|
||||||
fileGroups,
|
fileGroups,
|
||||||
|
|
||||||
|
// History loading state
|
||||||
|
isLoadingHistory,
|
||||||
|
getHistoryError,
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
onSourceChange: handleSourceChange,
|
onSourceChange: handleSourceChange,
|
||||||
onLocalFileClick: handleLocalFileClick,
|
onLocalFileClick: handleLocalFileClick,
|
||||||
@ -429,6 +555,8 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
fileInputRef,
|
fileInputRef,
|
||||||
expandedFileIds,
|
expandedFileIds,
|
||||||
fileGroups,
|
fileGroups,
|
||||||
|
isLoadingHistory,
|
||||||
|
getHistoryError,
|
||||||
handleSourceChange,
|
handleSourceChange,
|
||||||
handleLocalFileClick,
|
handleLocalFileClick,
|
||||||
handleFileSelect,
|
handleFileSelect,
|
||||||
|
@ -21,6 +21,7 @@ interface IndexedDBContextValue {
|
|||||||
|
|
||||||
// Batch operations
|
// Batch operations
|
||||||
loadAllMetadata: () => Promise<FileMetadata[]>;
|
loadAllMetadata: () => Promise<FileMetadata[]>;
|
||||||
|
loadLeafMetadata: () => Promise<FileMetadata[]>; // Only leaf files for recent files list
|
||||||
deleteMultiple: (fileIds: FileId[]) => Promise<void>;
|
deleteMultiple: (fileIds: FileId[]) => Promise<void>;
|
||||||
clearAll: () => Promise<void>;
|
clearAll: () => Promise<void>;
|
||||||
|
|
||||||
@ -140,6 +141,64 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
|
|||||||
await fileStorage.deleteFile(fileId);
|
await fileStorage.deleteFile(fileId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const loadLeafMetadata = useCallback(async (): Promise<FileMetadata[]> => {
|
||||||
|
const metadata = await fileStorage.getLeafFileMetadata(); // Only get leaf files
|
||||||
|
|
||||||
|
// Separate PDF and non-PDF files for different processing
|
||||||
|
const pdfFiles = metadata.filter(m => m.type.includes('pdf'));
|
||||||
|
const nonPdfFiles = metadata.filter(m => !m.type.includes('pdf'));
|
||||||
|
|
||||||
|
// Process non-PDF files immediately (no history extraction needed)
|
||||||
|
const nonPdfMetadata: FileMetadata[] = nonPdfFiles.map(m => ({
|
||||||
|
id: m.id,
|
||||||
|
name: m.name,
|
||||||
|
type: m.type,
|
||||||
|
size: m.size,
|
||||||
|
lastModified: m.lastModified,
|
||||||
|
thumbnail: m.thumbnail,
|
||||||
|
isLeaf: m.isLeaf
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Process PDF files with controlled concurrency to avoid memory issues
|
||||||
|
const BATCH_SIZE = 5; // Process 5 PDFs at a time to avoid overwhelming memory
|
||||||
|
const pdfMetadata: FileMetadata[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < pdfFiles.length; i += BATCH_SIZE) {
|
||||||
|
const batch = pdfFiles.slice(i, i + BATCH_SIZE);
|
||||||
|
|
||||||
|
const batchResults = await Promise.all(batch.map(async (m) => {
|
||||||
|
try {
|
||||||
|
// For PDF files, load and extract basic history for display only
|
||||||
|
const storedFile = await fileStorage.getFile(m.id);
|
||||||
|
if (storedFile?.data) {
|
||||||
|
const file = new File([storedFile.data], m.name, {
|
||||||
|
type: m.type,
|
||||||
|
lastModified: m.lastModified
|
||||||
|
});
|
||||||
|
return await createFileMetadataWithHistory(file, m.id, m.thumbnail);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (DEBUG) console.warn('🗂️ Failed to extract basic metadata from leaf file:', m.name, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to basic metadata without history
|
||||||
|
return {
|
||||||
|
id: m.id,
|
||||||
|
name: m.name,
|
||||||
|
type: m.type,
|
||||||
|
size: m.size,
|
||||||
|
lastModified: m.lastModified,
|
||||||
|
thumbnail: m.thumbnail,
|
||||||
|
isLeaf: m.isLeaf
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
pdfMetadata.push(...batchResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...nonPdfMetadata, ...pdfMetadata];
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadAllMetadata = useCallback(async (): Promise<FileMetadata[]> => {
|
const loadAllMetadata = useCallback(async (): Promise<FileMetadata[]> => {
|
||||||
const metadata = await fileStorage.getAllFileMetadata();
|
const metadata = await fileStorage.getAllFileMetadata();
|
||||||
|
|
||||||
@ -230,6 +289,7 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
|
|||||||
loadMetadata,
|
loadMetadata,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
loadAllMetadata,
|
loadAllMetadata,
|
||||||
|
loadLeafMetadata,
|
||||||
deleteMultiple,
|
deleteMultiple,
|
||||||
clearAll,
|
clearAll,
|
||||||
getStorageStats,
|
getStorageStats,
|
||||||
|
@ -15,7 +15,7 @@ import { generateThumbnailWithMetadata } from '../../utils/thumbnailUtils';
|
|||||||
import { FileLifecycleManager } from './lifecycle';
|
import { FileLifecycleManager } from './lifecycle';
|
||||||
import { fileProcessingService } from '../../services/fileProcessingService';
|
import { fileProcessingService } from '../../services/fileProcessingService';
|
||||||
import { buildQuickKeySet, buildQuickKeySetFromMetadata } from './fileSelectors';
|
import { buildQuickKeySet, buildQuickKeySetFromMetadata } from './fileSelectors';
|
||||||
import { extractFileHistory } from '../../utils/fileHistoryUtils';
|
import { extractFileHistory, extractBasicFileMetadata } from '../../utils/fileHistoryUtils';
|
||||||
|
|
||||||
const DEBUG = process.env.NODE_ENV === 'development';
|
const DEBUG = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
@ -184,25 +184,23 @@ export async function addFiles(
|
|||||||
if (DEBUG) console.log(`📄 addFiles(raw): Created initial processedFile metadata for ${file.name} with ${pageCount} pages`);
|
if (DEBUG) console.log(`📄 addFiles(raw): Created initial processedFile metadata for ${file.name} with ${pageCount} pages`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract file history from PDF metadata (async)
|
// Extract basic metadata (version number and tool chain) for display
|
||||||
extractFileHistory(file, record).then(updatedRecord => {
|
extractBasicFileMetadata(file, record).then(updatedRecord => {
|
||||||
if (updatedRecord !== record && (updatedRecord.originalFileId || updatedRecord.versionNumber)) {
|
if (updatedRecord !== record && (updatedRecord.versionNumber || updatedRecord.toolHistory)) {
|
||||||
// History was found, dispatch update to trigger re-render
|
// Basic metadata found, dispatch update to trigger re-render
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_FILE_RECORD',
|
type: 'UPDATE_FILE_RECORD',
|
||||||
payload: {
|
payload: {
|
||||||
id: fileId,
|
id: fileId,
|
||||||
updates: {
|
updates: {
|
||||||
originalFileId: updatedRecord.originalFileId,
|
|
||||||
versionNumber: updatedRecord.versionNumber,
|
versionNumber: updatedRecord.versionNumber,
|
||||||
parentFileId: updatedRecord.parentFileId,
|
|
||||||
toolHistory: updatedRecord.toolHistory
|
toolHistory: updatedRecord.toolHistory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (DEBUG) console.warn(`📄 Failed to extract history for ${file.name}:`, error);
|
if (DEBUG) console.warn(`📄 Failed to extract basic metadata for ${file.name}:`, error);
|
||||||
});
|
});
|
||||||
|
|
||||||
existingQuickKeys.add(quickKey);
|
existingQuickKeys.add(quickKey);
|
||||||
@ -247,36 +245,23 @@ export async function addFiles(
|
|||||||
if (DEBUG) console.log(`📄 addFiles(processed): Created initial processedFile metadata for ${file.name} with ${pageCount} pages`);
|
if (DEBUG) console.log(`📄 addFiles(processed): Created initial processedFile metadata for ${file.name} with ${pageCount} pages`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract file history from PDF metadata (async)
|
// Extract basic metadata (version number and tool chain) for display
|
||||||
if (DEBUG) console.log(`📄 addFiles(processed): Starting async history extraction for ${file.name}`);
|
extractBasicFileMetadata(file, record).then(updatedRecord => {
|
||||||
extractFileHistory(file, record).then(updatedRecord => {
|
if (updatedRecord !== record && (updatedRecord.versionNumber || updatedRecord.toolHistory)) {
|
||||||
if (DEBUG) console.log(`📄 addFiles(processed): History extraction completed for ${file.name}:`, {
|
// Basic metadata found, dispatch update to trigger re-render
|
||||||
hasChanges: updatedRecord !== record,
|
|
||||||
originalFileId: updatedRecord.originalFileId,
|
|
||||||
versionNumber: updatedRecord.versionNumber,
|
|
||||||
toolHistoryLength: updatedRecord.toolHistory?.length || 0
|
|
||||||
});
|
|
||||||
|
|
||||||
if (updatedRecord !== record && (updatedRecord.originalFileId || updatedRecord.versionNumber)) {
|
|
||||||
// History was found, dispatch update to trigger re-render
|
|
||||||
if (DEBUG) console.log(`📄 addFiles(processed): Dispatching UPDATE_FILE_RECORD for ${file.name}`);
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_FILE_RECORD',
|
type: 'UPDATE_FILE_RECORD',
|
||||||
payload: {
|
payload: {
|
||||||
id: fileId,
|
id: fileId,
|
||||||
updates: {
|
updates: {
|
||||||
originalFileId: updatedRecord.originalFileId,
|
|
||||||
versionNumber: updatedRecord.versionNumber,
|
versionNumber: updatedRecord.versionNumber,
|
||||||
parentFileId: updatedRecord.parentFileId,
|
|
||||||
toolHistory: updatedRecord.toolHistory
|
toolHistory: updatedRecord.toolHistory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
if (DEBUG) console.log(`📄 addFiles(processed): No history found for ${file.name}, skipping update`);
|
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (DEBUG) console.error(`📄 addFiles(processed): Failed to extract history for ${file.name}:`, error);
|
if (DEBUG) console.warn(`📄 Failed to extract basic metadata for ${file.name}:`, error);
|
||||||
});
|
});
|
||||||
|
|
||||||
existingQuickKeys.add(quickKey);
|
existingQuickKeys.add(quickKey);
|
||||||
@ -354,25 +339,23 @@ export async function addFiles(
|
|||||||
if (DEBUG) console.log(`📄 addFiles(stored): Created processedFile metadata for ${file.name} with ${pageCount} pages`);
|
if (DEBUG) console.log(`📄 addFiles(stored): Created processedFile metadata for ${file.name} with ${pageCount} pages`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract file history from PDF metadata (async) - same as raw files
|
// Extract basic metadata (version number and tool chain) for display
|
||||||
extractFileHistory(file, record).then(updatedRecord => {
|
extractBasicFileMetadata(file, record).then(updatedRecord => {
|
||||||
if (updatedRecord !== record && (updatedRecord.originalFileId || updatedRecord.versionNumber)) {
|
if (updatedRecord !== record && (updatedRecord.versionNumber || updatedRecord.toolHistory)) {
|
||||||
// History was found, dispatch update to trigger re-render
|
// Basic metadata found, dispatch update to trigger re-render
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_FILE_RECORD',
|
type: 'UPDATE_FILE_RECORD',
|
||||||
payload: {
|
payload: {
|
||||||
id: fileId,
|
id: fileId,
|
||||||
updates: {
|
updates: {
|
||||||
originalFileId: updatedRecord.originalFileId,
|
|
||||||
versionNumber: updatedRecord.versionNumber,
|
versionNumber: updatedRecord.versionNumber,
|
||||||
parentFileId: updatedRecord.parentFileId,
|
|
||||||
toolHistory: updatedRecord.toolHistory
|
toolHistory: updatedRecord.toolHistory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (DEBUG) console.warn(`📄 Failed to extract history for ${file.name}:`, error);
|
if (DEBUG) console.warn(`📄 Failed to extract basic metadata for ${file.name}:`, error);
|
||||||
});
|
});
|
||||||
|
|
||||||
existingQuickKeys.add(quickKey);
|
existingQuickKeys.add(quickKey);
|
||||||
@ -431,22 +414,20 @@ async function processFilesIntoRecords(
|
|||||||
record.processedFile = createProcessedFile(pageCount, thumbnail);
|
record.processedFile = createProcessedFile(pageCount, thumbnail);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract file history from PDF metadata (synchronous during consumeFiles)
|
// Extract basic metadata synchronously during consumeFiles for immediate display
|
||||||
if (file.type.includes('pdf')) {
|
if (file.type.includes('pdf')) {
|
||||||
try {
|
try {
|
||||||
const updatedRecord = await extractFileHistory(file, record);
|
const updatedRecord = await extractBasicFileMetadata(file, record);
|
||||||
|
|
||||||
if (updatedRecord !== record && (updatedRecord.originalFileId || updatedRecord.versionNumber)) {
|
if (updatedRecord !== record && (updatedRecord.versionNumber || updatedRecord.toolHistory)) {
|
||||||
// Update the record directly with history data
|
// Update the record directly with basic metadata
|
||||||
Object.assign(record, {
|
Object.assign(record, {
|
||||||
originalFileId: updatedRecord.originalFileId,
|
|
||||||
versionNumber: updatedRecord.versionNumber,
|
versionNumber: updatedRecord.versionNumber,
|
||||||
parentFileId: updatedRecord.parentFileId,
|
|
||||||
toolHistory: updatedRecord.toolHistory
|
toolHistory: updatedRecord.toolHistory
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (DEBUG) console.warn(`📄 Failed to extract history for ${file.name}:`, error);
|
if (DEBUG) console.warn(`📄 Failed to extract basic metadata for ${file.name}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
160
frontend/src/hooks/useFileHistory.ts
Normal file
160
frontend/src/hooks/useFileHistory.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/**
|
||||||
|
* Custom hook for on-demand file history loading
|
||||||
|
* Replaces automatic history extraction during file loading
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
import { FileId } from '../types/file';
|
||||||
|
import { FileRecord } from '../types/fileContext';
|
||||||
|
import { loadFileHistoryOnDemand } from '../utils/fileHistoryUtils';
|
||||||
|
|
||||||
|
interface FileHistoryState {
|
||||||
|
originalFileId?: string;
|
||||||
|
versionNumber?: number;
|
||||||
|
parentFileId?: FileId;
|
||||||
|
toolHistory?: Array<{
|
||||||
|
toolName: string;
|
||||||
|
timestamp: number;
|
||||||
|
parameters?: Record<string, any>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseFileHistoryResult {
|
||||||
|
historyData: FileHistoryState | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
loadHistory: (file: File, fileId: FileId, updateFileRecord?: (id: FileId, updates: Partial<FileRecord>) => void) => Promise<void>;
|
||||||
|
clearHistory: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFileHistory(): UseFileHistoryResult {
|
||||||
|
const [historyData, setHistoryData] = useState<FileHistoryState | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const loadHistory = useCallback(async (
|
||||||
|
file: File,
|
||||||
|
fileId: FileId,
|
||||||
|
updateFileRecord?: (id: FileId, updates: Partial<FileRecord>) => void
|
||||||
|
) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const history = await loadFileHistoryOnDemand(file, fileId, updateFileRecord);
|
||||||
|
setHistoryData(history);
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Failed to load file history';
|
||||||
|
setError(errorMessage);
|
||||||
|
setHistoryData(null);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clearHistory = useCallback(() => {
|
||||||
|
setHistoryData(null);
|
||||||
|
setError(null);
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
historyData,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
loadHistory,
|
||||||
|
clearHistory
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for managing history state of multiple files
|
||||||
|
*/
|
||||||
|
export function useMultiFileHistory() {
|
||||||
|
const [historyCache, setHistoryCache] = useState<Map<FileId, FileHistoryState>>(new Map());
|
||||||
|
const [loadingFiles, setLoadingFiles] = useState<Set<FileId>>(new Set());
|
||||||
|
const [errors, setErrors] = useState<Map<FileId, string>>(new Map());
|
||||||
|
|
||||||
|
const loadFileHistory = useCallback(async (
|
||||||
|
file: File,
|
||||||
|
fileId: FileId,
|
||||||
|
updateFileRecord?: (id: FileId, updates: Partial<FileRecord>) => void
|
||||||
|
) => {
|
||||||
|
// Don't reload if already loaded or currently loading
|
||||||
|
if (historyCache.has(fileId) || loadingFiles.has(fileId)) {
|
||||||
|
return historyCache.get(fileId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingFiles(prev => new Set(prev).add(fileId));
|
||||||
|
setErrors(prev => {
|
||||||
|
const newErrors = new Map(prev);
|
||||||
|
newErrors.delete(fileId);
|
||||||
|
return newErrors;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const history = await loadFileHistoryOnDemand(file, fileId, updateFileRecord);
|
||||||
|
|
||||||
|
if (history) {
|
||||||
|
setHistoryCache(prev => new Map(prev).set(fileId, history));
|
||||||
|
}
|
||||||
|
|
||||||
|
return history;
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Failed to load file history';
|
||||||
|
setErrors(prev => new Map(prev).set(fileId, errorMessage));
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
setLoadingFiles(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(fileId);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [historyCache, loadingFiles]);
|
||||||
|
|
||||||
|
const getHistory = useCallback((fileId: FileId) => {
|
||||||
|
return historyCache.get(fileId) || null;
|
||||||
|
}, [historyCache]);
|
||||||
|
|
||||||
|
const isLoadingHistory = useCallback((fileId: FileId) => {
|
||||||
|
return loadingFiles.has(fileId);
|
||||||
|
}, [loadingFiles]);
|
||||||
|
|
||||||
|
const getError = useCallback((fileId: FileId) => {
|
||||||
|
return errors.get(fileId) || null;
|
||||||
|
}, [errors]);
|
||||||
|
|
||||||
|
const clearHistory = useCallback((fileId: FileId) => {
|
||||||
|
setHistoryCache(prev => {
|
||||||
|
const newCache = new Map(prev);
|
||||||
|
newCache.delete(fileId);
|
||||||
|
return newCache;
|
||||||
|
});
|
||||||
|
setErrors(prev => {
|
||||||
|
const newErrors = new Map(prev);
|
||||||
|
newErrors.delete(fileId);
|
||||||
|
return newErrors;
|
||||||
|
});
|
||||||
|
setLoadingFiles(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(fileId);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clearAllHistory = useCallback(() => {
|
||||||
|
setHistoryCache(new Map());
|
||||||
|
setLoadingFiles(new Set());
|
||||||
|
setErrors(new Map());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loadFileHistory,
|
||||||
|
getHistory,
|
||||||
|
isLoadingHistory,
|
||||||
|
getError,
|
||||||
|
clearHistory,
|
||||||
|
clearAllHistory
|
||||||
|
};
|
||||||
|
}
|
@ -30,8 +30,8 @@ export const useFileManager = () => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load regular files metadata only
|
// Load only leaf files metadata (processed files that haven't been used as input for other tools)
|
||||||
const storedFileMetadata = await indexedDB.loadAllMetadata();
|
const storedFileMetadata = await indexedDB.loadLeafMetadata();
|
||||||
|
|
||||||
// For now, only regular files - drafts will be handled separately in the future
|
// For now, only regular files - drafts will be handled separately in the future
|
||||||
const allFiles = storedFileMetadata;
|
const allFiles = storedFileMetadata;
|
||||||
|
@ -62,13 +62,11 @@ class FileStorageService {
|
|||||||
const store = transaction.objectStore(this.storeName);
|
const store = transaction.objectStore(this.storeName);
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
console.log('Object store keyPath:', store.keyPath);
|
console.log('📄 LEAF FLAG DEBUG - Storing file:', {
|
||||||
console.log('Storing file with UUID:', {
|
id: storedFile.id,
|
||||||
id: storedFile.id, // Now a UUID from FileContext
|
|
||||||
name: storedFile.name,
|
name: storedFile.name,
|
||||||
hasData: !!storedFile.data,
|
isLeaf: storedFile.isLeaf,
|
||||||
dataSize: storedFile.data.byteLength,
|
dataSize: storedFile.data.byteLength
|
||||||
isLeaf: storedFile.isLeaf
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const request = store.add(storedFile);
|
const request = store.add(storedFile);
|
||||||
@ -222,11 +220,18 @@ class FileStorageService {
|
|||||||
getRequest.onsuccess = () => {
|
getRequest.onsuccess = () => {
|
||||||
const file = getRequest.result;
|
const file = getRequest.result;
|
||||||
if (file) {
|
if (file) {
|
||||||
|
console.log('📄 LEAF FLAG DEBUG - Marking as processed:', {
|
||||||
|
id: file.id,
|
||||||
|
name: file.name,
|
||||||
|
wasLeaf: file.isLeaf,
|
||||||
|
nowLeaf: false
|
||||||
|
});
|
||||||
file.isLeaf = false;
|
file.isLeaf = false;
|
||||||
const updateRequest = store.put(file);
|
const updateRequest = store.put(file);
|
||||||
updateRequest.onsuccess = () => resolve(true);
|
updateRequest.onsuccess = () => resolve(true);
|
||||||
updateRequest.onerror = () => reject(updateRequest.error);
|
updateRequest.onerror = () => reject(updateRequest.error);
|
||||||
} else {
|
} else {
|
||||||
|
console.warn('📄 LEAF FLAG DEBUG - File not found for processing:', id);
|
||||||
resolve(false); // File not found
|
resolve(false); // File not found
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -293,6 +298,7 @@ class FileStorageService {
|
|||||||
}
|
}
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
} else {
|
} else {
|
||||||
|
console.log('📄 LEAF FLAG DEBUG - Found leaf files:', files.map(f => ({ id: f.id, name: f.name, isLeaf: f.isLeaf })));
|
||||||
resolve(files);
|
resolve(files);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -334,6 +334,97 @@ export async function getRecentLeafFileMetadata(): Promise<Omit<import('../servi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract basic file metadata (version number and tool chain) without full history calculation
|
||||||
|
* This is lightweight and used for displaying essential info on file thumbnails
|
||||||
|
*/
|
||||||
|
export async function extractBasicFileMetadata(
|
||||||
|
file: File,
|
||||||
|
record: FileRecord
|
||||||
|
): Promise<FileRecord> {
|
||||||
|
// Only process PDF files
|
||||||
|
if (!file.type.includes('pdf')) {
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
const historyMetadata = await pdfMetadataService.extractHistoryMetadata(arrayBuffer);
|
||||||
|
|
||||||
|
if (historyMetadata) {
|
||||||
|
const history = historyMetadata.stirlingHistory;
|
||||||
|
|
||||||
|
// Update record with essential metadata only (no parent/original relationships)
|
||||||
|
return {
|
||||||
|
...record,
|
||||||
|
versionNumber: history.versionNumber,
|
||||||
|
toolHistory: history.toolChain
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (DEBUG) console.warn('📄 Failed to extract basic metadata:', file.name, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load file history on-demand for a specific file
|
||||||
|
* This replaces the automatic history extraction during file loading
|
||||||
|
*/
|
||||||
|
export async function loadFileHistoryOnDemand(
|
||||||
|
file: File,
|
||||||
|
fileId: FileId,
|
||||||
|
updateFileRecord?: (id: FileId, updates: Partial<FileRecord>) => void
|
||||||
|
): Promise<{
|
||||||
|
originalFileId?: string;
|
||||||
|
versionNumber?: number;
|
||||||
|
parentFileId?: FileId;
|
||||||
|
toolHistory?: Array<{
|
||||||
|
toolName: string;
|
||||||
|
timestamp: number;
|
||||||
|
parameters?: Record<string, any>;
|
||||||
|
}>;
|
||||||
|
} | null> {
|
||||||
|
// Only process PDF files
|
||||||
|
if (!file.type.includes('pdf')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const baseRecord: FileRecord = {
|
||||||
|
id: fileId,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
lastModified: file.lastModified
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedRecord = await extractFileHistory(file, baseRecord);
|
||||||
|
|
||||||
|
if (updatedRecord !== baseRecord && (updatedRecord.originalFileId || updatedRecord.versionNumber)) {
|
||||||
|
const historyData = {
|
||||||
|
originalFileId: updatedRecord.originalFileId,
|
||||||
|
versionNumber: updatedRecord.versionNumber,
|
||||||
|
parentFileId: updatedRecord.parentFileId,
|
||||||
|
toolHistory: updatedRecord.toolHistory
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the file record if update function is provided
|
||||||
|
if (updateFileRecord) {
|
||||||
|
updateFileRecord(fileId, historyData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return historyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load history for ${file.name}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create metadata for storing files with history information
|
* Create metadata for storing files with history information
|
||||||
*/
|
*/
|
||||||
@ -368,12 +459,11 @@ export async function createFileMetadataWithHistory(
|
|||||||
result.pdfMetadata = standardMetadata;
|
result.pdfMetadata = standardMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add history metadata if available
|
// Add history metadata if available (basic version for display)
|
||||||
if (historyMetadata) {
|
if (historyMetadata) {
|
||||||
const history = historyMetadata.stirlingHistory;
|
const history = historyMetadata.stirlingHistory;
|
||||||
result.originalFileId = history.originalFileId;
|
// Only add basic metadata needed for display, not full history relationships
|
||||||
result.versionNumber = history.versionNumber;
|
result.versionNumber = history.versionNumber;
|
||||||
result.parentFileId = history.parentFileId as FileId | undefined;
|
|
||||||
result.historyInfo = {
|
result.historyInfo = {
|
||||||
originalFileId: history.originalFileId,
|
originalFileId: history.originalFileId,
|
||||||
parentFileId: history.parentFileId,
|
parentFileId: history.parentFileId,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user