1// SPDX-License-Identifier: GPL-2-0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
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';
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
25 const scenarioFilePath = useScenarioStore(
26 (state) => state.scenarioFilePath
29 const [isConfirmOpen, setConfirmOpen] = useState(false);
31 const handleExport = async () => {
33 const state = useScenarioStore.getState();
34 const validationMessage = getBlockingFmcwValidationMessage(state);
35 if (validationMessage) {
36 showError(`FMCW validation failed: ${validationMessage}`);
40 await state.syncBackend();
42 const xmlContent = await invoke<string>('get_scenario_as_xml');
44 // 1. Determine default directory
46 if (scenarioFilePath) {
47 defaultDir = await dirname(scenarioFilePath);
50 // 2. Determine suggested filename
52 state.globalParameters.simulation_name || 'scenario';
53 const suggestedFileName = `${simName.replace(/[^a-z0-9]/gi, '_')}.fersxml`;
55 // 3. Combine for dialog
56 const defaultPath = await join(defaultDir, suggestedFileName);
58 const filePath = await save({
59 title: 'Export Scenario',
60 defaultPath: defaultPath,
63 name: 'FERS XML Scenario',
64 extensions: ['fersxml', 'xml'],
70 await writeTextFile(filePath, xmlContent);
71 setScenarioFilePath(filePath);
72 console.log('Scenario exported successfully to:', filePath);
76 error instanceof Error ? error.message : String(error);
77 console.error('Failed to export scenario:', errorMessage);
78 showError(`Export failed: ${errorMessage}`);
82 const performImport = async () => {
84 const selectedPath = await open({
85 title: 'Import Scenario',
89 name: 'FERS XML Scenario',
90 extensions: ['xml', 'fersxml'],
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',
100 filepath: selectedPath,
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);
108 // Update the UI's Zustand store with the new state after resetting the current state
110 loadScenario(scenarioData);
111 setScenarioFilePath(selectedPath);
112 warnings.forEach((warning) => showWarning(warning));
115 'Scenario imported and synchronized successfully from:',
121 error instanceof Error ? error.message : String(error);
122 console.error('Failed to import scenario:', errorMessage);
123 showError(`Import failed: ${errorMessage}`);
127 const handleImport = () => {
129 setConfirmOpen(true);
131 void performImport();
135 const handleConfirmImport = () => {
136 setConfirmOpen(false);
137 void performImport();
140 const handleCancelImport = () => {
141 setConfirmOpen(false);
146 <Tooltip title="Import Scenario (XML)">
147 <IconButton size="small" onClick={handleImport}>
148 <FileUploadIcon fontSize="inherit" />
151 <Tooltip title="Export Scenario (XML)">
152 <IconButton size="small" onClick={handleExport}>
153 <FileDownloadIcon fontSize="inherit" />
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?"