FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
ScenarioIO.tsx
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2-0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
3
4import FileDownloadIcon from '@mui/icons-material/FileDownload';
5import FileUploadIcon from '@mui/icons-material/FileUpload';
6import { IconButton, Tooltip } from '@mui/material';
7import { invoke } from '@tauri-apps/api/core';
8import { dirname, join } from '@tauri-apps/api/path';
9import { open, save } from '@tauri-apps/plugin-dialog';
10import { writeTextFile } from '@tauri-apps/plugin-fs';
11import { useState } from 'react';
12import { useScenarioStore } from '@/stores/scenarioStore';
13import { getBlockingFmcwValidationMessage } from '@/stores/scenarioStore/fmcwValidation';
14import ConfirmDialog from './ConfirmDialog';
15
16export default function ScenarioIO() {
17 const loadScenario = useScenarioStore((state) => state.loadScenario);
18 const isDirty = useScenarioStore((state) => state.isDirty);
19 const resetScenario = useScenarioStore((state) => state.resetScenario);
20 const showError = useScenarioStore((state) => state.showError);
21 const showWarning = useScenarioStore((state) => state.showWarning);
22 const setScenarioFilePath = useScenarioStore(
23 (state) => state.setScenarioFilePath
24 );
25 const scenarioFilePath = useScenarioStore(
26 (state) => state.scenarioFilePath
27 );
28
29 const [isConfirmOpen, setConfirmOpen] = useState(false);
30
31 const handleExport = async () => {
32 try {
33 const state = useScenarioStore.getState();
34 const validationMessage = getBlockingFmcwValidationMessage(state);
35 if (validationMessage) {
36 showError(`FMCW validation failed: ${validationMessage}`);
37 return;
38 }
39
40 await state.syncBackend();
41
42 const xmlContent = await invoke<string>('get_scenario_as_xml');
43
44 // 1. Determine default directory
45 let defaultDir = '.';
46 if (scenarioFilePath) {
47 defaultDir = await dirname(scenarioFilePath);
48 }
49
50 // 2. Determine suggested filename
51 const simName =
52 state.globalParameters.simulation_name || 'scenario';
53 const suggestedFileName = `${simName.replace(/[^a-z0-9]/gi, '_')}.fersxml`;
54
55 // 3. Combine for dialog
56 const defaultPath = await join(defaultDir, suggestedFileName);
57
58 const filePath = await save({
59 title: 'Export Scenario',
60 defaultPath: defaultPath,
61 filters: [
62 {
63 name: 'FERS XML Scenario',
64 extensions: ['fersxml', 'xml'],
65 },
66 ],
67 });
68
69 if (filePath) {
70 await writeTextFile(filePath, xmlContent);
71 setScenarioFilePath(filePath);
72 console.log('Scenario exported successfully to:', filePath);
73 }
74 } catch (error) {
75 const errorMessage =
76 error instanceof Error ? error.message : String(error);
77 console.error('Failed to export scenario:', errorMessage);
78 showError(`Export failed: ${errorMessage}`);
79 }
80 };
81
82 const performImport = async () => {
83 try {
84 const selectedPath = await open({
85 title: 'Import Scenario',
86 multiple: false,
87 filters: [
88 {
89 name: 'FERS XML Scenario',
90 extensions: ['xml', 'fersxml'],
91 },
92 ],
93 });
94
95 if (typeof selectedPath === 'string') {
96 // Load the XML file into the C++ core
97 const warnings = await invoke<string[]>(
98 'load_scenario_from_xml_file',
99 {
100 filepath: selectedPath,
101 }
102 );
103
104 // Fetch the new state as JSON from the C++ core
105 const jsonState = await invoke<string>('get_scenario_as_json');
106 const scenarioData = JSON.parse(jsonState);
107
108 // Update the UI's Zustand store with the new state after resetting the current state
109 resetScenario();
110 loadScenario(scenarioData);
111 setScenarioFilePath(selectedPath);
112 warnings.forEach((warning) => showWarning(warning));
113
114 console.log(
115 'Scenario imported and synchronized successfully from:',
116 selectedPath
117 );
118 }
119 } catch (error) {
120 const errorMessage =
121 error instanceof Error ? error.message : String(error);
122 console.error('Failed to import scenario:', errorMessage);
123 showError(`Import failed: ${errorMessage}`);
124 }
125 };
126
127 const handleImport = () => {
128 if (isDirty) {
129 setConfirmOpen(true);
130 } else {
131 void performImport();
132 }
133 };
134
135 const handleConfirmImport = () => {
136 setConfirmOpen(false);
137 void performImport();
138 };
139
140 const handleCancelImport = () => {
141 setConfirmOpen(false);
142 };
143
144 return (
145 <>
146 <Tooltip title="Import Scenario (XML)">
147 <IconButton size="small" onClick={handleImport}>
148 <FileUploadIcon fontSize="inherit" />
149 </IconButton>
150 </Tooltip>
151 <Tooltip title="Export Scenario (XML)">
152 <IconButton size="small" onClick={handleExport}>
153 <FileDownloadIcon fontSize="inherit" />
154 </IconButton>
155 </Tooltip>
156 <ConfirmDialog
157 open={isConfirmOpen}
158 onConfirm={handleConfirmImport}
159 onCancel={handleCancelImport}
160 title="Overwrite Current Scenario?"
161 message="Importing a new scenario will discard all unsaved changes. Are you sure you want to proceed?"
162 />
163 </>
164 );
165}