/** * SFTPView - SFTP 文件传输主组件 (简化版) * * 使用子组件拆分后的简化版本 */ import { useState, useCallback } from 'react' import { useSFTP } from '../hooks' import { FileListPanel } from './sftp/FileListPanel' import { TransferPanel } from './sftp/TransferPanel' import { Toolbar } from './sftp/Toolbar' import { ConnectionPanel } from './sftp/ConnectionPanel' import { ContextMenu, type MenuItem } from './sftp/ContextMenu' import { sortFiles } from './sftp/utils' import type { FileItem, SortField, SortOrder } from '../../shared/types/sftp.types' interface SFTPViewProps { tabId: string isActive?: boolean } export default function SFTPView({ tabId, isActive: _isActive = true }: SFTPViewProps) { const { sessions, localPath, localFiles, localSelected, setLocalSelected, localHistory, localHistoryIndex, navigateLocal, navigateLocalBack, navigateLocalForward, loadLocalDirectory, remotePath, remoteFiles, remoteSelected, setRemoteSelected, remoteHistory, remoteHistoryIndex, navigateRemote, navigateRemoteBack, navigateRemoteForward, loadRemoteDirectory, isConnected, isConnecting, connect, transferQueue, uploadFiles, downloadFiles, error, } = useSFTP({ tabId }) // UI State const [sortField, setSortField] = useState('name') const [sortOrder, setSortOrder] = useState('asc') const [isTransferPanelOpen, setIsTransferPanelOpen] = useState(true) const [showConnectionSelector, setShowConnectionSelector] = useState(false) const [contextMenu, setContextMenu] = useState<{ x: number; y: number; items: MenuItem[] } | null>(null) // Sort handler const handleSort = useCallback((field: SortField) => { if (sortField === field) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') } else { setSortField(field) setSortOrder('asc') } }, [sortField, sortOrder]) // Local selection const toggleLocalSelect = useCallback((name: string) => { setLocalSelected((prev: Set) => { const next = new Set(prev) if (next.has(name)) next.delete(name) else next.add(name) return next }) }, [setLocalSelected]) // Remote selection const toggleRemoteSelect = useCallback((name: string) => { setRemoteSelected((prev: Set) => { const next = new Set(prev) if (next.has(name)) next.delete(name) else next.add(name) return next }) }, [setRemoteSelected]) // Local context menu const showLocalContextMenu = useCallback((e: React.MouseEvent, file: FileItem) => { e.preventDefault() const items: MenuItem[] = [ { icon: '📂', label: '打开', action: () => file.type === 'directory' && navigateLocal(file.path), }, { icon: '📤', label: '上传', action: () => {}, }, ] setContextMenu({ x: e.clientX, y: e.clientY, items }) }, [navigateLocal]) // Remote context menu const showRemoteContextMenu = useCallback((e: React.MouseEvent, file: FileItem) => { e.preventDefault() const items: MenuItem[] = [ { icon: '📂', label: '打开', action: () => file.type === 'directory' && navigateRemote(file.path), }, { icon: '📥', label: '下载', action: () => { setRemoteSelected(new Set([file.name])) downloadFiles() }, }, { icon: '🗑️', label: '删除', danger: true, action: () => {}, }, ] setContextMenu({ x: e.clientX, y: e.clientY, items }) }, [navigateRemote, downloadFiles, setRemoteSelected]) // Navigate up const navigateLocalUp = useCallback(() => { const parent = localPath.split('/').slice(0, -1).join('/') || '/' navigateLocal(parent) }, [localPath, navigateLocal]) const navigateRemoteUp = useCallback(() => { const parent = remotePath.split('/').slice(0, -1).join('/') || '/' navigateRemote(parent) }, [remotePath, navigateRemote]) // Handle file open const handleLocalOpen = useCallback((file: FileItem) => { if (file.type === 'directory') { navigateLocal(file.path) } }, [navigateLocal]) const handleRemoteOpen = useCallback((file: FileItem) => { if (file.type === 'directory') { navigateRemote(file.path) } }, [navigateRemote]) // Handle upload const handleUpload = useCallback(() => { const input = document.createElement('input') input.type = 'file' input.multiple = true input.onchange = (e) => { uploadFiles((e.target as HTMLInputElement).files) } input.click() }, [uploadFiles]) // Handle new folder const handleNewFolder = useCallback(() => { const name = prompt('输入文件夹名称:') if (name && window.electronAPI?.localMkdir) { window.electronAPI.localMkdir(`${localPath}/${name}`).then(() => { loadLocalDirectory(localPath) }) } }, [localPath, loadLocalDirectory]) return (
{/* Connection Status */} sessionId && connect(sessionId)} onShowSelector={() => setShowConnectionSelector(true)} onCloseSelector={() => setShowConnectionSelector(false)} /> {/* Toolbar */} { loadLocalDirectory(localPath) if (isConnected) loadRemoteDirectory(remotePath) }} onNewFolder={handleNewFolder} /> {/* File Panels */}
{/* Local Panel */} setContextMenu(null)} /> {/* Remote Panel */} setContextMenu(null)} />
{/* Transfer Panel */} setIsTransferPanelOpen(!isTransferPanelOpen)} onCancel={() => {}} onRemove={() => {}} /> {/* Global Context Menu */} {contextMenu && ( setContextMenu(null)} /> )}
) }