205 lines
6.8 KiB
Plaintext
205 lines
6.8 KiB
Plaintext
@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
|
|
}
|
|
}
|
|
|