237 lines
9.0 KiB
Plaintext
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);
|
|
}
|
|
}
|