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 (
CYBERPUNK REMOTE SHELL TERMINAL
{/* 操作按钮 */}