cosmo/frontend/src/components/FocusInfo.tsx

170 lines
6.8 KiB
TypeScript

import { X, Ruler, Activity, Radar, Eye } from 'lucide-react';
import { useState } from 'react';
import { request } from '../utils/request';
import type { CelestialBody } from '../types';
import { TerminalModal } from './TerminalModal';
import type { ToastContextValue } from '../contexts/ToastContext'; // Import ToastContextValue type
interface FocusInfoProps {
body: CelestialBody | null;
onClose: () => void;
toast: ToastContextValue; // Add toast prop
onViewDetails?: (body: CelestialBody) => void; // Add onViewDetails prop
}
export function FocusInfo({ body, onClose, toast, onViewDetails }: FocusInfoProps) {
const [showTerminal, setShowTerminal] = useState(false);
const [terminalData, setTerminalData] = useState('');
const [loading, setLoading] = useState(false);
if (!body) return null;
// Calculate distance if position is available
const pos = body.positions[0];
const distance = pos ? Math.sqrt(pos.x ** 2 + pos.y ** 2 + pos.z ** 2).toFixed(2) : '---';
const isProbe = body.type === 'probe';
const isActive = body.is_active !== false;
const fetchNasaData = async () => {
setShowTerminal(true);
setLoading(true);
try {
const { data } = await request.get(`/celestial/${body.id}/nasa-data`);
setTerminalData(data.raw_data);
} catch (err) {
console.error(err);
toast.error('连接 NASA Horizons 失败');
// If failed, maybe show error in terminal
setTerminalData("CONNECTION FAILED.\n\nError establishing link with JPL Horizons System.\nCheck connection frequencies.");
} finally {
setLoading(false);
}
};
const styles = `
@keyframes spin-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin-slow {
animation: spin-slow 3s linear infinite;
}
`;
return (
// Remove fixed positioning, now handled by parent container (Html component in 3D)
<div className="flex flex-col items-center -translate-y-24 pointer-events-none">
<style>{styles}</style>
{/* Main Info Card */}
<div className="bg-black/80 backdrop-blur-xl border border-[#238636] rounded-2xl p-5 min-w-[340px] max-w-md shadow-2xl shadow-[#238636]/20 pointer-events-auto relative group mb-2">
{/* Close Button */}
<button
onClick={(e) => {
e.stopPropagation();
onClose();
}}
className="absolute top-3 right-3 text-gray-400 hover:text-white transition-colors p-1 rounded-full hover:bg-white/10"
>
<X size={16} />
</button>
{/* Header */}
<div className="flex items-start justify-between mb-3 pr-6">
<div>
<div className="flex items-center gap-2 mb-1">
<h2 className="text-xl font-bold text-white tracking-tight">
{body.name_zh || body.name}
</h2>
{onViewDetails && (
<button
onClick={(e) => {
e.stopPropagation();
onViewDetails(body);
}}
className="text-gray-400 hover:text-white transition-colors p-1 rounded-full hover:bg-white/10"
title="查看详细信息"
>
<Eye size={16} />
</button>
)}
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider border ${
isProbe
? 'bg-purple-500/20 border-purple-500/40 text-purple-300'
: 'bg-[#238636]/20 border-[#238636]/40 text-[#4ade80]'
}`}>
{isProbe ? '探测器' : '天体'}
</span>
</div>
<p className="text-xs text-gray-400 line-clamp-2 leading-relaxed">
{body.description || '暂无描述'}
</p>
</div>
</div>
{/* Stats and Actions Grid */}
<div className="grid grid-cols-2 gap-2 mb-2">
{/* Column 1: Heliocentric Distance Card */}
<div className="bg-white/5 rounded-lg p-2.5 flex items-center gap-2.5 border border-white/5 h-[52px]">
<div className="p-1.5 rounded-full bg-[#238636]/20 text-[#4ade80]">
<Ruler size={14} />
</div>
<div>
<div className="text-[9px] text-gray-500 uppercase"></div>
<div className="text-xs font-mono text-gray-200">{distance} AU</div>
</div>
</div>
{/* Column 2: JPL Horizons Button */}
<button
onClick={fetchNasaData}
className="px-3 py-2.5 rounded-lg bg-cyan-950/30 text-cyan-400 border border-cyan-500/20 hover:bg-cyan-500/10 hover:border-cyan-500/50 transition-all flex items-center justify-center gap-2 text-[10px] font-mono uppercase tracking-widest group/btn h-[52px]"
title="连接 JPL Horizons System"
>
<Radar size={12} className="group-hover/btn:animate-spin-slow" />
<span>JPL Horizons</span>
</button>
</div>
{/* Conditional Probe Status Card (if isProbe is true, this goes in a new row) */}
{isProbe && (
<div className="grid grid-cols-1 mb-2">
<div className="bg-white/5 rounded-lg p-2 flex items-center gap-2.5 border border-white/5">
<div className={`p-1.5 rounded-full ${isActive ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}`}>
<Activity size={14} />
</div>
<div>
<div className="text-[9px] text-gray-500 uppercase"></div>
<div className={`text-xs font-medium ${isActive ? 'text-green-300' : 'text-red-300'}`}>
{isActive ? '运行中' : '已失效'}
</div>
</div>
</div>
</div>
)}
</div>
{/* Connecting Line/Triangle pointing down to the body */}
<div className="w-0 h-0 border-l-[8px] border-l-transparent border-r-[8px] border-r-transparent border-t-[8px] border-t-[#238636] backdrop-blur-xl mt-[-1px]"></div>
<TerminalModal
open={showTerminal}
onClose={() => setShowTerminal(false)}
title={
<div className="flex items-center gap-2 text-[#2ea043] font-mono tracking-wider text-xs">
<div className="w-2 h-2 rounded-full bg-[#2ea043] animate-pulse"></div>
JPL/HORIZONS SYSTEM INTERFACE // {body.name.toUpperCase()}
</div>
}
loading={loading}
loadingText="ESTABLISHING SECURE UPLINK..."
>
<div className="whitespace-pre-wrap">
{terminalData}
</div>
</TerminalModal>
</div>
);
}