OrbitalSimulator/web/src/components/SimulationList.tsx
2025-06-21 23:29:14 -04:00

139 lines
5.0 KiB
TypeScript

import React from 'react';
import { SimulationInfo, SimulationUpdate } from '../App';
import { Play, Pause, Square, Trash2 } from 'lucide-react';
import TimelineSlider from './TimelineSlider';
interface Props {
simulations: SimulationInfo[];
selectedSimulation: string | null;
currentData: SimulationUpdate | null;
isAutoPlaying: boolean;
onSelectSimulation: (id: string) => void;
onDeleteSimulation: (id: string) => void;
onControlSimulation: (id: string, action: string) => void;
onSeek: (step: number) => void;
onToggleAutoPlay: () => void;
onRestart: () => void;
}
const SimulationList: React.FC<Props> = ({
simulations,
selectedSimulation,
currentData,
isAutoPlaying,
onSelectSimulation,
onDeleteSimulation,
onControlSimulation,
onSeek,
onToggleAutoPlay,
onRestart,
}) => {
const formatTime = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
return (
<div className="simulation-list-container">
<h3>Active Simulations</h3>
{simulations.length === 0 ? (
<div className="loading">No simulations running</div>
) : (
<div className="simulations-scroll">
{simulations.map((sim) => (
<div key={sim.id} className="simulation-item">
<div
className={`simulation-card ${selectedSimulation === sim.id ? 'selected' : ''}`}
onClick={() => onSelectSimulation(sim.id)}
style={{
cursor: 'pointer',
border: selectedSimulation === sim.id ? '2px solid #00aaff' : '1px solid #444',
}}
>
<div className="simulation-status">
<div className={`status-indicator ${sim.is_running ? 'running' : 'paused'}`} />
<span>{sim.is_running ? 'Running' : 'Paused'}</span>
</div>
<div className="stats-grid">
<div className="stat-item">
<div className="stat-label">Bodies</div>
<div className="stat-value">{sim.bodies_count}</div>
</div>
<div className="stat-item">
<div className="stat-label">Step</div>
<div className="stat-value">{sim.current_step.toLocaleString()}</div>
</div>
<div className="stat-item">
<div className="stat-label">Runtime</div>
<div className="stat-value">{formatTime(sim.elapsed_time)}</div>
</div>
<div className="stat-item">
<div className="stat-label">ID</div>
<div className="stat-value" style={{ fontSize: '0.7rem' }}>
{sim.id.substring(0, 8)}...
</div>
</div>
</div>
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.5rem' }}>
<button
className="button"
onClick={(e) => {
e.stopPropagation();
onControlSimulation(sim.id, sim.is_running ? 'pause' : 'start');
}}
style={{ flex: 1 }}
>
{sim.is_running ? <Pause size={14} /> : <Play size={14} />}
</button>
<button
className="button secondary"
onClick={(e) => {
e.stopPropagation();
onControlSimulation(sim.id, 'stop');
}}
>
<Square size={14} />
</button>
<button
className="button danger"
onClick={(e) => {
e.stopPropagation();
if (confirm('Delete this simulation?')) {
onDeleteSimulation(sim.id);
}
}}
>
<Trash2 size={14} />
</button>
</div>
</div>
{/* Timeline slider slides out from selected simulation */}
{selectedSimulation === sim.id && currentData && (
<div className="timeline-slideout">
<TimelineSlider
simulation={sim}
isAutoPlaying={isAutoPlaying}
onSeek={onSeek}
onToggleAutoPlay={onToggleAutoPlay}
onRestart={onRestart}
/>
</div>
)}
</div>
))}
</div>
)}
</div>
);
};
export default SimulationList;