139 lines
5.0 KiB
TypeScript
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;
|