Initial commit
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
@page "/maps"
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
@using MudBlazor
|
||||
@using RobotNet10.RobotApp.Client.Shared.SLAM
|
||||
@using RobotNet10.RobotApp.Client.Clients
|
||||
@using RobotNet10.RobotApp.Client.Components.SLAM
|
||||
@implements IAsyncDisposable
|
||||
@inject SLAMClient CartographerClient
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
|
||||
<PageTitle>Map Management</PageTitle>
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h5">Map Management</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Refresh"
|
||||
OnClick="RefreshMapsAsync"
|
||||
Disabled="@(!CartographerClient.IsConnected || IsLoading)">
|
||||
Refresh
|
||||
</MudButton>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
@if (IsLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
}
|
||||
else if (Maps.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.body1" Color="Color.Secondary">
|
||||
No maps found. Create a map by starting scan mapping.
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@Maps" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Created</MudTh>
|
||||
<MudTh>Resolution</MudTh>
|
||||
<MudTh>Size</MudTh>
|
||||
<MudTh>Trajectory Nodes</MudTh>
|
||||
<MudTh></MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">
|
||||
<MudText Typo="Typo.body1">@context.Name</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Created">
|
||||
<MudText Typo="Typo.body2">@context.CreatedDate.ToString("yyyy-MM-dd HH:mm")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Resolution">
|
||||
<MudText Typo="Typo.body2">@context.Resolution.ToString("F3") m/p</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Size">
|
||||
<MudText Typo="Typo.body2">@context.Width.ToString("F1") x @context.Height.ToString("F1") m</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Trajectory Nodes">
|
||||
<MudText Typo="Typo.body2">@context.TrajectoryNodeCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Actions">
|
||||
<MudTooltip Text="View map details">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.OpenInNew"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
Href="@($"/map/{context.Name}")" />
|
||||
</MudTooltip>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private List<MapInfoDto> Maps { get; set; } = new();
|
||||
private bool IsLoading { get; set; } = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await CartographerClient.StartAsync();
|
||||
await RefreshMapsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshMapsAsync()
|
||||
{
|
||||
if (!CartographerClient.IsConnected)
|
||||
{
|
||||
Snackbar.Add("Not connected to server", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
Maps = (await CartographerClient.ListMapsAsync()).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Failed to load maps: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadMapAsync(string mapName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await CartographerClient.StartLocalizationAsync(mapName);
|
||||
if (success)
|
||||
{
|
||||
Snackbar.Add($"Started localization with map: {mapName}", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add($"Failed to start localization with map: {mapName}", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Failed to load map: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteMapAsync(string mapName)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["MapName"] = mapName
|
||||
};
|
||||
|
||||
var options = new DialogOptions
|
||||
{
|
||||
CloseOnEscapeKey = true,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<DeleteMapDialog>("Delete Map", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result != null && !result.Canceled)
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await CartographerClient.DeleteMapAsync(mapName);
|
||||
if (success)
|
||||
{
|
||||
Snackbar.Add($"Map deleted: {mapName}", Severity.Success);
|
||||
await RefreshMapsAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add($"Failed to delete map: {mapName}", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Failed to delete map: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatFileSize(long bytes)
|
||||
{
|
||||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||||
double len = bytes;
|
||||
int order = 0;
|
||||
while (len >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
len = len / 1024;
|
||||
}
|
||||
return $"{len:0.##} {sizes[order]}";
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// CartographerClient is scoped, will be disposed by DI
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user