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",
"noFileSelected": "No files selected",
"showHistory": "Show History",
"hideHistory": "Hide History",
"hideHistory": "Hide History",
"fileHistory": "FileHistory",
"loadingHistory": "Loading History...",
"lastModified": "Last Modified",
"toolChain": "Tools Applied",
"addToRecents": "Add to Recents",
"restore": "Restore",
"searchFiles": "Search files...",
"recent": "Recent",
"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 { useTranslation } from 'react-i18next';
import FileListItem from './FileListItem';
import FileHistoryGroup from './FileHistoryGroup';
import { useFileManagerContext } from '../../contexts/FileManagerContext';
interface FileListAreaProps {
@ -22,6 +23,7 @@ const FileListArea: React.FC<FileListAreaProps> = ({
selectedFilesSet,
fileGroups,
expandedFileIds,
loadedHistoryFiles,
onFileSelect,
onFileRemove,
onFileDoubleClick,
@ -53,24 +55,34 @@ const FileListArea: React.FC<FileListAreaProps> = ({
</Center>
) : (
filteredFiles.map((file, index) => {
// Determine if this is a history file based on whether it's in the recent files or loaded as history
const isLeafFile = recentFiles.some(rf => rf.id === file.id);
const isHistoryFile = !isLeafFile; // If not in recent files, it's a loaded history file
const isLatestVersion = isLeafFile; // Only leaf files (from recent files) are latest versions
// All files in filteredFiles are now leaf files only
const historyFiles = loadedHistoryFiles.get(file.id) || [];
const isExpanded = expandedFileIds.has(file.id);
return (
<FileListItem
key={file.id}
file={file}
isSelected={selectedFilesSet.has(file.id)}
isSupported={isFileSupported(file.name)}
onSelect={(shiftKey) => onFileSelect(file, index, shiftKey)}
onRemove={() => onFileRemove(index)}
onDownload={() => onDownloadSingle(file)}
onDoubleClick={() => onFileDoubleClick(file)}
isHistoryFile={isHistoryFile}
isLatestVersion={isLatestVersion}
/>
<React.Fragment key={file.id}>
<FileListItem
file={file}
isSelected={selectedFilesSet.has(file.id)}
isSupported={isFileSupported(file.name)}
onSelect={(shiftKey) => onFileSelect(file, index, shiftKey)}
onRemove={() => onFileRemove(index)}
onDownload={() => onDownloadSingle(file)}
onDoubleClick={() => onFileDoubleClick(file)}
isHistoryFile={false} // All files here are leaf files
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 AddIcon from '@mui/icons-material/Add';
import HistoryIcon from '@mui/icons-material/History';
import RestoreIcon from '@mui/icons-material/Restore';
import { useTranslation } from 'react-i18next';
import { getFileSize, getFileDate } from '../../utils/fileUtils';
import { StirlingFileStub } from '../../types/fileContext';
@ -54,8 +55,10 @@ const FileListItem: React.FC<FileListItemProps> = ({
<Box
p="sm"
style={{
cursor: 'pointer',
backgroundColor: isSelected ? 'var(--mantine-color-gray-1)' : (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'),
cursor: isHistoryFile ? 'default' : 'pointer',
backgroundColor: isSelected
? 'var(--mantine-color-gray-1)'
: (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'),
opacity: isSupported ? 1 : 0.5,
transition: 'background-color 0.15s ease',
userSelect: 'none',
@ -65,32 +68,34 @@ const FileListItem: React.FC<FileListItemProps> = ({
paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files
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}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Group gap="sm">
<Box>
{/* Checkbox for all files */}
<Checkbox
checked={isSelected}
onChange={() => {}} // Handled by parent onClick
size="sm"
pl="sm"
pr="xs"
styles={{
input: {
cursor: 'pointer'
}
}}
/>
</Box>
{!isHistoryFile && (
<Box>
{/* Checkbox for regular files only */}
<Checkbox
checked={isSelected}
onChange={() => {}} // Handled by parent onClick
size="sm"
pl="sm"
pr="xs"
styles={{
input: {
cursor: 'pointer'
}
}}
/>
</Box>
)}
<Box style={{ flex: 1, minWidth: 0 }}>
<Group gap="xs" align="center">
<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}
</Badge>
@ -172,17 +177,17 @@ const FileListItem: React.FC<FileListItemProps> = ({
</>
)}
{/* Add to Recents option for history files */}
{/* Restore option for history files */}
{isHistoryFile && (
<>
<Menu.Item
leftSection={<AddIcon style={{ fontSize: 16 }} />}
leftSection={<RestoreIcon style={{ fontSize: 16 }} />}
onClick={(e) => {
e.stopPropagation();
onAddToRecents(file);
}}
>
{t('fileManager.addToRecents', 'Add to Recents')}
{t('fileManager.restore', 'Restore')}
</Menu.Item>
<Menu.Divider />
</>

View File

@ -17,6 +17,7 @@ interface FileManagerContextValue {
selectedFilesSet: Set<string>;
expandedFileIds: Set<string>;
fileGroups: Map<string, StirlingFileStub[]>;
loadedHistoryFiles: Map<FileId, StirlingFileStub[]>;
// Handlers
onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
@ -103,24 +104,9 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
const displayFiles = useMemo(() => {
if (!recentFiles || recentFiles.length === 0) return [];
const expandedFiles = [];
// Since we now only load leaf files, iterate through recent files directly
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]);
// Only return leaf files - history files will be handled by separate components
return recentFiles;
}, [recentFiles]);
const selectedFiles = selectedFileIds.length === 0 ? [] :
displayFiles.filter(file => selectedFilesSet.has(file.id));
@ -533,6 +519,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
selectedFilesSet,
expandedFileIds,
fileGroups,
loadedHistoryFiles,
// Handlers
onSourceChange: handleSourceChange,
@ -564,6 +551,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
fileInputRef,
expandedFileIds,
fileGroups,
loadedHistoryFiles,
handleSourceChange,
handleLocalFileClick,
handleFileSelect,