/** * SFTP File row component for file list */ import { Folder, Link } from 'lucide-react'; import React, { memo, useCallback } from 'react'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { cn } from '../../lib/utils'; import { SftpFileEntry } from '../../types'; import { sftpFileRowDensityClass, sftpFileRowIconDensityClass, type SftpListDensity, } from '../../domain/sftpListDensity'; import { buildSftpColumnTemplate, formatBytes, formatDate, getFileIcon, isNavigableDirectory, type ColumnWidths, type SftpColumnVisibility } from './utils'; interface SftpFileRowProps { entry: SftpFileEntry; index: number; isSelected: boolean; showSelectionHighlight: boolean; isDragOver: boolean; columnWidths: ColumnWidths; visibleColumns: SftpColumnVisibility; onSelect: (entry: SftpFileEntry, index: number, e: React.MouseEvent) => void; onOpen: (entry: SftpFileEntry) => void; onDragStart: (entry: SftpFileEntry, e: React.DragEvent) => void; onDragEnd: () => void; onDragOver: (entry: SftpFileEntry, e: React.DragEvent) => void; onDragLeave: () => void; onDrop: (entry: SftpFileEntry, e: React.DragEvent) => void; density?: SftpListDensity; } const SftpFileRowInner: React.FC = ({ entry, index, isSelected, showSelectionHighlight, isDragOver, columnWidths, visibleColumns, onSelect, onOpen, onDragStart, onDragEnd, onDragOver, onDragLeave, onDrop, density = "comfortable", }) => { const isParentDir = entry.name === '..'; // A symlink pointing to a directory behaves like a directory (navigable, accepts drops) const isNavDir = isNavigableDirectory(entry); const isSymlinkToDirectory = entry.type === 'symlink' && entry.linkTarget === 'directory'; const modifiedLabel = entry.lastModifiedFormatted || formatDate(entry.lastModified); const sizeLabel = entry.sizeFormatted || formatBytes(entry.size); const handleSelect = useCallback((e: React.MouseEvent) => { onSelect(entry, index, e); }, [entry, index, onSelect]); const handleOpen = useCallback(() => { onOpen(entry); }, [entry, onOpen]); const handleDragStart = useCallback((e: React.DragEvent) => { onDragStart(entry, e); }, [entry, onDragStart]); const handleDragOver = useCallback((e: React.DragEvent) => { onDragOver(entry, e); }, [entry, onDragOver]); const handleDrop = useCallback((e: React.DragEvent) => { onDrop(entry, e); }, [entry, onDrop]); const isSelectionVisible = isSelected && showSelectionHighlight; return (
{isNavDir ? : getFileIcon(entry)} {/* Show link indicator for symlinks */} {entry.type === 'symlink' && ( )}
{entry.name} {entry.type === 'symlink' && (symbolic link)} {entry.name}
{visibleColumns.modified && ( {modifiedLabel} )} {visibleColumns.size && ( {isNavDir ? '--' : sizeLabel} )} {visibleColumns.type && ( {isSymlinkToDirectory ? 'link → folder' : entry.type === 'directory' ? 'folder' : entry.type === 'symlink' ? 'link' : entry.name.split('.').pop()?.toLowerCase() || 'file'} )} {visibleColumns.owner && ( {isParentDir ? '' : (entry.owner || '--')} )}
); }; const areEqual = (prev: SftpFileRowProps, next: SftpFileRowProps): boolean => { if (prev.index !== next.index) return false; if (prev.isSelected !== next.isSelected) return false; // Only re-render for showSelectionHighlight changes when the row is actually selected if (prev.isSelected && prev.showSelectionHighlight !== next.showSelectionHighlight) return false; if (prev.isDragOver !== next.isDragOver) return false; if (prev.columnWidths.name !== next.columnWidths.name) return false; if (prev.columnWidths.modified !== next.columnWidths.modified) return false; if (prev.columnWidths.size !== next.columnWidths.size) return false; if (prev.columnWidths.type !== next.columnWidths.type) return false; if (prev.columnWidths.owner !== next.columnWidths.owner) return false; if (prev.visibleColumns.modified !== next.visibleColumns.modified) return false; if (prev.visibleColumns.size !== next.visibleColumns.size) return false; if (prev.visibleColumns.type !== next.visibleColumns.type) return false; if (prev.visibleColumns.owner !== next.visibleColumns.owner) return false; // Compare callbacks - important for ".." entry which has static properties if (prev.onOpen !== next.onOpen) return false; if (prev.onSelect !== next.onSelect) return false; if (prev.density !== next.density) return false; const prevEntry = prev.entry; const nextEntry = next.entry; return ( prevEntry.name === nextEntry.name && prevEntry.type === nextEntry.type && prevEntry.size === nextEntry.size && prevEntry.lastModified === nextEntry.lastModified && prevEntry.linkTarget === nextEntry.linkTarget && prevEntry.sizeFormatted === nextEntry.sizeFormatted && prevEntry.lastModifiedFormatted === nextEntry.lastModifiedFormatted && prevEntry.owner === nextEntry.owner ); }; export const SftpFileRow = memo(SftpFileRowInner, areEqual); SftpFileRow.displayName = 'SftpFileRow';