2025-08-08 15:15:09 +01:00
|
|
|
import React, { useState } from 'react';
|
2025-09-04 12:11:09 +01:00
|
|
|
import { Group, Box, Text, ActionIcon, Checkbox, Divider, Menu, Badge, Button, Loader } from '@mantine/core';
|
2025-08-20 16:51:55 +01:00
|
|
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
2025-08-08 15:15:09 +01:00
|
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
2025-08-20 16:51:55 +01:00
|
|
|
import DownloadIcon from '@mui/icons-material/Download';
|
2025-09-03 17:47:58 +01:00
|
|
|
import AddIcon from '@mui/icons-material/Add';
|
2025-09-02 17:24:26 +01:00
|
|
|
import HistoryIcon from '@mui/icons-material/History';
|
2025-08-20 16:51:55 +01:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2025-08-08 15:15:09 +01:00
|
|
|
import { getFileSize, getFileDate } from '../../utils/fileUtils';
|
2025-08-21 17:30:26 +01:00
|
|
|
import { FileMetadata } from '../../types/file';
|
2025-09-02 17:24:26 +01:00
|
|
|
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
2025-09-03 17:47:58 +01:00
|
|
|
import ToolChain from '../shared/ToolChain';
|
2025-08-08 15:15:09 +01:00
|
|
|
|
|
|
|
interface FileListItemProps {
|
2025-08-21 17:30:26 +01:00
|
|
|
file: FileMetadata;
|
2025-08-08 15:15:09 +01:00
|
|
|
isSelected: boolean;
|
|
|
|
isSupported: boolean;
|
2025-08-20 16:51:55 +01:00
|
|
|
onSelect: (shiftKey?: boolean) => void;
|
2025-08-08 15:15:09 +01:00
|
|
|
onRemove: () => void;
|
2025-08-20 16:51:55 +01:00
|
|
|
onDownload?: () => void;
|
2025-08-08 15:15:09 +01:00
|
|
|
onDoubleClick?: () => void;
|
|
|
|
isLast?: boolean;
|
2025-09-03 17:47:58 +01:00
|
|
|
isHistoryFile?: boolean; // Whether this is a history file (indented)
|
|
|
|
isLatestVersion?: boolean; // Whether this is the latest version (shows chevron)
|
2025-08-08 15:15:09 +01:00
|
|
|
}
|
|
|
|
|
2025-08-20 16:51:55 +01:00
|
|
|
const FileListItem: React.FC<FileListItemProps> = ({
|
|
|
|
file,
|
|
|
|
isSelected,
|
|
|
|
isSupported,
|
|
|
|
onSelect,
|
|
|
|
onRemove,
|
|
|
|
onDownload,
|
2025-09-03 17:47:58 +01:00
|
|
|
onDoubleClick,
|
|
|
|
isHistoryFile = false,
|
|
|
|
isLatestVersion = false
|
2025-08-08 15:15:09 +01:00
|
|
|
}) => {
|
|
|
|
const [isHovered, setIsHovered] = useState(false);
|
2025-08-20 16:51:55 +01:00
|
|
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
|
|
const { t } = useTranslation();
|
2025-09-04 12:11:09 +01:00
|
|
|
const { fileGroups, expandedFileIds, onToggleExpansion, onAddToRecents, isLoadingHistory, getHistoryError } = useFileManagerContext();
|
2025-08-20 16:51:55 +01:00
|
|
|
|
|
|
|
// Keep item in hovered state if menu is open
|
|
|
|
const shouldShowHovered = isHovered || isMenuOpen;
|
2025-08-08 15:15:09 +01:00
|
|
|
|
2025-09-02 17:24:26 +01:00
|
|
|
// Get version information for this file
|
2025-09-03 17:47:58 +01:00
|
|
|
const leafFileId = isLatestVersion ? file.id : (file.originalFileId || file.id);
|
|
|
|
const lineagePath = fileGroups.get(leafFileId) || [];
|
2025-09-04 12:11:09 +01:00
|
|
|
const hasVersionHistory = (file.versionNumber || 0) > 0; // Show history for any processed file (v1+)
|
2025-09-02 17:24:26 +01:00
|
|
|
const currentVersion = file.versionNumber || 0; // Display original files as v0
|
2025-09-03 17:47:58 +01:00
|
|
|
const isExpanded = expandedFileIds.has(leafFileId);
|
2025-09-04 12:11:09 +01:00
|
|
|
|
|
|
|
// Get loading state for this file's history
|
|
|
|
const isLoadingFileHistory = isLoadingHistory(file.id);
|
|
|
|
const historyError = getHistoryError(file.id);
|
2025-09-02 17:24:26 +01:00
|
|
|
|
2025-08-08 15:15:09 +01:00
|
|
|
return (
|
|
|
|
<>
|
2025-08-20 16:51:55 +01:00
|
|
|
<Box
|
|
|
|
p="sm"
|
|
|
|
style={{
|
2025-08-08 15:15:09 +01:00
|
|
|
cursor: 'pointer',
|
2025-08-20 16:51:55 +01:00
|
|
|
backgroundColor: isSelected ? 'var(--mantine-color-gray-1)' : (shouldShowHovered ? 'var(--mantine-color-gray-1)' : 'var(--bg-file-list)'),
|
2025-08-08 15:15:09 +01:00
|
|
|
opacity: isSupported ? 1 : 0.5,
|
2025-08-20 16:51:55 +01:00
|
|
|
transition: 'background-color 0.15s ease',
|
|
|
|
userSelect: 'none',
|
|
|
|
WebkitUserSelect: 'none',
|
|
|
|
MozUserSelect: 'none',
|
2025-09-03 17:47:58 +01:00
|
|
|
msUserSelect: 'none',
|
|
|
|
paddingLeft: isHistoryFile ? '2rem' : '0.75rem', // Indent history files
|
|
|
|
borderLeft: isHistoryFile ? '3px solid var(--mantine-color-blue-4)' : 'none' // Visual indicator for history
|
2025-08-08 15:15:09 +01:00
|
|
|
}}
|
2025-08-20 16:51:55 +01:00
|
|
|
onClick={(e) => onSelect(e.shiftKey)}
|
2025-08-08 15:15:09 +01:00
|
|
|
onDoubleClick={onDoubleClick}
|
|
|
|
onMouseEnter={() => setIsHovered(true)}
|
|
|
|
onMouseLeave={() => setIsHovered(false)}
|
|
|
|
>
|
|
|
|
<Group gap="sm">
|
|
|
|
<Box>
|
2025-09-03 17:47:58 +01:00
|
|
|
{/* Checkbox for all files */}
|
2025-08-08 15:15:09 +01:00
|
|
|
<Checkbox
|
|
|
|
checked={isSelected}
|
|
|
|
onChange={() => {}} // Handled by parent onClick
|
|
|
|
size="sm"
|
|
|
|
pl="sm"
|
|
|
|
pr="xs"
|
|
|
|
styles={{
|
|
|
|
input: {
|
|
|
|
cursor: 'pointer'
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</Box>
|
2025-08-20 16:51:55 +01:00
|
|
|
|
2025-08-08 15:15:09 +01:00
|
|
|
<Box style={{ flex: 1, minWidth: 0 }}>
|
2025-08-21 17:30:26 +01:00
|
|
|
<Group gap="xs" align="center">
|
|
|
|
<Text size="sm" fw={500} truncate style={{ flex: 1 }}>{file.name}</Text>
|
2025-09-04 12:11:09 +01:00
|
|
|
{isLoadingFileHistory && <Loader size={14} />}
|
2025-09-03 17:47:58 +01:00
|
|
|
<Badge size="xs" variant="light" color={currentVersion > 0 ? "blue" : "gray"}>
|
2025-09-02 17:24:26 +01:00
|
|
|
v{currentVersion}
|
|
|
|
</Badge>
|
2025-09-03 17:47:58 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
</Group>
|
2025-09-03 17:47:58 +01:00
|
|
|
<Group gap="xs" align="center">
|
|
|
|
<Text size="xs" c="dimmed">
|
|
|
|
{getFileSize(file)} • {getFileDate(file)}
|
|
|
|
{hasVersionHistory && (
|
2025-09-04 12:11:09 +01:00
|
|
|
<Text span c="dimmed"> • has history</Text>
|
2025-09-03 17:47:58 +01:00
|
|
|
)}
|
|
|
|
</Text>
|
2025-09-03 17:57:39 +01:00
|
|
|
|
2025-09-03 17:47:58 +01:00
|
|
|
{/* Tool chain for processed files */}
|
|
|
|
{file.historyInfo?.toolChain && file.historyInfo.toolChain.length > 0 && (
|
2025-09-03 17:57:39 +01:00
|
|
|
<ToolChain
|
2025-09-03 17:47:58 +01:00
|
|
|
toolChain={file.historyInfo.toolChain}
|
2025-09-03 17:57:39 +01:00
|
|
|
maxWidth={'150px'}
|
2025-09-03 17:47:58 +01:00
|
|
|
displayStyle="text"
|
|
|
|
size="xs"
|
|
|
|
/>
|
2025-09-02 17:24:26 +01:00
|
|
|
)}
|
2025-09-03 17:47:58 +01:00
|
|
|
</Group>
|
2025-08-08 15:15:09 +01:00
|
|
|
</Box>
|
2025-08-20 16:51:55 +01:00
|
|
|
|
|
|
|
{/* Three dots menu - fades in/out on hover */}
|
|
|
|
<Menu
|
|
|
|
position="bottom-end"
|
|
|
|
withinPortal
|
|
|
|
onOpen={() => setIsMenuOpen(true)}
|
|
|
|
onClose={() => setIsMenuOpen(false)}
|
2025-08-08 15:15:09 +01:00
|
|
|
>
|
2025-08-20 16:51:55 +01:00
|
|
|
<Menu.Target>
|
|
|
|
<ActionIcon
|
|
|
|
variant="subtle"
|
|
|
|
c="dimmed"
|
|
|
|
size="md"
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
style={{
|
|
|
|
opacity: shouldShowHovered ? 1 : 0,
|
|
|
|
transform: shouldShowHovered ? 'scale(1)' : 'scale(0.8)',
|
|
|
|
transition: 'opacity 0.3s ease, transform 0.3s ease',
|
|
|
|
pointerEvents: shouldShowHovered ? 'auto' : 'none'
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<MoreVertIcon style={{ fontSize: 20 }} />
|
|
|
|
</ActionIcon>
|
|
|
|
</Menu.Target>
|
|
|
|
|
|
|
|
<Menu.Dropdown>
|
|
|
|
{onDownload && (
|
|
|
|
<Menu.Item
|
|
|
|
leftSection={<DownloadIcon style={{ fontSize: 16 }} />}
|
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation();
|
|
|
|
onDownload();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t('fileManager.download', 'Download')}
|
|
|
|
</Menu.Item>
|
|
|
|
)}
|
2025-09-03 17:47:58 +01:00
|
|
|
|
|
|
|
{/* Show/Hide History option for latest version files */}
|
|
|
|
{isLatestVersion && hasVersionHistory && (
|
2025-09-02 17:24:26 +01:00
|
|
|
<>
|
2025-09-03 17:47:58 +01:00
|
|
|
<Menu.Item
|
2025-09-04 12:11:09 +01:00
|
|
|
leftSection={
|
|
|
|
isLoadingFileHistory ?
|
|
|
|
<Loader size={16} /> :
|
|
|
|
<HistoryIcon style={{ fontSize: 16 }} />
|
|
|
|
}
|
2025-09-03 17:47:58 +01:00
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation();
|
|
|
|
onToggleExpansion(leafFileId);
|
|
|
|
}}
|
2025-09-04 12:11:09 +01:00
|
|
|
disabled={isLoadingFileHistory}
|
2025-09-03 17:47:58 +01:00
|
|
|
>
|
2025-09-04 12:11:09 +01:00
|
|
|
{isLoadingFileHistory ?
|
|
|
|
t('fileManager.loadingHistory', 'Loading History...') :
|
|
|
|
(isExpanded ?
|
|
|
|
t('fileManager.hideHistory', 'Hide History') :
|
|
|
|
t('fileManager.showHistory', 'Show History')
|
|
|
|
)
|
2025-09-03 17:47:58 +01:00
|
|
|
}
|
|
|
|
</Menu.Item>
|
2025-09-04 12:11:09 +01:00
|
|
|
{historyError && (
|
|
|
|
<Menu.Item disabled c="red" style={{ fontSize: '12px' }}>
|
|
|
|
{t('fileManager.historyError', 'Error loading history')}
|
|
|
|
</Menu.Item>
|
|
|
|
)}
|
2025-09-02 17:24:26 +01:00
|
|
|
<Menu.Divider />
|
2025-09-03 17:47:58 +01:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* Add to Recents option for history files */}
|
|
|
|
{isHistoryFile && (
|
|
|
|
<>
|
|
|
|
<Menu.Item
|
|
|
|
leftSection={<AddIcon style={{ fontSize: 16 }} />}
|
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation();
|
|
|
|
onAddToRecents(file);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t('fileManager.addToRecents', 'Add to Recents')}
|
|
|
|
</Menu.Item>
|
2025-09-02 17:24:26 +01:00
|
|
|
<Menu.Divider />
|
|
|
|
</>
|
|
|
|
)}
|
2025-09-03 17:47:58 +01:00
|
|
|
|
2025-08-20 16:51:55 +01:00
|
|
|
<Menu.Item
|
|
|
|
leftSection={<DeleteIcon style={{ fontSize: 16 }} />}
|
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation();
|
|
|
|
onRemove();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t('fileManager.delete', 'Delete')}
|
|
|
|
</Menu.Item>
|
|
|
|
</Menu.Dropdown>
|
|
|
|
</Menu>
|
2025-08-08 15:15:09 +01:00
|
|
|
</Group>
|
|
|
|
</Box>
|
|
|
|
{ <Divider color="var(--mantine-color-gray-3)" />}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-08-20 16:51:55 +01:00
|
|
|
export default FileListItem;
|