cosmo/frontend/src/App.tsx

139 lines
4.7 KiB
TypeScript
Raw Normal View History

/**
* Cosmo - Deep Space Explorer
* Main application component
*/
2025-11-27 10:14:25 +00:00
import { useState, useCallback } from 'react';
import { useSpaceData } from './hooks/useSpaceData';
2025-11-27 10:14:25 +00:00
import { useHistoricalData } from './hooks/useHistoricalData';
import { useTrajectory } from './hooks/useTrajectory';
import { Scene } from './components/Scene';
import { ProbeList } from './components/ProbeList';
2025-11-27 10:14:25 +00:00
import { TimelineController } from './components/TimelineController';
import { Loading } from './components/Loading';
import type { CelestialBody } from './types';
function App() {
2025-11-27 10:14:25 +00:00
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);
2025-11-27 10:14:25 +00:00
// 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>
2025-11-27 10:14:25 +00:00
<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}
/>
2025-11-27 10:14:25 +00:00
{/* 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;