import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import TitleBar from './components/TitleBar'; import Sidebar from './components/Sidebar'; import Terminal from './components/Terminal'; import HostManager from './components/HostManager'; import Settings from './components/Settings'; import CommandPalette from './components/CommandPalette'; import HostInfoPanel from './components/HostInfoPanel'; import SFTPBrowser from './components/SFTPBrowser'; import ServerConfig from './components/ServerConfig'; import HostEditPanel from './components/HostEditPanel'; import { getAPI, platform } from './services/api'; function App() { const [hosts, setHosts] = useState([]); const [activeTabs, setActiveTabs] = useState([]); const [activeTabId, setActiveTabId] = useState(null); const [showHostManager, setShowHostManager] = useState(false); const [editingHost, setEditingHost] = useState(null); const [showSettings, setShowSettings] = useState(false); const [showCommandPalette, setShowCommandPalette] = useState(false); const [isRemoteConnected, setIsRemoteConnected] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [showInfoPanel, setShowInfoPanel] = useState(false); const [showSFTP, setShowSFTP] = useState(false); const [showServerConfig, setShowServerConfig] = useState(false); const [selectedHost, setSelectedHost] = useState(null); // 选中的主机(用于右侧编辑面板) // 获取跨平台 API const api = useMemo(() => getAPI(), []); // 检测是否是移动端 const isMobile = platform.isMobile(); // 加载主机列表 const loadHosts = useCallback(async () => { const hostList = await api.hosts.getAll(); setHosts(hostList || []); }, [api]); // 检查远程连接状态 const checkRemoteStatus = useCallback(async () => { const connected = await api.db.isRemoteConnected(); setIsRemoteConnected(connected); // 如果已连接,刷新主机列表(因为启动时可能已自动同步) if (connected) { loadHosts(); } }, [api, loadHosts]); useEffect(() => { loadHosts(); checkRemoteStatus(); }, [loadHosts, checkRemoteStatus]); // 关闭标签页 (需要在 useEffect 之前定义) const closeTab = useCallback((tabId) => { setActiveTabs((prev) => { const newTabs = prev.filter((t) => t.id !== tabId); return newTabs; }); setActiveTabId((prevActiveId) => { if (prevActiveId === tabId) { // 找到要关闭的标签的索引 const tabIndex = activeTabs.findIndex(t => t.id === tabId); const remainingTabs = activeTabs.filter((t) => t.id !== tabId); if (remainingTabs.length > 0) { // 优先切换到右边的标签,否则切换到左边的 const newIndex = Math.min(tabIndex, remainingTabs.length - 1); return remainingTabs[newIndex].id; } return null; } return prevActiveId; }); }, [activeTabs]); // 键盘快捷键 useEffect(() => { const handleKeyDown = (e) => { // Ctrl+K: 打开命令面板 if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); setShowCommandPalette(true); } // Ctrl+W: 关闭当前标签页 if ((e.ctrlKey || e.metaKey) && e.key === 'w') { e.preventDefault(); if (activeTabId) { closeTab(activeTabId); } } // Escape: 关闭弹窗 if (e.key === 'Escape') { setShowCommandPalette(false); setShowHostManager(false); setShowSettings(false); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [activeTabId, closeTab]); // 连接主机 const connectHost = useCallback((host) => { const tabId = `terminal-${host.id}-${Date.now()}`; const newTab = { id: tabId, hostId: host.id, title: host.name, host: host.host, type: 'terminal', connected: false, }; setActiveTabs((prev) => [...prev, newTab]); setActiveTabId(tabId); setShowHostManager(false); setSelectedHost(null); // 关闭右侧编辑面板 }, []); // 更新连接状态 const handleConnectionChange = useCallback((tabId, connected) => { setActiveTabs((prev) => prev.map((t) => (t.id === tabId ? { ...t, connected } : t)) ); }, []); // 处理主机更新 const handleHostsUpdate = useCallback(() => { loadHosts(); }, [loadHosts]); // 编辑主机 - 打开模态框 const handleEditHost = useCallback((host) => { setEditingHost(host); setShowHostManager(true); }, []); // 选中主机 - 右侧面板编辑 const handleSelectHost = useCallback((host) => { setSelectedHost(host); }, []); // 新增主机 - 右侧面板 const handleAddNewHost = useCallback(() => { setSelectedHost({}); // 空对象表示新建 }, []); const openHostManager = useCallback(() => { setEditingHost(null); setShowHostManager(true); }, []); const openSettings = useCallback(() => { setShowSettings(true); }, []); const openCommandPalette = useCallback(() => { setShowCommandPalette(true); }, []); return (
{/* 桌面端显示标题栏 */} {!isMobile && } {/* 移动端顶部栏 */} {isMobile && (
EasyShell EASYSHELL
)}
setSidebarCollapsed(!sidebarCollapsed)} isMobile={isMobile} onOpenServerConfig={() => setShowServerConfig(true)} />
{/* 标签栏 */} {activeTabs.length > 0 && (
{/* 背景装饰 */}
{activeTabs.map((tab, index) => ( setActiveTabId(tab.id)} > {/* 活动指示器 */} {activeTabId === tab.id && ( )} {/* 连接状态 */} {/* 标签名 */} {tab.title} {/* 关闭按钮 */} { e.stopPropagation(); closeTab(tab.id); }} className="opacity-0 group-hover:opacity-100 w-5 h-5 rounded flex items-center justify-center hover:bg-shell-error/20 hover:text-shell-error transition-all ml-1" > × ))}
)} {/* 终端内容 - 所有终端都渲染,通过显示/隐藏切换 */}
{activeTabs.length === 0 ? (
{/* 背景装饰 */}
{/* Logo */}
EasyShell
{/* 标题 */}

WELCOME TO EASYSHELL

CYBERPUNK REMOTE SHELL TERMINAL

{/* 操作按钮 */}
+ 添加主机 ⚡ 云端同步
{/* 快捷键提示 */} Ctrl + K 打开命令面板 {/* 装饰性扫描线 */}
{[...Array(5)].map((_, i) => ( ))}
) : ( activeTabs.map((tab) => (
handleConnectionChange(tab.id, connected)} onShowCommandPalette={openCommandPalette} onToggleInfoPanel={() => setShowInfoPanel(!showInfoPanel)} onOpenSFTP={() => setShowSFTP(true)} showInfoPanel={showInfoPanel} onCloseTab={() => closeTab(tab.id)} />
)) )}
{/* 右侧主机信息面板 */} {showInfoPanel && activeTabId && activeTabs.find(t => t.id === activeTabId) && ( t.id === activeTabId)?.hostId} connectionId={activeTabId} isConnected={activeTabs.find(t => t.id === activeTabId)?.connected} onOpenSFTP={() => setShowSFTP(true)} onClose={() => setShowInfoPanel(false)} /> )} {/* 右侧主机编辑面板 */} {selectedHost && ( setSelectedHost(null)} onConnect={(host) => { connectHost(host); setSelectedHost(null); }} onUpdate={() => { loadHosts(); }} onDelete={() => { loadHosts(); setSelectedHost(null); }} /> )}
{/* 弹窗 */} {showHostManager && ( { setShowHostManager(false); setEditingHost(null); }} onConnect={connectHost} onUpdate={handleHostsUpdate} /> )} {showSettings && ( setShowSettings(false)} isRemoteConnected={isRemoteConnected} onConnectionChange={(connected) => { setIsRemoteConnected(connected); if (connected) loadHosts(); }} onHostsUpdate={loadHosts} /> )} {showCommandPalette && ( setShowCommandPalette(false)} onSelectCommand={(cmd) => { if (activeTabId) { const event = new CustomEvent('terminal-command', { detail: { tabId: activeTabId, command: cmd }, }); window.dispatchEvent(event); } setShowCommandPalette(false); }} /> )} {/* SFTP 文件浏览器 */} {showSFTP && activeTabId && activeTabs.find(t => t.id === activeTabId) && ( t.id === activeTabId)?.hostId} isConnected={activeTabs.find(t => t.id === activeTabId)?.connected} onClose={() => setShowSFTP(false)} /> )} {/* 服务器配置 (移动端) */} setShowServerConfig(false)} />
); } export default App;