cosmo/frontend/src/App.tsx

139 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/**
* 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;