502 lines
21 KiB
Plaintext
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();
|
|
}
|
|
}
|