156 lines
5.7 KiB
TypeScript
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>
|
|
);
|
|
}
|