RobotNet/RobotNet.WebApp/Robots/Components/Monitoring/MonitorMap.razor
2025-10-15 15:15:53 +07:00

502 lines
21 KiB
Plaintext

@using RobotNet.WebApp.Robots.Components.Monitoring.Element
@using System.Text.Json
@inject IJSRuntime JSRuntime
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@inject IHttpClientFactory HttpFactory
<div class="monitor-map" @ref="ViewContainerRef">
<div @ref="ViewMovementRef">
<svg @ref="MapContainerRef" class="monitor-map-view" letter-spacing="normal">
<MapSvgDefs />
<image @ref="MapImageRef" class="monitor-map-image" />
<OriginVector />
<MapGrid @ref="@MapGridRef" Show="@ShowGrid" />
@foreach (var zone in zones)
{
<Zone Model="zone" IsShow="true" />
}
@foreach (var edge in edges)
{
<Edge Model="edge" />
}
@foreach (var edge in edges)
{
<EdgeDirection Model="edge" />
}
@foreach (var node in nodes)
{
<Node Model="node" ShowName="@ShowName" />
}
@foreach (var element in elements)
{
<Element Model="element" ShowName="@ShowName" Show="@ShowElement" DoubleClick="ElementDoubleClick" />
}
<RobotCurrentPath @ref="@RobotCurrentPathRef" Show="@ShowPath" />
<RobotPath @ref="@RobotPathRef" Show="@ShowPath" />
<MapRobot Models="robots" ShowName="@ShowName" />
<RobotLaserScaner @ref="RobotLaserScanerRef" ShowLaser="ShowLaser" />
</svg>
</div>
<MapMousePosition @ref="MapInfoRef" />
</div>
<MudDialog @bind-Visible="updateElementVisble">
<TitleContent>
<MudText Typo="Typo.h6">
Update Element @ElementUpdateModel.Name
</MudText>
</TitleContent>
<DialogContent>
<div class="d-flex flex-row" style="max-height: 700px; overflow-y: auto; width: 600px">
<div class="d-flex flex-column h-100 me-2" style="width: 47%">
<MudTextField T="string" Label="Name" @bind-value="ElementUpdateModel.Name" Variant="Variant.Outlined"
Required="true" RequiredError="Name is required!" ReadOnly
Validation="@(new Func<string, IEnumerable<string>>(MapEditorHelper.NameValidation))" />
<MudNumericField T="double" Label="Offset X" @bind-value="ElementUpdateModel.OffsetX" Variant="Variant.Outlined" Step="0.1" ReadOnly />
<MudNumericField T="double" Label="Offset Y" @bind-value="ElementUpdateModel.OffsetY" Variant="Variant.Outlined" Step="0.1" ReadOnly />
<MudCheckBox Size="Size.Small" T="bool" LabelPlacement="Placement.Start" Label="IsOpen" @bind-value="ElementUpdateModel.IsOpen" Color="Color.Success"
UncheckedColor="Color.Default" />
</div>
<div class="paper-property-container">
<div class="paper-property ms-2">
@foreach (var property in ElementProperties)
{
@if (property.Type == typeof(int).ToString())
{
if (int.TryParse(property.DefaultValue, out int value))
{
<MudNumericField T="int" Margin="Margin.Dense" Label="@property.Name" Value="value" Variant="Variant.Outlined" ReadOnly="@(property.ReadOnly)"
ValueChanged="((data) => DefaultIntValueChanged(property.Id, data))" />
}
}
else if (property.Type == typeof(double).ToString())
{
if (double.TryParse(property.DefaultValue, out double value))
{
<MudNumericField T="double" Margin="Margin.Dense" Label="@property.Name" Value="value" Variant="Variant.Outlined" Step="0.1" ReadOnly="@(property.ReadOnly)"
ValueChanged="((data) => DefaultDoubleValueChanged(property.Id, data))" />
}
}
else if (property.Type == typeof(bool).ToString())
{
if (bool.TryParse(property.DefaultValue, out bool value))
{
<MudCheckBox Class="m-0" Size="Size.Small" T="bool" LabelPlacement="Placement.Start" Label="@property.Name" Value="value" Color="Color.Success"
UncheckedColor="Color.Default" ReadOnly="@(property.ReadOnly)" ValueChanged="((data) => DefaultBooleanValueChanged(property.Id, data))" />
}
}
else if (property.Type == typeof(string).ToString())
{
<MudTextField T="string" Label="@property.Name" Value="@property.DefaultValue" Variant="Variant.Outlined" Margin="Margin.Dense" ReadOnly="@(property.ReadOnly)"
ValueChanged="((data) => property.DefaultValue = data)" />
}
}
</div>
<span class="paper-title">Properties</span>
</div>
</div>
</DialogContent>
<DialogActions>
<MudButton OnClick="(() => updateElementVisble = false)" Class="px-10" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="UpdateElement" Variant="Variant.Filled" Color="Color.Primary" Class="px-10 me-4">Update</MudButton>
</DialogActions>
</MudDialog>
@code {
[Parameter]
public bool ShowGrid { get; set; }
[Parameter]
public bool ShowName { get; set; }
[Parameter]
public bool ShowPath { get; set; }
[Parameter]
public bool ShowLaser { get; set; }
[Parameter]
public bool ShowElement { get; set; }
private MapMousePosition MapInfoRef = null!;
private RobotPath RobotPathRef = null!;
private RobotCurrentPath RobotCurrentPathRef = null!;
private MapGrid MapGridRef = null!;
private RobotLaserScaner RobotLaserScanerRef = null!;
private DotNetObjectReference<MonitorMap> DotNetObj = null!;
private ElementReference ViewContainerRef;
private ElementReference ViewMovementRef;
private ElementReference MapContainerRef;
private ElementReference MapImageRef;
private double ViewContainerRectX;
private double ViewContainerRectY;
private double ViewContainerRectWidth;
private double ViewContainerRectHeight;
private double ViewContainerRectTop;
private double ViewContainerRectRight;
private double ViewContainerRectBottom;
private double ViewContainerRectLeft;
private double CursorX;
private double CursorY;
private double ClientOriginX;
private double ClientOriginY;
private double Resolution = 1;
private double OriginX;
private double OriginY;
private double ImageOriginY;
private double ImageWidth;
private double ImageHeight;
private double Scale = 1;
private double Left;
private double Top;
private double FitScale = 1;
private double OldOriginY;
private List<EdgeModel> edges = new();
private List<NodeModel> nodes = new();
private List<ZoneModel> zones = new();
private List<ElementModel> elements = new();
private MapRobotModel robots = new();
private bool IsFocusRobot = false;
private string RobotSelectedId = "";
private bool updateElementVisble;
private ElementUpdateModel ElementUpdateModel = new();
private List<MapShares.Property.ElementProperty> ElementProperties = [];
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (!firstRender) return;
DotNetObj = DotNetObjectReference.Create(this);
await JSRuntime.InvokeVoidAsync("DOMCssLoaded");
await JSRuntime.InvokeVoidAsync("UpdateViewContainerRect", DotNetObj, ViewContainerRef, nameof(ViewContainerResize));
await JSRuntime.InvokeVoidAsync("ResizeObserverRegister", DotNetObj, ViewContainerRef, nameof(ViewContainerResize));
await JSRuntime.InvokeVoidAsync("AddEventListener", DotNetObj, ViewContainerRef, "click", nameof(ViewContainerClick));
await JSRuntime.InvokeVoidAsync("AddMouseMoveEventListener", DotNetObj, ViewContainerRef, nameof(MouseMoveOnMapContainer));
await JSRuntime.InvokeVoidAsync("AddMouseWheelEventListener", DotNetObj, MapContainerRef, nameof(MouseWheelOnMapContainer));
await JSRuntime.InvokeVoidAsync("AddTouchMoveEventListener", DotNetObj, ViewContainerRef, nameof(TouchMoveOnMapContainer));
}
public async Task LoadMap(MapDataDto? mapData)
{
if (mapData == null)
{
nodes.Clear();
edges.Clear();
zones.Clear();
elements.Clear();
robots.ReplaceAll([]);
RobotCurrentPathRef.UpdatePath([]);
RobotPathRef.UpdatePath([]);
Resolution = 1.0;
OriginY = 0;
OriginX = 0;
OldOriginY = 0;
ImageOriginY = 0;
ImageHeight = 0;
ImageWidth = 0;
MapGridRef.Resize(OriginX, OriginY, ImageHeight, ImageWidth);
await JSRuntime.InvokeVoidAsync("SetMapSvgConfig", MapContainerRef, ImageWidth, ImageHeight, OriginX, 1.0);
await JSRuntime.InvokeVoidAsync("SetImageAttribute", MapImageRef, ImageWidth, ImageHeight, OriginX, OriginY, "");
}
else
{
nodes = [.. mapData.Nodes.Select(node => new NodeModel(node))];
edges = [.. mapData.Edges.Select(edge => new EdgeModel(edge, nodes.First(n => n.Id == edge.StartNodeId), nodes.First(n => n.Id == edge.EndNodeId)))];
zones = [.. mapData.Zones.Select(zone => new ZoneModel(zone))];
elements = [.. mapData.Elements.Select(element => new ElementModel(element, nodes.First(n => n.Id == element.NodeId)))];
Resolution = mapData.Resolution;
OldOriginY = mapData.OriginY;
OriginY = -mapData.ImageHeight * Resolution - mapData.OriginY;
OriginX = mapData.OriginX;
ImageOriginY = mapData.OriginY;
ImageHeight = mapData.ImageHeight * Resolution;
ImageWidth = mapData.ImageWidth * Resolution;
MapGridRef.Resize(OriginX, OriginY, ImageHeight, ImageWidth);
using var Http = HttpFactory.CreateClient("MapManagerAPI");
await JSRuntime.InvokeVoidAsync("SetMapSvgConfig", MapContainerRef, ImageWidth, ImageHeight, OriginX, mapData.OriginY);
await JSRuntime.InvokeVoidAsync("SetImageAttribute", MapImageRef, ImageWidth, ImageHeight, OriginX, OriginY, $"{Http.BaseAddress}api/images/map/{mapData.Id}");
await JSRuntime.InvokeVoidAsync("UpdateViewContainerRect", DotNetObj, ViewContainerRef, nameof(ViewContainerResize));
await ScaleFitContentAsync();
}
StateHasChanged();
}
public async Task ScaleFitContentAsync()
{
Scale = FitScale;
await SetViewMovement((ViewContainerRectWidth - ImageWidth * Scale) / 2, (ViewContainerRectHeight - ImageHeight * Scale) / 2);
await JSRuntime.InvokeVoidAsync("SetMapSvgRect", MapContainerRef, ImageWidth * Scale, ImageHeight * Scale);
}
private async Task SetViewMovement(double left, double top)
{
Top = top;
Left = left;
ClientOriginX = ViewContainerRectLeft + Left - OriginX * Scale;
ClientOriginY = ViewContainerRectTop + Top - OriginY * Scale;
await JSRuntime.InvokeVoidAsync("SetMapMovement", ViewMovementRef, Top, Left);
}
public async Task ScaleZoom(double deltaY)
{
if (deltaY > 0)
{
if (Scale >= FitScale * 20) return;
}
else
{
if (Scale <= FitScale / 2) return;
}
double oldScale = Scale;
Scale += deltaY;
double centerXBefore = ((ViewContainerRectLeft + ViewContainerRectWidth / 2) - ClientOriginX) / oldScale - OriginX;
double centerYBefore = (ClientOriginY - (ViewContainerRectHeight / 2 + ViewContainerRectTop)) / oldScale - OldOriginY;
await SetViewMovement(Left - centerXBefore * deltaY, Top - (ImageHeight - centerYBefore) * deltaY);
await JSRuntime.InvokeVoidAsync("SetMapSvgRect", MapContainerRef, ImageWidth * Scale, ImageHeight * Scale);
}
[JSInvokable]
public void ViewContainerResize(double x, double y, double width, double height, double top, double right, double bottom, double left)
{
ViewContainerRectX = x;
ViewContainerRectY = y;
ViewContainerRectWidth = width;
ViewContainerRectHeight = height;
ViewContainerRectTop = top;
ViewContainerRectRight = right;
ViewContainerRectBottom = bottom;
ViewContainerRectLeft = left;
ClientOriginX = ViewContainerRectLeft + Left - OriginX * Scale;
ClientOriginY = ViewContainerRectTop + Top - OriginY * Scale;
FitScale = Math.Min(ViewContainerRectWidth / ImageWidth, ViewContainerRectHeight / ImageHeight);
}
[JSInvokable]
public async Task TouchMoveOnMapContainer(double clientX, double clientY, int touchCount, double movementX, double movementY)
{
CursorX = (clientX - ClientOriginX) / Scale;
CursorY = (ClientOriginY - clientY) / Scale;
MapInfoRef.Update(CursorX, CursorY);
if (touchCount == 1) await SetViewMovement(Left + movementX, Top + movementY);
}
[JSInvokable]
public async Task MouseWheelOnMapContainer(double deltaY, double offsetX, double offsetY)
{
double scaleChange;
if (deltaY > 0)
{
if (Scale <= FitScale / 2) return;
scaleChange = Scale > FitScale ? -(Scale / FitScale) : -0.1;
}
else
{
if (Scale >= FitScale * 100) return;
scaleChange = Scale < FitScale ? 0.5 : (Scale / FitScale);
}
double oldScale = Scale;
Scale += scaleChange;
await JSRuntime.InvokeVoidAsync("SetMapSvgRect", MapContainerRef, ImageWidth * Scale, ImageHeight * Scale);
double mouseX = CursorX - OriginX;
double mouseY = CursorY - OldOriginY;
await SetViewMovement(Left - mouseX * scaleChange, Top - (ImageHeight - mouseY) * scaleChange);
}
[JSInvokable]
public async Task ViewContainerClick() => await ViewContainerRef.FocusAsync();
[JSInvokable]
public async Task MouseMoveOnMapContainer(double clientX, double clientY, long buttons, bool ctrlKey, double movementX, double movementY)
{
CursorX = (clientX - ClientOriginX) / Scale;
CursorY = (ClientOriginY - clientY) / Scale;
MapInfoRef.Update(CursorX, CursorY);
if (IsFocusRobot) return;
switch (buttons)
{
case 4:
await SetViewMovement(Left + movementX, Top + movementY);
break;
}
}
public void LoadRobots(IEnumerable<RobotInfomationDto> robotDtos)
{
if (robotDtos.Any())
{
var robotModel = robotDtos.Select(robot => new RobotVisualizationModel()
{
RobotId = robot.RobotId,
RobotName = robot.Name,
X = robot.AgvPosition.X,
Y = robot.AgvPosition.Y,
Theta = robot.AgvPosition.Theta,
}).ToArray();
robots.ReplaceAll(robotModel);
}
else robots.ReplaceAll([]);
StateHasChanged();
}
public void RobotSelectedChange(string robotId)
{
if (robotId != RobotSelectedId) RobotSelectedId = robotId;
}
public void FocusRobot(bool value) => IsFocusRobot = value;
public async Task FocusRobotAsync()
{
var robot = robots.FirstOrDefault(r => r.RobotId == RobotSelectedId);
if (robot is null) return;
double mapX = (ViewContainerRectWidth - ImageWidth * Scale) / 2 + ((ImageWidth / 2 + OriginX) * Scale) - robot.X * Scale;
double mapY = (ViewContainerRectHeight - ImageHeight * Scale) / 2 + ((ImageHeight / 2 + OriginY) * Scale) + robot.Y * Scale;
await SetViewMovement(mapX, mapY);
}
public async Task SetRobotPosition(List<RobotInfomationDto> robotStates)
{
foreach (var robot in robotStates)
{
var robotModel = robots.FirstOrDefault(rm => rm.RobotId == robot.RobotId);
if (robotModel is not null)
{
robotModel.Update(robot.AgvPosition.X, robot.AgvPosition.Y, robot.AgvPosition.Theta, robot.Loads);
if (IsFocusRobot && robot.RobotId == RobotSelectedId) await FocusRobotAsync();
}
else
{
robots.Add(new()
{
RobotId = robot.RobotId,
RobotName = robot.Name,
X = robot.AgvPosition.X,
Y = robot.AgvPosition.Y,
Theta = robot.AgvPosition.Theta,
Loads = robot.Loads
});
StateHasChanged();
}
if (robot.RobotId == RobotSelectedId)
{
RobotPathRef.UpdatePath([.. robot.Navigation.RobotPath]);
RobotCurrentPathRef.UpdatePath([.. robot.Navigation.RobotBasePath]);
RobotLaserScanerRef.SetData(robot.Navigation.LaserScaner);
}
}
}
public void ElementStateUpdated(IEnumerable<ElementDto> elementsState)
{
foreach (var elmenet in elementsState)
{
var elemetModel = elements.FirstOrDefault(e => e.Id == elmenet.Id);
if (elemetModel is not null)
{
elemetModel.Update(elmenet);
}
else
{
var node = nodes.FirstOrDefault(n => n.Id == elmenet.NodeId);
if (node is not null) elements.Add(new ElementModel(elmenet, node));
}
}
StateHasChanged();
}
private void DefaultIntValueChanged(Guid id, int value)
{
var property = ElementProperties.FirstOrDefault(p => p.Id == id);
if (property is null) return;
property.DefaultValue = value.ToString();
}
private void DefaultDoubleValueChanged(Guid id, double value)
{
var property = ElementProperties.FirstOrDefault(p => p.Id == id);
if (property is null) return;
property.DefaultValue = value.ToString();
}
private void DefaultBooleanValueChanged(Guid id, bool value)
{
var property = ElementProperties.FirstOrDefault(p => p.Id == id);
if (property is null) return;
property.DefaultValue = value.ToString();
}
private void ElementDoubleClick(ElementModel model)
{
ElementUpdateModel.Id = model.Id;
ElementUpdateModel.Name = model.Name;
ElementUpdateModel.IsOpen = model.IsOpen;
ElementUpdateModel.OffsetX = model.OffsetX;
ElementUpdateModel.OffsetY = model.OffsetY;
ElementUpdateModel.Content = model.Content;
if (model is not null && !string.IsNullOrEmpty(model.Content))
{
var properties = JsonSerializer.Deserialize<List<MapShares.Property.ElementProperty>>(model.Content, JsonOptionExtends.Read);
if (properties is not null) ElementProperties = [.. properties];
}
updateElementVisble = true;
StateHasChanged();
}
private async Task UpdateElement()
{
var selectedElement = elements.FirstOrDefault(e => e.Id == ElementUpdateModel.Id);
if (selectedElement is null) return;
using var Http = HttpFactory.CreateClient("MapManagerAPI");
var result = await (await Http.PutAsJsonAsync($"api/Elements", new ElementUpdateModel()
{
Id = ElementUpdateModel.Id,
Name = ElementUpdateModel.Name,
IsOpen = ElementUpdateModel.IsOpen,
OffsetX = ElementUpdateModel.OffsetX,
OffsetY = ElementUpdateModel.OffsetY,
Content = JsonSerializer.Serialize(ElementProperties, JsonOptionExtends.Write),
})).Content.ReadFromJsonAsync<MessageResult<ElementDto>>();
if (result == null) Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error);
else if (!result.IsSuccess) Snackbar.Add(result.Message, Severity.Error);
else if (result.Data is null) Snackbar.Add("Lỗi dữ liệu", Severity.Error);
else
{
selectedElement.Update(result.Data);
Snackbar.Add("Cập nhật thành công", Severity.Success);
}
updateElementVisble = false;
StateHasChanged();
}
}