Files
BQP/srcs/RobotNet10/Components/RobotNet10.NavigationTuneUI/Components/ScenarioEditor.razor
2026-07-13 09:25:40 +07:00

237 lines
9.0 KiB
Plaintext

@using RobotNet10.NavigationTune.Shared.Models
@using RobotNet10.NavigationTuneUI.Services
@using RobotNet10.NavigationTuneUI.Helpers
@using RobotNet10.NavigationTuneUI.Components
@using MudBlazor
@inject TuningApiService ApiService
@inject ISnackbar Snackbar
<MudCard Style="width: 100%; height: 600px; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box;">
<MudCardHeader Style="flex-shrink: 0; box-sizing: border-box;">
<CardHeaderContent>
<MudText Typo="Typo.h6">Scenario Configuration</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">Custom path editor</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudButton Variant="Variant.Outlined" Size="Size.Small" Color="Color.Success" OnClick="CreateCustomPath">
<MudIcon Icon="@Icons.Material.Filled.Add" Class="mr-1" />
New Custom Path
</MudButton>
<MudButton Variant="Variant.Outlined" Size="Size.Small" OnClick="SaveScenario" Disabled="@(SelectedScenario == null)">
Save
</MudButton>
<MudButton Variant="Variant.Text" Size="Size.Small" OnClick="ResetScenario" Disabled="@(SelectedScenario == null)">
Reset
</MudButton>
@if (SelectedScenario != null && !SelectedScenario.IsDefault)
{
<MudButton Variant="Variant.Text" Size="Size.Small" Color="Color.Error" OnClick="OpenDeleteDialog">
<MudIcon Icon="@Icons.Material.Filled.Delete" Class="mr-1" />
Delete
</MudButton>
}
</CardHeaderActions>
</MudCardHeader>
<MudCardContent Style="flex: 1; overflow-x: auto; overflow-y: auto; box-sizing: border-box;">
@if (SelectedScenario == null)
{
<MudAlert Severity="Severity.Info" Class="mt-2">
No scenario selected. Click "New Custom Path" to create one.
</MudAlert>
}
else
{
<MudGrid Spacing="2" Class="mb-3">
<MudItem xs="12" sm="6">
<MudTextField T="string" Label="Scenario name" Variant="Variant.Outlined"
Value="@SelectedScenario.Name"
ValueChanged="@(v => UpdateScenarioName(v))"
Immediate="true" />
</MudItem>
<MudItem xs="12" sm="6">
<MudTextField T="string" Label="Description" Variant="Variant.Outlined"
Value="@SelectedScenario.Description"
ValueChanged="@(v => UpdateScenarioDescription(v))"
Immediate="true" />
</MudItem>
</MudGrid>
<PathEditor Scenario="@GetCustomPathScenario()" OnScenarioChanged="HandleCustomPathChanged" />
}
</MudCardContent>
</MudCard>
<!-- Delete scenario confirmation -->
<MudDialog @bind-Visible="_isDeleteDialogVisible">
<TitleContent>
<MudText Typo="Typo.h6">Delete scenario</MudText>
</TitleContent>
<DialogContent>
<MudText>Are you sure you want to delete "@SelectedScenario?.Name"? This action cannot be undone.</MudText>
</DialogContent>
<DialogActions>
<MudButton OnClick="@(() => _isDeleteDialogVisible = false)">Cancel</MudButton>
<MudButton Color="Color.Error" Variant="Variant.Filled" OnClick="ConfirmDeleteScenario">Delete</MudButton>
</DialogActions>
</MudDialog>
@code {
[Parameter] public TestScenario? SelectedScenario { get; set; }
[Parameter] public EventCallback<TestScenario> OnScenarioChanged { get; set; }
[Parameter] public EventCallback<Guid> OnScenarioDeleted { get; set; }
[Parameter] public EventCallback<TestScenario> OnScenarioCreated { get; set; }
private CustomPathScenario? _customPathScenario;
private bool _isDeleteDialogVisible;
private CustomPathScenario? GetCustomPathScenario()
{
if (SelectedScenario is CustomPathScenario custom)
return custom;
if (_customPathScenario == null)
{
var id = (SelectedScenario != null && SelectedScenario.Id != Guid.Empty) ? SelectedScenario.Id : Guid.Empty;
_customPathScenario = new CustomPathScenario
{
Id = id,
Name = SelectedScenario?.Name ?? "Custom Path",
Description = SelectedScenario?.Description ?? "User-defined path",
Type = TrajectoryType.Custom,
Edges = new List<PathEdge>(),
Resolution = 0.05
};
}
return _customPathScenario;
}
private void UpdateScenarioName(string? value)
{
if (SelectedScenario != null)
{
SelectedScenario.Name = value ?? string.Empty;
OnScenarioChanged.InvokeAsync(SelectedScenario);
}
}
private void UpdateScenarioDescription(string? value)
{
if (SelectedScenario != null)
{
SelectedScenario.Description = value ?? string.Empty;
OnScenarioChanged.InvokeAsync(SelectedScenario);
}
}
private async Task HandleCustomPathChanged(CustomPathScenario scenario)
{
SelectedScenario = scenario;
await OnScenarioChanged.InvokeAsync(scenario);
}
private async Task SaveScenario()
{
if (SelectedScenario == null) return;
var scenarioToSave = GetCustomPathScenario();
if (scenarioToSave == null) return;
// Sync base metadata
scenarioToSave.Name = SelectedScenario.Name;
scenarioToSave.Description = SelectedScenario.Description;
scenarioToSave.Id = SelectedScenario.Id;
scenarioToSave.IsDefault = SelectedScenario.IsDefault;
try
{
if (scenarioToSave.Id == Guid.Empty)
{
var created = await ApiService.CreateScenarioAsync(scenarioToSave);
SelectedScenario = created;
_customPathScenario = created as CustomPathScenario;
await OnScenarioChanged.InvokeAsync(created);
await OnScenarioCreated.InvokeAsync(created);
Snackbar.Add($"Created scenario: {created.Name}", Severity.Success);
}
else
{
await ApiService.UpdateScenarioAsync(scenarioToSave.Id, scenarioToSave);
SelectedScenario = scenarioToSave;
_customPathScenario = scenarioToSave;
Snackbar.Add($"Updated scenario: {scenarioToSave.Name}", Severity.Success);
}
}
catch (ApiException ex)
{
Snackbar.Add($"Cannot save scenario: {ex.Message}", Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add($"Error saving scenario: {ErrorHelper.GetErrorMessage(ex)}", Severity.Error);
}
}
private void ResetScenario()
{
InvokeAsync(StateHasChanged);
Snackbar.Add("Scenario reset to original values", Severity.Info);
}
private void OpenDeleteDialog()
{
if (SelectedScenario != null && !SelectedScenario.IsDefault)
_isDeleteDialogVisible = true;
}
private async Task ConfirmDeleteScenario()
{
if (SelectedScenario == null || SelectedScenario.IsDefault) return;
var idToDelete = SelectedScenario.Id;
try
{
await ApiService.DeleteScenarioAsync(idToDelete);
_isDeleteDialogVisible = false;
await OnScenarioDeleted.InvokeAsync(idToDelete);
Snackbar.Add("Scenario deleted", Severity.Success);
}
catch (ApiException ex)
{
Snackbar.Add($"Cannot delete scenario: {ex.Message}", Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add($"Error deleting scenario: {ErrorHelper.GetErrorMessage(ex)}", Severity.Error);
}
}
private async Task CreateCustomPath()
{
var customPath = new CustomPathScenario
{
Id = Guid.Empty,
Name = $"Custom Path {DateTime.Now:HHmmss}",
Description = "User-defined custom path",
Type = TrajectoryType.Custom,
Edges = new List<PathEdge>
{
new PathEdge
{
StartX = 0,
StartY = 0,
EndX = 1,
EndY = 0,
Degree = 1
}
},
Resolution = 0.05
};
SelectedScenario = customPath;
_customPathScenario = customPath;
await OnScenarioChanged.InvokeAsync(customPath);
await OnScenarioCreated.InvokeAsync(customPath);
Snackbar.Add("New Custom Path created. Add edges to build your path.", Severity.Success);
}
}