cosmo/frontend/src/components/ProbeList.tsx

156 lines
5.7 KiB
TypeScript

/**
* ProbeList component - sidebar showing planets and probes
*/
import { useState } from 'react';
import type { CelestialBody } from '../types';
interface ProbeListProps {
probes: CelestialBody[];
planets: CelestialBody[];
onBodySelect: (body: CelestialBody | null) => void;
selectedBody: CelestialBody | null;
}
export function ProbeList({ probes, planets, onBodySelect, selectedBody }: ProbeListProps) {
const [isExpanded, setIsExpanded] = useState(true);
// Calculate distance for each probe
const probesWithDistance = probes.map((probe) => {
const pos = probe.positions[0];
const distance = Math.sqrt(pos.x ** 2 + pos.y ** 2 + pos.z ** 2);
return { body: probe, distance };
});
// Calculate distance for each planet (exclude Sun)
const planetsWithDistance = planets
.filter((p) => p.type !== 'star') // Exclude Sun
.map((planet) => {
const pos = planet.positions[0];
const distance = Math.sqrt(pos.x ** 2 + pos.y ** 2 + pos.z ** 2);
return { body: planet, distance };
});
// Sort by distance
probesWithDistance.sort((a, b) => a.distance - b.distance);
planetsWithDistance.sort((a, b) => a.distance - b.distance);
const totalCount = probes.length + planets.length - 1; // -1 for Sun
// Collapsed state - show only toggle button
if (!isExpanded) {
return (
<button
onClick={() => setIsExpanded(true)}
className="absolute top-36 left-4 z-50 bg-black bg-opacity-80 backdrop-blur-sm rounded-lg p-3 hover:bg-opacity-90 transition-all"
title="展开天体列表"
>
<div className="flex items-center gap-2 text-white">
<span className="text-lg">🌍</span>
<span className="text-sm font-medium"></span>
<span className="text-gray-400">({totalCount})</span>
</div>
</button>
);
}
return (
<div className="absolute top-36 left-4 z-50 bg-black bg-opacity-80 backdrop-blur-sm rounded-lg p-4 max-w-xs">
<div className="flex items-center justify-between mb-3">
<h2 className="text-white text-lg font-bold flex items-center gap-2">
🌍
</h2>
<button
onClick={() => setIsExpanded(false)}
className="text-gray-400 hover:text-white transition-colors text-sm"
title="收起列表"
>
</button>
</div>
{/* Planets Section */}
<div className="mb-4">
<h3 className="text-white text-sm font-semibold mb-2 text-gray-300">
({planetsWithDistance.length})
</h3>
{planetsWithDistance.length === 0 ? (
<div className="text-gray-500 text-xs p-2">...</div>
) : (
<div className="space-y-2 max-h-64 overflow-y-auto">
{planetsWithDistance.map(({ body, distance }) => (
<button
key={body.id}
onClick={() => onBodySelect(body)}
className={`w-full text-left p-2 rounded transition-all ${
selectedBody?.id === body.id
? 'bg-blue-500 bg-opacity-30 border-2 border-blue-400'
: 'bg-gray-800 bg-opacity-50 border border-gray-600 hover:bg-gray-700'
}`}
>
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="text-white font-medium text-sm">{body.name}</div>
<div className="text-gray-400 text-xs mt-0.5">
{distance.toFixed(2)} AU
</div>
</div>
{selectedBody?.id === body.id && (
<div className="text-blue-400 text-xs"> </div>
)}
</div>
</button>
))}
</div>
)}
</div>
{/* Probes Section */}
<div>
<h3 className="text-white text-sm font-semibold mb-2 text-gray-300">
({probesWithDistance.length})
</h3>
{probesWithDistance.length === 0 ? (
<div className="text-gray-500 text-xs p-2">...</div>
) : (
<div className="space-y-2 max-h-64 overflow-y-auto">
{probesWithDistance.map(({ body, distance }) => (
<button
key={body.id}
onClick={() => onBodySelect(body)}
className={`w-full text-left p-2 rounded transition-all ${
selectedBody?.id === body.id
? 'bg-cyan-500 bg-opacity-30 border-2 border-cyan-400'
: 'bg-gray-800 bg-opacity-50 border border-gray-600 hover:bg-gray-700'
}`}
>
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="text-white font-medium text-sm">{body.name}</div>
<div className="text-gray-400 text-xs mt-0.5">
{distance.toFixed(2)} AU
{distance > 30 && ' (遥远)'}
</div>
</div>
{selectedBody?.id === body.id && (
<div className="text-cyan-400 text-xs"> </div>
)}
</div>
</button>
))}
</div>
)}
</div>
{/* Return button */}
<div className="mt-3 pt-3 border-t border-gray-600">
<button
onClick={() => onBodySelect(null)}
className="w-full text-center p-2 rounded bg-gray-700 hover:bg-gray-600 text-white text-sm"
>
</button>
</div>
</div>
);
}