1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
4import AddIcon from '@mui/icons-material/Add';
5import AdjustIcon from '@mui/icons-material/Adjust';
6import ChevronRightIcon from '@mui/icons-material/ChevronRight';
7import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
8import FlightIcon from '@mui/icons-material/Flight';
9import PodcastsIcon from '@mui/icons-material/Podcasts';
10import PublicIcon from '@mui/icons-material/Public';
11import RemoveIcon from '@mui/icons-material/Remove';
12import RssFeedIcon from '@mui/icons-material/RssFeed';
13import SensorsIcon from '@mui/icons-material/Sensors';
14import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
15import TimerIcon from '@mui/icons-material/Timer';
16import WavesIcon from '@mui/icons-material/Waves';
17import { Box, Divider, IconButton, Tooltip, Typography } from '@mui/material';
18import { SimpleTreeView, TreeItem } from '@mui/x-tree-view';
19import React from 'react';
20import { useShallow } from 'zustand/react/shallow';
25} from '@/stores/scenarioStore';
26import ScenarioIO from './ScenarioIO';
28const SectionHeader = ({
38 justifyContent: 'space-between',
41 minWidth: 0, // Allow the component to shrink within its flex container
47 color: 'text.secondary',
50 textOverflow: 'ellipsis',
51 pr: 1, // Padding between text and add button
56 <Tooltip title={`Add ${title.slice(0, -1)}`}>
64 <AddIcon fontSize="inherit" />
70const getPlatformIcon = (platform: Platform) => {
71 const types = platform.components.map((c) => c.type);
73 if (types.includes('monostatic')) {
74 return <SensorsIcon sx={{ mr: 1 }} fontSize="small" />;
76 if (types.includes('transmitter')) {
77 return <PodcastsIcon sx={{ mr: 1 }} fontSize="small" />;
79 if (types.includes('receiver')) {
80 return <RssFeedIcon sx={{ mr: 1 }} fontSize="small" />;
82 if (types.includes('target')) {
83 return <AdjustIcon sx={{ mr: 1 }} fontSize="small" />;
85 return <FlightIcon sx={{ mr: 1 }} fontSize="small" />;
88const getComponentIcon = (component: PlatformComponent) => {
89 switch (component.type) {
91 return <SensorsIcon sx={{ mr: 1 }} fontSize="small" />;
93 return <PodcastsIcon sx={{ mr: 1 }} fontSize="small" />;
95 return <RssFeedIcon sx={{ mr: 1 }} fontSize="small" />;
97 return <AdjustIcon sx={{ mr: 1 }} fontSize="small" />;
99 return <FlightIcon sx={{ mr: 1 }} fontSize="small" />;
103export default function SceneTree() {
118 removePlatformComponent,
119 } = useScenarioStore(
120 useShallow((state) => ({
121 globalParameters: state.globalParameters,
122 waveforms: state.waveforms,
123 timings: state.timings,
124 antennas: state.antennas,
125 platforms: state.platforms,
126 selectedItemId: state.selectedItemId,
127 selectedComponentId: state.selectedComponentId,
128 selectItem: state.selectItem,
129 addWaveform: state.addWaveform,
130 addTiming: state.addTiming,
131 addAntenna: state.addAntenna,
132 addPlatform: state.addPlatform,
133 removeItem: state.removeItem,
134 removePlatformComponent: state.removePlatformComponent,
138 const handleSelect = (
139 _event: React.SyntheticEvent | null,
140 nodeId: string | null
148 if (nodeId && rootNodes.includes(nodeId)) {
159 flexDirection: 'column',
162 {/* 1. Fixed Header */}
166 justifyContent: 'space-between',
167 alignItems: 'center',
174 <Typography variant="overline" sx={{ color: 'text.secondary' }}>
181 <Divider sx={{ mx: 2, mb: 1 }} />
183 {/* 2. Scrollable Content Area */}
186 flexGrow: 1, // Takes up all remaining space
187 overflowY: 'auto', // Enables vertical scrolling
188 minHeight: 0, // Crucial for flexbox scrolling
189 px: 2, // Apply horizontal padding inside scroll area
193 selectedItems={selectedComponentId ?? selectedItemId}
194 onSelectedItemsChange={handleSelect}
196 collapseIcon: ExpandMoreIcon,
197 expandIcon: ChevronRightIcon,
200 // Show remove button on hover
201 '& .MuiTreeItem-content .remove-button': {
202 visibility: 'hidden',
204 '& .MuiTreeItem-content:hover .remove-button': {
205 visibility: 'visible',
207 '& .MuiTreeItem-content': { py: 0.5 },
211 itemId={globalParameters.id}
213 <Box sx={{ display: 'flex', alignItems: 'center' }}>
214 <PublicIcon sx={{ mr: 1 }} fontSize="small" />
215 <Typography variant="body2">
222 itemId="waveforms-root"
227 alignItems: 'center',
231 <WavesIcon sx={{ mr: 1 }} fontSize="small" />
239 {waveforms.map((waveform) => (
247 alignItems: 'center',
259 whiteSpace: 'nowrap',
261 textOverflow: 'ellipsis',
270 className="remove-button"
273 removeItem(waveform.id);
276 <RemoveIcon fontSize="inherit" />
284 itemId="timings-root"
289 alignItems: 'center',
293 <TimerIcon sx={{ mr: 1 }} fontSize="small" />
301 {timings.map((timing) => (
309 alignItems: 'center',
321 whiteSpace: 'nowrap',
323 textOverflow: 'ellipsis',
332 className="remove-button"
335 removeItem(timing.id);
338 <RemoveIcon fontSize="inherit" />
346 itemId="antennas-root"
351 alignItems: 'center',
355 <SettingsInputAntennaIcon
366 {antennas.map((antenna) => (
374 alignItems: 'center',
378 <SettingsInputAntennaIcon
386 whiteSpace: 'nowrap',
388 textOverflow: 'ellipsis',
397 className="remove-button"
400 removeItem(antenna.id);
403 <RemoveIcon fontSize="inherit" />
411 itemId="platforms-root"
416 alignItems: 'center',
420 <FlightIcon sx={{ mr: 1 }} fontSize="small" />
428 {platforms.map((platform) => {
437 alignItems: 'center',
441 {getPlatformIcon(platform)}
446 whiteSpace: 'nowrap',
448 textOverflow: 'ellipsis',
457 className="remove-button"
460 removeItem(platform.id);
463 <RemoveIcon fontSize="inherit" />
468 {platform.components.map((component) => (
471 itemId={component.id}
476 alignItems: 'center',
500 className="remove-button"
503 removePlatformComponent(
509 <RemoveIcon fontSize="inherit" />