139 lines
4.7 KiB
TypeScript
139 lines
4.7 KiB
TypeScript
/**
|
||
* Cosmo - Deep Space Explorer
|
||
* Main application component
|
||
*/
|
||
import { useState, useCallback } from 'react';
|
||
import { useSpaceData } from './hooks/useSpaceData';
|
||
import { useHistoricalData } from './hooks/useHistoricalData';
|
||
import { useTrajectory } from './hooks/useTrajectory';
|
||
import { Scene } from './components/Scene';
|
||
import { ProbeList } from './components/ProbeList';
|
||
import { TimelineController } from './components/TimelineController';
|
||
import { Loading } from './components/Loading';
|
||
import type { CelestialBody } from './types';
|
||
|
||
function App() {
|
||
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
||
const [isTimelineMode, setIsTimelineMode] = useState(false);
|
||
|
||
// Use real-time data or historical data based on mode
|
||
const { bodies: realTimeBodies, loading: realTimeLoading, error: realTimeError } = useSpaceData();
|
||
const { bodies: historicalBodies, loading: historicalLoading, error: historicalError } = useHistoricalData(selectedDate);
|
||
|
||
const bodies = isTimelineMode ? historicalBodies : realTimeBodies;
|
||
const loading = isTimelineMode ? historicalLoading : realTimeLoading;
|
||
const error = isTimelineMode ? historicalError : realTimeError;
|
||
|
||
const [selectedBody, setSelectedBody] = useState<CelestialBody | null>(null);
|
||
const { trajectoryPositions } = useTrajectory(selectedBody);
|
||
|
||
// Handle time change from timeline controller
|
||
const handleTimeChange = useCallback((date: Date) => {
|
||
setSelectedDate(date);
|
||
}, []);
|
||
|
||
// Toggle timeline mode
|
||
const toggleTimelineMode = useCallback(() => {
|
||
setIsTimelineMode((prev) => !prev);
|
||
if (!isTimelineMode) {
|
||
// Entering timeline mode, set initial date to Cassini launch (1997)
|
||
setSelectedDate(new Date(1997, 0, 1));
|
||
} else {
|
||
setSelectedDate(null);
|
||
}
|
||
}, [isTimelineMode]);
|
||
|
||
// Filter probes and planets from all bodies
|
||
const probes = bodies.filter((b) => b.type === 'probe');
|
||
const planets = bodies.filter((b) => b.type === 'planet');
|
||
|
||
const handleBodySelect = (body: CelestialBody | null) => {
|
||
setSelectedBody(body);
|
||
};
|
||
|
||
if (loading) {
|
||
return <Loading />;
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="w-full h-full flex items-center justify-center bg-black text-white">
|
||
<div className="text-center">
|
||
<h1 className="text-2xl font-bold mb-4">数据加载失败</h1>
|
||
<p className="text-red-400">{error}</p>
|
||
<p className="mt-4 text-sm text-gray-400">
|
||
请确保后端 API 运行在 http://localhost:8000
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="w-full h-full relative">
|
||
{/* Title overlay */}
|
||
<div className="absolute top-4 left-4 z-50 text-white">
|
||
<h1 className="text-3xl font-bold mb-1">Cosmo</h1>
|
||
<p className="text-sm text-gray-300">深空探测器可视化</p>
|
||
<p className="text-xs text-gray-400 mt-1">
|
||
{selectedBody ? `聚焦: ${selectedBody.name}` : `${bodies.length} 个天体`}
|
||
</p>
|
||
<button
|
||
onClick={toggleTimelineMode}
|
||
className={`mt-2 px-4 py-2 rounded text-sm font-medium transition-colors ${
|
||
isTimelineMode
|
||
? 'bg-blue-600 hover:bg-blue-700 text-white'
|
||
: 'bg-gray-700 hover:bg-gray-600 text-gray-300'
|
||
}`}
|
||
>
|
||
{isTimelineMode ? '🕐 时间轴模式 (点击退出)' : '📅 切换到时间轴模式'}
|
||
</button>
|
||
</div>
|
||
|
||
{/* Probe List Sidebar */}
|
||
<ProbeList
|
||
probes={probes}
|
||
planets={planets}
|
||
onBodySelect={handleBodySelect}
|
||
selectedBody={selectedBody}
|
||
/>
|
||
|
||
{/* 3D Scene */}
|
||
<Scene
|
||
bodies={bodies}
|
||
selectedBody={selectedBody}
|
||
trajectoryPositions={trajectoryPositions}
|
||
/>
|
||
|
||
{/* Timeline Controller */}
|
||
{isTimelineMode && (
|
||
<TimelineController
|
||
onTimeChange={handleTimeChange}
|
||
minDate={new Date(1997, 0, 1)} // Cassini launch date
|
||
maxDate={new Date()}
|
||
/>
|
||
)}
|
||
|
||
{/* Instructions overlay */}
|
||
<div className="absolute bottom-4 right-4 z-50 text-white text-xs bg-black bg-opacity-70 p-3 rounded">
|
||
{selectedBody ? (
|
||
<>
|
||
<p className="text-cyan-400 font-bold mb-2">聚焦模式</p>
|
||
<p>点击侧边栏的"返回太阳系视图"按钮</p>
|
||
</>
|
||
) : (
|
||
<>
|
||
<p className="font-bold mb-2">太阳系俯视图</p>
|
||
<p>🖱️ 左键拖动: 旋转</p>
|
||
<p>🖱️ 右键拖动: 平移</p>
|
||
<p>🖱️ 滚轮: 缩放</p>
|
||
<p className="mt-2 text-gray-400">点击左侧天体列表查看详情</p>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default App;
|