Show history

This commit is contained in:
Connor Yoh 2025-09-10 19:05:30 +01:00
parent 3cea686acd
commit dc9acdd7cc
5 changed files with 132 additions and 58 deletions

View File

@ -2147,11 +2147,12 @@
"supportMessage": "Powered by browser database storage for unlimited capacity", "supportMessage": "Powered by browser database storage for unlimited capacity",
"noFileSelected": "No files selected", "noFileSelected": "No files selected",
"showHistory": "Show History", "showHistory": "Show History",
"hideHistory": "Hide History", "hideHistory": "Hide History",
"fileHistory": "FileHistory",
"loadingHistory": "Loading History...", "loadingHistory": "Loading History...",
"lastModified": "Last Modified", "lastModified": "Last Modified",
"toolChain": "Tools Applied", "toolChain": "Tools Applied",
"addToRecents": "Add to Recents", "restore": "Restore",
"searchFiles": "Search files...", "searchFiles": "Search files...",
"recent": "Recent", "recent": "Recent",
"localFiles": "Local Files", "localFiles": "Local Files",

View File

@ -0,0 +1,68 @@
import React from 'react';
import { Box, Text, Collapse, Group } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { StirlingFileStub } from '../../types/fileContext';
import FileListItem from './FileListItem';
interface FileHistoryGroupProps {
leafFile: StirlingFileStub;
historyFiles: StirlingFileStub[];
isExpanded: boolean;
onDownloadSingle: (file: StirlingFileStub) => void;
onFileDoubleClick: (file: StirlingFileStub) => void;
onFileRemove: (index: number) => void;
isFileSupported: (fileName: string) => boolean;
}
const FileHistoryGroup: React.FC<FileHistoryGroupProps> = ({
leafFile,
historyFiles,
isExpanded,
onDownloadSingle,
onFileDoubleClick,
onFileRemove,
isFileSupported,
}) => {
const { t } = useTranslation();
// Sort history files by version number (oldest first, excluding the current leaf file)
const sortedHistory = historyFiles
.filter(file => file.id !== leafFile.id) // Exclude the leaf file itself
.sort((a, b) => (a.versionNumber || 1) - (b.versionNumber || 1));
if (!isExpanded || sortedHistory.length === 0) {
return null;
}
return (
<Collapse in={isExpanded}>
<Box ml="md" mt="xs" mb="sm">
<Group align="center" mb="sm">
<Text size="xs" fw={600} c="dimmed" tt="uppercase">
{t('fileManager.fileHistory', 'File History')} ({sortedHistory.length})
</Text>
</Group>
<Box ml="md">
{sortedHistory.map((historyFile, index) => (
<FileListItem
key={`history-${historyFile.id}-${historyFile.versionNumber || 1}`}
file={historyFile}
isSelected={false} // History files are not selectable
isSupported={isFileSupported(historyFile.name)}
onSelect={() => {}} // No selection for history files
onRemove={() => onFileRemove(index)} // Pass through remove handler
onDownload={() => onDownloadSingle(historyFile)}
onDoubleClick={() => onFileDoubleClick(historyFile)}
isHistoryFile={true} // This enables "Add to Recents" in menu
isLatestVersion={false} // History files are never latest
// onAddToRecents is accessed from context by FileListItem
/>
))}
</Box>
</Box>
</Collapse>
);
};
export default FileHistoryGroup;

View File

@ -4,6 +4,7 @@ import CloudIcon from '@mui/icons-material/Cloud';
import HistoryIcon from '@mui/icons-material/History'; import HistoryIcon from '@mui/icons-material/History';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import FileListItem from './FileListItem'; import FileListItem from './FileListItem';
import FileHistoryGroup from './FileHistoryGroup';
import { useFileManagerContext } from '../../contexts/FileManagerContext'; import { useFileManagerContext } from '../../contexts/FileManagerContext';
interface FileListAreaProps { interface FileListAreaProps {
@ -22,6 +23,7 @@ const FileListArea: React.FC<FileListAreaProps> = ({
selectedFilesSet, selectedFilesSet,
fileGroups, fileGroups,
expandedFileIds, expandedFileIds,
loadedHistoryFiles,
onFileSelect, onFileSelect,
onFileRemove, onFileRemove,
onFileDoubleClick, onFileDoubleClick,
@ -53,24 +55,34 @@ const FileListArea: React.FC<FileListAreaProps> = ({
</Center> </Center>
) : ( ) : (
filteredFiles.map((file, index) => { filteredFiles.map((file, index) => {
// Determine if this is a history file based on whether it's in the recent files or loaded as history // All files in filteredFiles are now leaf files only
const isLeafFile = recentFiles.some(rf => rf.id === file.id); const historyFiles = loadedHistoryFiles.get(file.id) || [];
const isHistoryFile = !isLeafFile; // If not in recent files, it's a loaded history file const isExpanded = expandedFileIds.has(file.id);
const isLatestVersion = isLeafFile; // Only leaf files (from recent files) are latest versions
return ( return (
<FileListItem <React.Fragment key={file.id}>
key={file.id} <FileListItem
file={file} file={file}
isSelected={selectedFilesSet.has(file.id)} isSelected={selectedFilesSet.has(file.id)}
isSupported={isFileSupported(file.name)} isSupported={isFileSupported(file.name)}
onSelect={(shiftKey) => onFileSelect(file, index, shiftKey)} onSelect={(shiftKey) => onFileSelect(file, index, shiftKey)}
onRemove={() => onFileRemove(index)} onRemove={() => onFileRemove(index)}
onDownload={() => onDownloadSingle(file)} onDownload={() => onDownloadSingle(file)}
onDoubleClick={() => onFileDoubleClick(file)} onDoubleClick={() => onFileDoubleClick(file)}
isHistoryFile={isHistoryFile} isHistoryFile={false} // All files here are leaf files
isLatestVersion={isLatestVersion} isLatestVersion={true} // All files here are the latest versions
/> />
<FileHistoryGroup
leafFile={file}
historyFiles={historyFiles}
isExpanded={isExpanded}
onDownloadSingle={onDownloadSingle}
onFileDoubleClick={onFileDoubleClick}
onFileRemove={onFileRemove}
isFileSupported={isFileSupported}
/>
</React.Fragment>
); );
}) })
)} )}

View File

@ -5,6 +5,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
import DownloadIcon from '@mui/icons-material/Download'; import DownloadIcon from '@mui/icons-material/Download';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import HistoryIcon from '@mui/icons-material/History'; import HistoryIcon from '@mui/icons-material/History';
import RestoreIcon from '@mui/icons-material/Restore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { getFileSize, getFileDate } from '../../utils/fileUtils'; import { getFileSize, getFileDate } from '../../utils/fileUtils';
import { StirlingFileStub } from '../../types/fileContext'; import { StirlingFileStub } from '../../types/fileContext';
@ -54,8 +55,10 @@ const FileListItem: React.FC<FileListItemProps> = ({
<Box <Box
p="sm" p="sm"
style={{ style={{
cursor: 'pointer', cursor: isHistoryFile ? 'default' : 'pointer',
backgroundColor: isSelected ? 'var(--mantine-color-gray-1)' : (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'), backgroundColor: isSelected
? 'var(--mantine-color-gray-1)'
: (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'),
opacity: isSupported ? 1 : 0.5, opacity: isSupported ? 1 : 0.5,
transition: 'background-color 0.15s ease', transition: 'background-color 0.15s ease',
userSelect: 'none', userSelect: 'none',
@ -65,32 +68,34 @@ const FileListItem: React.FC<FileListItemProps> = ({
paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files
borderLeft: isHistoryFile ? '3px solid var(--mantine-color-blue-4)' : 'none' // Visual indicator for history borderLeft: isHistoryFile ? '3px solid var(--mantine-color-blue-4)' : 'none' // Visual indicator for history
}} }}
onClick={(e) => onSelect(e.shiftKey)} onClick={isHistoryFile ? undefined : (e) => onSelect(e.shiftKey)}
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
> >
<Group gap="sm"> <Group gap="sm">
<Box> {!isHistoryFile && (
{/* Checkbox for all files */} <Box>
<Checkbox {/* Checkbox for regular files only */}
checked={isSelected} <Checkbox
onChange={() => {}} // Handled by parent onClick checked={isSelected}
size="sm" onChange={() => {}} // Handled by parent onClick
pl="sm" size="sm"
pr="xs" pl="sm"
styles={{ pr="xs"
input: { styles={{
cursor: 'pointer' input: {
} cursor: 'pointer'
}} }
/> }}
</Box> />
</Box>
)}
<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>
<Badge size="xs" variant="light" color={currentVersion > 1 ? "blue" : "gray"}> <Badge size="xs" variant="light" color={"blue"}>
v{currentVersion} v{currentVersion}
</Badge> </Badge>
@ -172,17 +177,17 @@ const FileListItem: React.FC<FileListItemProps> = ({
</> </>
)} )}
{/* Add to Recents option for history files */} {/* Restore option for history files */}
{isHistoryFile && ( {isHistoryFile && (
<> <>
<Menu.Item <Menu.Item
leftSection={<AddIcon style={{ fontSize: 16 }} />} leftSection={<RestoreIcon style={{ fontSize: 16 }} />}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onAddToRecents(file); onAddToRecents(file);
}} }}
> >
{t('fileManager.addToRecents', 'Add to Recents')} {t('fileManager.restore', 'Restore')}
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
</> </>

View File

@ -17,6 +17,7 @@ interface FileManagerContextValue {
selectedFilesSet: Set<string>; selectedFilesSet: Set<string>;
expandedFileIds: Set<string>; expandedFileIds: Set<string>;
fileGroups: Map<string, StirlingFileStub[]>; fileGroups: Map<string, StirlingFileStub[]>;
loadedHistoryFiles: Map<FileId, StirlingFileStub[]>;
// Handlers // Handlers
onSourceChange: (source: 'recent' | 'local' | 'drive') => void; onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
@ -103,24 +104,9 @@ 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 expandedFiles = []; // Only return leaf files - history files will be handled by separate components
return recentFiles;
// Since we now only load leaf files, iterate through recent files directly }, [recentFiles]);
for (const leafFile of recentFiles) {
// Add the leaf file (main file shown in list)
expandedFiles.push(leafFile);
// If expanded, add the loaded history files
if (expandedFileIds.has(leafFile.id)) {
const historyFiles = loadedHistoryFiles.get(leafFile.id) || [];
// Sort history files by version number (oldest first)
const sortedHistory = historyFiles.sort((a, b) => (a.versionNumber || 1) - (b.versionNumber || 1));
expandedFiles.push(...sortedHistory);
}
}
return expandedFiles;
}, [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));
@ -533,6 +519,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
selectedFilesSet, selectedFilesSet,
expandedFileIds, expandedFileIds,
fileGroups, fileGroups,
loadedHistoryFiles,
// Handlers // Handlers
onSourceChange: handleSourceChange, onSourceChange: handleSourceChange,
@ -564,6 +551,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
fileInputRef, fileInputRef,
expandedFileIds, expandedFileIds,
fileGroups, fileGroups,
loadedHistoryFiles,
handleSourceChange, handleSourceChange,
handleLocalFileClick, handleLocalFileClick,
handleFileSelect, handleFileSelect,