1093 lines
45 KiB
C#
1093 lines
45 KiB
C#
using MudBlazor;
|
|
using RobotNet.MapShares;
|
|
using RobotNet.MapShares.Dtos;
|
|
using RobotNet.MapShares.Enums;
|
|
using RobotNet.MapShares.Models;
|
|
using RobotNet.Shares;
|
|
using RobotNet.WebApp.Components;
|
|
using RobotNet.WebApp.Maps.Models;
|
|
using System.Net.Http.Json;
|
|
|
|
namespace RobotNet.WebApp.Maps.Components.Editor;
|
|
|
|
public class MergeNodeBackup
|
|
{
|
|
public Guid NodeId { get; set; }
|
|
public List<(Guid edgeId, NodeModel node)> EdgesMerge { get; set; } = [];
|
|
}
|
|
|
|
public partial class MapContainer
|
|
{
|
|
public readonly List<MapEditorBackup> EditorBackup = [];
|
|
|
|
public async Task CreateEdge(double x1, double y1, double x2, double y2, TrajectoryDegree degree = TrajectoryDegree.One, double cpX1 = 0, double cpY1 = 0, double cpX2 = 0, double cpY2 = 0)
|
|
{
|
|
if (MapData is null) { Snackbar.Add("Dữ liệu bản đồ rỗng", Severity.Warning); return; }
|
|
if (MapData.Active) { Snackbar.Add("Không thể tạo edge do bản đồ đang được Active", Severity.Warning); return; }
|
|
|
|
var result = await (await Http.PostAsJsonAsync($"api/Edges", new EdgeCreateModel()
|
|
{
|
|
MapId = MapData.Id,
|
|
X1 = x1,
|
|
Y1 = y1,
|
|
X2 = x2,
|
|
Y2 = y2,
|
|
TrajectoryDegree = degree,
|
|
ControlPoint1X = cpX1,
|
|
ControlPoint1Y = cpY1,
|
|
ControlPoint2X = cpX2,
|
|
ControlPoint2Y = cpY2,
|
|
})).Content.ReadFromJsonAsync<MessageResult<EdgeCreateDto>>();
|
|
if (result is null) { Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error); return; }
|
|
else if (!result.IsSuccess) { Snackbar.Add(result.Message ?? "Tạo Edge không thành công", Severity.Error); return; }
|
|
else if (result.Data == null || !result.Data.EdgesDto.Any() || result.Data.EdgesDto.Any(e => e.StartNode is null || e.EndNode is null))
|
|
{
|
|
Snackbar.Add("Lỗi dữ liệu trả về", Severity.Error);
|
|
return;
|
|
}
|
|
|
|
foreach (var edge in result.Data.EdgesDto)
|
|
{
|
|
if (edge.StartNode is null || edge.EndNode is null) continue;
|
|
if (!Nodes.ContainsKey(edge.StartNodeId)) Nodes.Add(edge.StartNode);
|
|
if (!Nodes.ContainsKey(edge.EndNodeId)) Nodes.Add(edge.EndNode);
|
|
|
|
Edges.Add(edge, Nodes[edge.StartNodeId], Nodes[edge.EndNodeId]);
|
|
}
|
|
|
|
foreach (var removeEdgeId in result.Data.RemoveEdge)
|
|
{
|
|
var removeEdge = Edges.FirstOrDefault(e => e.Id == removeEdgeId);
|
|
if (removeEdge is not null)
|
|
{
|
|
Edges.Remove(removeEdge);
|
|
var edgeEditSteps = EditorBackup.Where(step => (step.Type == MapEditorBackupType.ControlPoint1Edge || step.Type == MapEditorBackupType.ControlPoint2Edge) && step.Id == removeEdge.Id);
|
|
}
|
|
}
|
|
Snackbar.Add("Tạo Edge mới thành công", Severity.Success);
|
|
}
|
|
|
|
public async Task DeleteEdge()
|
|
{
|
|
if (EditorBackup.Count > 0)
|
|
{
|
|
if (!await SaveChanged()) return;
|
|
}
|
|
|
|
if (Edges.ActivedEdges.Count == 0) return;
|
|
if (MapData is null) { Snackbar.Add("Dữ liệu bản đồ rỗng", Severity.Warning); return; }
|
|
if (MapData.Active) { Snackbar.Add("Không thể xóa edge do bản đồ đang được Active", Severity.Warning); return; }
|
|
|
|
var parameters = new DialogParameters<ConfirmDialog>
|
|
{
|
|
{ x => x.Content, "Bạn chắc chắn muốn xóa edge đi không?" },
|
|
{ x => x.ConfirmText, "Delete" },
|
|
{ x => x.Color, Color.Secondary }
|
|
};
|
|
var Confirm = await Dialog.ShowAsync<ConfirmDialog>("Xoá Edge", parameters);
|
|
var ConfirmResult = await Confirm.Result;
|
|
if (ConfirmResult is not null && ConfirmResult.Data is not null && bool.TryParse(ConfirmResult.Data.ToString(), out bool data) && data)
|
|
{
|
|
OverlayIsVisible = true;
|
|
StateHasChanged();
|
|
|
|
var deleteListEdgeId = Edges.ActivedEdges.Select(x => x.Id).ToList();
|
|
HttpRequestMessage request = new()
|
|
{
|
|
Content = JsonContent.Create(deleteListEdgeId),
|
|
Method = HttpMethod.Delete,
|
|
RequestUri = new Uri($"{Http.BaseAddress}api/Edges")
|
|
};
|
|
var response = await Http.SendAsync(request);
|
|
var result = await response.Content.ReadFromJsonAsync<MessageResult>();
|
|
if (result is null) { Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error); return; }
|
|
else if (!result.IsSuccess) { Snackbar.Add(result.Message ?? "Xóa Edge không thành công", Severity.Error); return; }
|
|
|
|
if (Edges.ActivedEdges.Count < 20)
|
|
{
|
|
List<EdgeModel> ActivedEdgeCopy = [.. Edges.ActivedEdges];
|
|
Nodes.UnActivedNode();
|
|
Edges.UnActivedEdge();
|
|
foreach (var edge in ActivedEdgeCopy)
|
|
{
|
|
var removeEdge = Edges.FirstOrDefault(e => e.Id == edge.Id);
|
|
if (removeEdge == null) continue;
|
|
Edges.Remove(removeEdge);
|
|
var edgeEditSteps = EditorBackup.Where(step => (step.Type == MapEditorBackupType.ControlPoint1Edge || step.Type == MapEditorBackupType.ControlPoint2Edge) && step.Id == removeEdge.Id);
|
|
for (int i = 0; i < edgeEditSteps.Count(); i++) EditorBackup.Remove(edgeEditSteps.ElementAt(i));
|
|
|
|
if (removeEdge.StartNode.NumberOfEdgeReference <= 1)
|
|
{
|
|
Nodes.Remove(removeEdge.StartNode);
|
|
var element = Elements.FirstOrDefault(el => el.NodeId == removeEdge.StartNode.Id);
|
|
if (element is not null) Elements.Remove(element);
|
|
var nodeEditSteps = EditorBackup.Where(step => step.Type == MapEditorBackupType.Node && step.Id == removeEdge.StartNode.Id);
|
|
for (int i = 0; i < nodeEditSteps.Count(); i++) EditorBackup.Remove(nodeEditSteps.ElementAt(i));
|
|
}
|
|
|
|
if (removeEdge.EndNode.NumberOfEdgeReference <= 1)
|
|
{
|
|
Nodes.Remove(removeEdge.EndNode);
|
|
var element = Elements.FirstOrDefault(el => el.NodeId == removeEdge.EndNode.Id);
|
|
if (element is not null) Elements.Remove(element);
|
|
var nodeEditSteps = EditorBackup.Where(step => step.Type == MapEditorBackupType.Node && step.Id == removeEdge.EndNode.Id);
|
|
for (int i = 0; i < nodeEditSteps.Count(); i++) EditorBackup.Remove(nodeEditSteps.ElementAt(i));
|
|
}
|
|
}
|
|
|
|
await OnSelectedEdgeChanged(null);
|
|
await MultiselectedEdgeChanged.InvokeAsync(false);
|
|
await MultiselectedNodeChanged.InvokeAsync(false);
|
|
|
|
Snackbar.Add("Xóa Edge thành công", Severity.Success);
|
|
OverlayIsVisible = false;
|
|
}
|
|
else await LoadMap();
|
|
StateHasChanged();
|
|
}
|
|
|
|
}
|
|
|
|
public async Task CreateZone(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, ZoneType type)
|
|
{
|
|
if (MapData is null) { Snackbar.Add("Dữ liệu bản đồ rỗng", Severity.Warning); return; }
|
|
if (MapData.Active) { Snackbar.Add("Không thể tạo edge do bản đồ đang được Active", Severity.Warning); return; }
|
|
|
|
var result = await (await Http.PostAsJsonAsync($"api/Zones", new ZoneCreateModel()
|
|
{
|
|
MapId = MapData.Id,
|
|
X1 = x1,
|
|
Y1 = y1,
|
|
X2 = x2,
|
|
Y2 = y2,
|
|
X3 = x3,
|
|
Y3 = y3,
|
|
X4 = x4,
|
|
Y4 = y4,
|
|
Type = type
|
|
})).Content.ReadFromJsonAsync<MessageResult<ZoneDto>>();
|
|
|
|
if (result is null) { Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error); return; }
|
|
else if (!result.IsSuccess) { Snackbar.Add(result.Message ?? "Tạo Zone không thành công", Severity.Error); return; }
|
|
else if (result.Data is null) { Snackbar.Add("Lỗi dữ liệu trả về", Severity.Error); return; }
|
|
|
|
Zones.Add(result.Data);
|
|
Snackbar.Add("Tạo Zone mới thành công", Severity.Success);
|
|
}
|
|
|
|
public async Task DeleteZone()
|
|
{
|
|
if (EditorBackup.Count > 0)
|
|
{
|
|
if (!await SaveChanged()) return;
|
|
}
|
|
|
|
if (Zones.ZoneActived is null) return;
|
|
if (MapData is null) { Snackbar.Add("Dữ liệu bản đồ rỗng", Severity.Warning); return; }
|
|
if (MapData.Active) { Snackbar.Add("Không thể tạo edge do bản đồ đang được Active", Severity.Warning); return; }
|
|
|
|
var parameters = new DialogParameters<ConfirmDialog>
|
|
{
|
|
{ x => x.Content, "Bạn chắc chắn muốn xóa Zone đi không?" },
|
|
{ x => x.ConfirmText, "Delete" },
|
|
{ x => x.Color, Color.Secondary }
|
|
};
|
|
var Confirm = await Dialog.ShowAsync<ConfirmDialog>("Xoá Zone", parameters);
|
|
var ConfirmResult = await Confirm.Result;
|
|
if (ConfirmResult is not null && ConfirmResult.Data is not null && bool.TryParse(ConfirmResult.Data.ToString(), out bool data) && data)
|
|
{
|
|
|
|
var result = await Http.DeleteFromJsonAsync<MessageResult>($"/api/zones/{Zones.ZoneActived.Id}");
|
|
if (result is null) { Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error); return; }
|
|
else if (!result.IsSuccess) { Snackbar.Add(result.Message ?? "Xoá Zone không thành công", Severity.Error); return; }
|
|
|
|
|
|
var removeZone = Zones.ZoneActived;
|
|
Zones.Remove(removeZone);
|
|
var zoneEditSteps = EditorBackup.Where(step => step.Id == removeZone.Id);
|
|
for (int i = 0; i < zoneEditSteps.Count(); i++) EditorBackup.Remove(zoneEditSteps.ElementAt(i));
|
|
|
|
ZoneControlPointRef.SetControl(null);
|
|
Snackbar.Add("Xoá Zone thành công", Severity.Success);
|
|
}
|
|
}
|
|
|
|
public async Task<bool> SaveChanged()
|
|
{
|
|
if (EditorBackup.Count == 0) return true;
|
|
if (MapData is null) { Snackbar.Add("Dữ liệu bản đồ rỗng", Severity.Warning); return false; }
|
|
if (MapData.Active) { Snackbar.Add("Không thể tạo edge do bản đồ đang được Active", Severity.Warning); return false; }
|
|
|
|
var parameters = new DialogParameters<ConfirmDialog>
|
|
{
|
|
{ x => x.Content, "Có dữ liệu thay đổi, bạn có muốn lưu lại không?" },
|
|
{ x => x.ConfirmText, "Save" },
|
|
{ x => x.Color, Color.Primary }
|
|
};
|
|
var Confirm = await Dialog.ShowAsync<ConfirmDialog>("Lưu", parameters);
|
|
var ConfirmResult = await Confirm.Result;
|
|
if (ConfirmResult is not null && ConfirmResult.Data is not null && bool.TryParse(ConfirmResult.Data.ToString(), out bool data) && data)
|
|
{
|
|
var editBackupSteps = new List<MapEditorBackup>();
|
|
foreach (var backup in EditorBackup)
|
|
{
|
|
switch (backup.Type)
|
|
{
|
|
case MapEditorBackupType.Node:
|
|
if (Nodes.TryGetValue(backup.Id, out var node) && node != null)
|
|
{
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.Node,
|
|
Id = node.Id,
|
|
Obj = new PositionBackup()
|
|
{
|
|
Id = node.Id,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case MapEditorBackupType.ControlPoint1Edge:
|
|
if (Edges.TryGetValue(backup.Id, out var edge1) && edge1 is not null)
|
|
{
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.ControlPoint1Edge,
|
|
Id = edge1.Id,
|
|
Obj = new PositionBackup()
|
|
{
|
|
Id = edge1.Id,
|
|
X = edge1.ControlPoint1X,
|
|
Y = edge1.ControlPoint1Y,
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case MapEditorBackupType.ControlPoint2Edge:
|
|
if (Edges.TryGetValue(backup.Id, out var edge2) && edge2 is not null)
|
|
{
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.ControlPoint2Edge,
|
|
Id = edge2.Id,
|
|
Obj = new PositionBackup()
|
|
{
|
|
Id = edge2.Id,
|
|
X = edge2.ControlPoint2X,
|
|
Y = edge2.ControlPoint2Y,
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case MapEditorBackupType.Zone:
|
|
if (Zones.TryGetValue(backup.Id, out var zone) && zone is not null)
|
|
{
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.Zone,
|
|
Id = zone.Id,
|
|
Obj = new ZoneShapeBackup()
|
|
{
|
|
X1 = zone.X1,
|
|
Y1 = zone.Y1,
|
|
X2 = zone.X2,
|
|
Y2 = zone.Y2,
|
|
X3 = zone.X3,
|
|
Y3 = zone.Y3,
|
|
X4 = zone.X4,
|
|
Y4 = zone.Y4,
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case MapEditorBackupType.MoveNode:
|
|
if (backup.Obj is List<PositionBackup> nodesChange)
|
|
{
|
|
foreach (var nodeChange in nodesChange)
|
|
{
|
|
if (Nodes.TryGetValue(nodeChange.Id, out var nodemove) && nodemove != null)
|
|
{
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.Node,
|
|
Id = nodemove.Id,
|
|
Obj = new PositionBackup()
|
|
{
|
|
Id = nodemove.Id,
|
|
X = nodemove.X,
|
|
Y = nodemove.Y,
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.MoveEdge:
|
|
List<EdgeBackup> saveEdge = [];
|
|
if (backup.Obj is List<EdgeBackup> edgesBackup)
|
|
{
|
|
foreach (var edgeChange in edgesBackup)
|
|
{
|
|
if (Edges.TryGetValue(edgeChange.Id, out var edge) && edge != null)
|
|
{
|
|
saveEdge.Add(new()
|
|
{
|
|
Id = edge.Id,
|
|
TrajectoryDegree = edge.TrajectoryDegree,
|
|
StartX = edge.X1,
|
|
StartY = edge.Y1,
|
|
EndX = edge.X2,
|
|
EndY = edge.Y2,
|
|
ControlPoint1X = edge.ControlPoint1X,
|
|
ControlPoint1Y = edge.ControlPoint1Y,
|
|
ControlPoint2X = edge.ControlPoint2X,
|
|
ControlPoint2Y = edge.ControlPoint2Y
|
|
});
|
|
}
|
|
}
|
|
}
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveEdge,
|
|
Obj = saveEdge,
|
|
});
|
|
break;
|
|
case MapEditorBackupType.Copy:
|
|
if (MapCopyRef.isMoving)
|
|
{
|
|
List<EdgeMapCopyModel> edgeCopyModel = [.. MapCopyRef.EdgeModels.Select(e => new EdgeMapCopyModel()
|
|
{
|
|
X1 = e.X1,
|
|
Y1 = e.Y1,
|
|
X2 = e.X2,
|
|
Y2 = e.Y2,
|
|
MapId = e.MapId,
|
|
TrajectoryDegree = e.TrajectoryDegree,
|
|
StartNodeId = e.StartNode.Id,
|
|
EndNodeId = e.EndNode.Id,
|
|
ControlPoint1X = e.ControlPoint1X,
|
|
ControlPoint1Y = e.ControlPoint1Y,
|
|
ControlPoint2X = e.ControlPoint2X,
|
|
ControlPoint2Y = e.ControlPoint2Y,
|
|
Actions = e.Actions,
|
|
AllowedDeviationTheta = e.AllowedDeviationTheta,
|
|
AllowedDeviationXy = e.AllowedDeviationXy,
|
|
DirectionAllowed = e.DirectionAllowed,
|
|
RotationAllowed = e.RotationAllowed,
|
|
MaxHeight = e.MaxHeight,
|
|
MinHeight = e.MinHeight,
|
|
MaxRotationSpeed = e.MaxRotationSpeed,
|
|
MaxSpeed = e.MaxSpeed,
|
|
})];
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.Copy,
|
|
Obj = edgeCopyModel,
|
|
});
|
|
}
|
|
else MapCopyRef.OnShow(false, []);
|
|
break;
|
|
case MapEditorBackupType.SplitNode:
|
|
editBackupSteps.Add(backup);
|
|
break;
|
|
case MapEditorBackupType.MergeNode:
|
|
if (backup.Obj is MergeNodeBackup mergeBackup)
|
|
{
|
|
Dictionary<Guid, Guid> edgeMerge = [];
|
|
mergeBackup.EdgesMerge.ForEach(e => edgeMerge.Add(e.edgeId, e.node.Id));
|
|
editBackupSteps.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MergeNode,
|
|
Id = mergeBackup.NodeId,
|
|
Obj = new MergeNodeUpdate()
|
|
{
|
|
NodeId = mergeBackup.NodeId,
|
|
EdgesMerge = edgeMerge,
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
var result = await (await Http.PutAsJsonAsync($"api/MapsData/{MapData.Id}/updates", new MapEditorBackupModel() { Steps = [.. editBackupSteps], })).Content.ReadFromJsonAsync<MessageResult<IEnumerable<EdgeDto>>>();
|
|
if (result is null) { Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error); return false; }
|
|
else if (!result.IsSuccess) { Snackbar.Add(result.Message ?? "Tạo Edge không thành công", Severity.Error); return false; }
|
|
else if (result.Data is null) { Snackbar.Add("Lỗi dữ liệu trả về", Severity.Error); return false; }
|
|
else if (result.Data.Any())
|
|
{
|
|
foreach (var edge in result.Data)
|
|
{
|
|
if (edge.StartNode is null || edge.EndNode is null) continue;
|
|
if (!Nodes.ContainsKey(edge.StartNodeId)) Nodes.Add(edge.StartNode);
|
|
if (!Nodes.ContainsKey(edge.EndNodeId)) Nodes.Add(edge.EndNode);
|
|
Edges.Add(edge, Nodes[edge.StartNodeId], Nodes[edge.EndNodeId]);
|
|
}
|
|
MapCopyRef.OnShow(false, []);
|
|
}
|
|
|
|
EditorBackup.Clear();
|
|
await NodesUndoableChanged.InvokeAsync(false);
|
|
Snackbar.Add("Cập nhật thành công", Severity.Success);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void UndoEditorBackup()
|
|
{
|
|
if (EditorBackup.Count == 0 || MapData.Active) return;
|
|
|
|
var lastChange = EditorBackup[^1];
|
|
EditorBackup.Remove(lastChange);
|
|
|
|
switch (lastChange.Type)
|
|
{
|
|
case MapEditorBackupType.Node:
|
|
if (lastChange.Obj is PositionBackup nodepos)
|
|
{
|
|
if (Nodes.TryGetValue(lastChange.Id, out var node) && node != null)
|
|
{
|
|
node.UpdatePosition(nodepos.X, nodepos.Y);
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.ControlPoint1Edge:
|
|
if (lastChange.Obj is PositionBackup cppos)
|
|
{
|
|
if (Edges.TryGetValue(lastChange.Id, out var edge) && edge != null)
|
|
{
|
|
edge.UpdateControlPoint1(cppos.X, cppos.Y);
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.ControlPoint2Edge:
|
|
if (lastChange.Obj is PositionBackup cp2pos)
|
|
{
|
|
if (Edges.TryGetValue(lastChange.Id, out var edge) && edge != null)
|
|
{
|
|
edge.UpdateControlPoint2(cp2pos.X, cp2pos.Y);
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.Zone:
|
|
if (lastChange.Obj is ZoneShapeBackup zoneshape)
|
|
{
|
|
if (Zones.TryGetValue(lastChange.Id, out var zone) && zone is not null)
|
|
{
|
|
zone.UpdateControlNode(zoneshape.X1, zoneshape.Y1, zoneshape.X2, zoneshape.Y2, zoneshape.X3, zoneshape.Y3, zoneshape.X4, zoneshape.Y4);
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.MoveNode:
|
|
if (lastChange.Obj is List<PositionBackup> nodesbackup)
|
|
{
|
|
foreach (var node in nodesbackup)
|
|
{
|
|
if (Nodes.TryGetValue(node.Id, out var model) && model != null)
|
|
{
|
|
model.UpdatePosition(node.X, node.Y);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.MoveEdge:
|
|
if (lastChange.Obj is List<EdgeBackup> edgesBackup)
|
|
{
|
|
foreach (var edge in edgesBackup)
|
|
{
|
|
if (Edges.TryGetValue(edge.Id, out var model) && model != null)
|
|
{
|
|
model.StartNode.UpdatePosition(edge.StartX, edge.StartY);
|
|
model.EndNode.UpdatePosition(edge.EndX, edge.EndY);
|
|
if (model.TrajectoryDegree == TrajectoryDegree.Two || model.TrajectoryDegree == TrajectoryDegree.Three) model.UpdateControlPoint1(edge.ControlPoint1X, edge.ControlPoint1Y);
|
|
if (model.TrajectoryDegree == TrajectoryDegree.Three) model.UpdateControlPoint2(edge.ControlPoint2X, edge.ControlPoint2Y);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.Copy:
|
|
MapCopyRef.OnShow(false, []);
|
|
break;
|
|
case MapEditorBackupType.SplitNode:
|
|
if (lastChange.Obj is SplitNodeBackup splitBackup)
|
|
{
|
|
var node = Nodes[splitBackup.NodeId];
|
|
if (node is not null)
|
|
{
|
|
foreach (var data in splitBackup.EdgeSplit)
|
|
{
|
|
var edge = Edges[data.Key];
|
|
if (edge is not null && (edge.StartNode.Id == data.Value.Id || edge.EndNode.Id == data.Value.Id))
|
|
{
|
|
edge.UpdateNode(data.Value.Id, node);
|
|
Nodes.Remove(Nodes[data.Value.Id]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MapEditorBackupType.MergeNode:
|
|
if (lastChange.Obj is MergeNodeBackup mergeBackup)
|
|
{
|
|
foreach (var (edgeId, node) in mergeBackup.EdgesMerge)
|
|
{
|
|
var edge = Edges[edgeId];
|
|
if (edge is not null && (edge.StartNode.Id == mergeBackup.NodeId || edge.EndNode.Id == mergeBackup.NodeId))
|
|
{
|
|
Nodes.Add(new NodeDto()
|
|
{
|
|
Id = node.Id,
|
|
MapId = node.MapId,
|
|
Name = node.Name,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
Theta = node.Theta,
|
|
AllowedDeviationXy = node.AllowedDeviationXy,
|
|
AllowedDeviationTheta = node.AllowedDeviationTheta,
|
|
Actions = node.Actions
|
|
});
|
|
edge.UpdateNode(mergeBackup.NodeId, Nodes[node.Id]);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
NodesUndoableChanged.InvokeAsync(EditorBackup.Count > 0);
|
|
}
|
|
|
|
public void HorizontalLeft()
|
|
{
|
|
if (Nodes.ActivedNodes.Count > 0)
|
|
{
|
|
List<PositionBackup> ActiveNode = [];
|
|
double MinX = Nodes.ActivedNodes.First().X;
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
ActiveNode.Add(new()
|
|
{
|
|
Id = node.Id,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
});
|
|
if (MinX > node.X) MinX = node.X;
|
|
}
|
|
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveNode,
|
|
Obj = ActiveNode,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
node.UpdatePosition(MinX, node.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void HorizontalRight()
|
|
{
|
|
if (Nodes.ActivedNodes.Count > 0)
|
|
{
|
|
List<PositionBackup> ActiveNode = [];
|
|
double MaxX = Nodes.ActivedNodes.First().X;
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
ActiveNode.Add(new()
|
|
{
|
|
Id = node.Id,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
});
|
|
if (MaxX < node.X) MaxX = node.X;
|
|
}
|
|
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveNode,
|
|
Obj = ActiveNode,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
node.UpdatePosition(MaxX, node.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void VerticalTop()
|
|
{
|
|
if (Nodes.ActivedNodes.Count > 0)
|
|
{
|
|
List<PositionBackup> ActiveNode = [];
|
|
double MaxY = Nodes.ActivedNodes.First().Y;
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
ActiveNode.Add(new()
|
|
{
|
|
Id = node.Id,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
});
|
|
if (MaxY < node.Y) MaxY = node.Y;
|
|
}
|
|
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveNode,
|
|
Obj = ActiveNode,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
node.UpdatePosition(node.X, MaxY);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void VerticalBottom()
|
|
{
|
|
if (Nodes.ActivedNodes.Count > 0)
|
|
{
|
|
List<PositionBackup> ActiveNode = [];
|
|
double MinY = Nodes.ActivedNodes.First().Y;
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
ActiveNode.Add(new()
|
|
{
|
|
Id = node.Id,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
});
|
|
if (MinY > node.Y) MinY = node.Y;
|
|
}
|
|
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveNode,
|
|
Obj = ActiveNode,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
node.UpdatePosition(node.X, MinY);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void HorizontalCenter()
|
|
{
|
|
if (Nodes.ActivedNodes.Count > 2)
|
|
{
|
|
List<PositionBackup> ActiveNode = [];
|
|
double MinX = Nodes.ActivedNodes.First().X;
|
|
double MaxX = Nodes.ActivedNodes.First().X;
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
ActiveNode.Add(new()
|
|
{
|
|
Id = node.Id,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
});
|
|
if (MinX > node.X) MinX = node.X;
|
|
if (MaxX < node.X) MaxX = node.X;
|
|
}
|
|
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveNode,
|
|
Obj = ActiveNode,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
|
|
List<NodeModel> NodeSort = [.. Nodes.ActivedNodes.OrderBy(n => n.X)];
|
|
var offset = (MaxX - MinX) / (Nodes.ActivedNodes.Count - 1);
|
|
|
|
for (int i = 1; i < NodeSort.Count - 1; i++)
|
|
{
|
|
var node = Nodes.ActivedNodes.FirstOrDefault(n => n.Id == NodeSort[i].Id);
|
|
node?.UpdatePosition(NodeSort[0].X + offset * i, node.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void VerticalCenter()
|
|
{
|
|
if (Nodes.ActivedNodes.Count > 2)
|
|
{
|
|
List<PositionBackup> ActiveNode = [];
|
|
double MinY = Nodes.ActivedNodes.First().Y;
|
|
double MaxY = Nodes.ActivedNodes.First().Y;
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
ActiveNode.Add(new()
|
|
{
|
|
Id = node.Id,
|
|
X = node.X,
|
|
Y = node.Y,
|
|
});
|
|
if (MinY > node.Y) MinY = node.Y;
|
|
if (MaxY < node.Y) MaxY = node.Y;
|
|
}
|
|
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveNode,
|
|
Obj = ActiveNode,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
|
|
List<NodeModel> NodeSort = [.. Nodes.ActivedNodes.OrderBy(n => n.Y)];
|
|
var offset = (MaxY - MinY) / (Nodes.ActivedNodes.Count - 1);
|
|
|
|
for (int i = 1; i < NodeSort.Count - 1; i++)
|
|
{
|
|
var node = Nodes.ActivedNodes.FirstOrDefault(n => n.Id == NodeSort[i].Id);
|
|
node?.UpdatePosition(node.X, NodeSort[0].Y + offset * i);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ZoneUpdateShape(double x, double y)
|
|
{
|
|
if (Zones.ZoneActived is not null)
|
|
{
|
|
if (EditorBackup.Count == 0 || EditorBackup.Last().Id != Zones.ZoneActived.Id || EditorBackup.Count > 0 && EditorBackup.Last().Obj is ZoneShapeBackup zonebackup && zonebackup.NodeChange != Zones.ZoneActived.ActiveNode)
|
|
{
|
|
EditorBackup.Add(new()
|
|
{
|
|
Id = Zones.ZoneActived.Id,
|
|
Type = MapEditorBackupType.Zone,
|
|
Obj = new ZoneShapeBackup()
|
|
{
|
|
NodeChange = Zones.ZoneActived.ActiveNode,
|
|
X1 = Zones.ZoneActived.X1,
|
|
Y1 = Zones.ZoneActived.Y1,
|
|
X2 = Zones.ZoneActived.X2,
|
|
Y2 = Zones.ZoneActived.Y2,
|
|
X3 = Zones.ZoneActived.X3,
|
|
Y3 = Zones.ZoneActived.Y3,
|
|
X4 = Zones.ZoneActived.X4,
|
|
Y4 = Zones.ZoneActived.Y4,
|
|
}
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
}
|
|
if (Zones.ZoneActived.ActiveNode > 0) Zones.ZoneActived.UpdateControlNode(Zones.ZoneActived.ActiveNode, x, y);
|
|
}
|
|
}
|
|
|
|
public void EdgesPositionMove(double x, double y)
|
|
{
|
|
if (EditorBackup.Count == 0)
|
|
{
|
|
List<EdgeBackup> BackupEdge = [];
|
|
foreach (var edge in Edges.ActivedEdges)
|
|
{
|
|
BackupEdge.Add(new()
|
|
{
|
|
Id = edge.Id,
|
|
TrajectoryDegree = edge.TrajectoryDegree,
|
|
StartX = edge.X1,
|
|
StartY = edge.Y1,
|
|
EndX = edge.X2,
|
|
EndY = edge.Y2,
|
|
ControlPoint1X = edge.ControlPoint1X,
|
|
ControlPoint1Y = edge.ControlPoint1Y,
|
|
ControlPoint2X = edge.ControlPoint2X,
|
|
ControlPoint2Y = edge.ControlPoint2Y,
|
|
});
|
|
}
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveEdge,
|
|
Obj = BackupEdge,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
}
|
|
Edges.UpdateMoveEdge(x, y);
|
|
}
|
|
|
|
public void NodesPositionMove(double x, double y)
|
|
{
|
|
if (EditorBackup.Count == 0)
|
|
{
|
|
List<PositionBackup> ActiveNode = [];
|
|
foreach (var node in Nodes.ActivedNodes)
|
|
{
|
|
ActiveNode.Add(new()
|
|
{
|
|
Id = node.Id,
|
|
X = x,
|
|
Y = y,
|
|
});
|
|
}
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MoveNode,
|
|
Obj = ActiveNode,
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
}
|
|
Nodes.UpdateMoveNode(x, y);
|
|
}
|
|
|
|
public void NodePositionMove(double x, double y)
|
|
{
|
|
if (Nodes.SelectedNode is not null)
|
|
{
|
|
if (EditorBackup.Count == 0 || EditorBackup.Last().Id != Nodes.SelectedNode.Id)
|
|
{
|
|
EditorBackup.Add(new()
|
|
{
|
|
Id = Nodes.SelectedNode.Id,
|
|
Type = MapEditorBackupType.Node,
|
|
Obj = new PositionBackup()
|
|
{
|
|
Id = Nodes.SelectedNode.Id,
|
|
X = Nodes.SelectedNode.X,
|
|
Y = Nodes.SelectedNode.Y,
|
|
}
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
}
|
|
|
|
Nodes.SelectedNode.UpdatePosition(x, y);
|
|
}
|
|
else if (Edges.ActivedEdges.Count == 1)
|
|
{
|
|
if (Edges.ActivedEdges[0].ActivedControlPoint1)
|
|
{
|
|
if (EditorBackup.Count == 0 || EditorBackup.Last().Id != Edges.ActivedEdges[0].Id || EditorBackup.Last().Id == Edges.ActivedEdges[0].Id && EditorBackup.Last().Type != MapEditorBackupType.ControlPoint1Edge)
|
|
{
|
|
EditorBackup.Add(new()
|
|
{
|
|
Id = Edges.ActivedEdges[0].Id,
|
|
Type = MapEditorBackupType.ControlPoint1Edge,
|
|
Obj = new PositionBackup()
|
|
{
|
|
Id = Edges.ActivedEdges[0].Id,
|
|
X = Edges.ActivedEdges[0].ControlPoint1X,
|
|
Y = Edges.ActivedEdges[0].ControlPoint1Y,
|
|
}
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
}
|
|
Edges.ActivedEdges[0].UpdateControlPoint1(x, y);
|
|
}
|
|
else if (Edges.ActivedEdges[0].ActivedControlPoint2)
|
|
{
|
|
if (EditorBackup.Count == 0 || EditorBackup.Last().Id != Edges.ActivedEdges[0].Id || EditorBackup.Last().Id == Edges.ActivedEdges[0].Id && EditorBackup.Last().Type != MapEditorBackupType.ControlPoint2Edge)
|
|
{
|
|
EditorBackup.Add(new()
|
|
{
|
|
Id = Edges.ActivedEdges[0].Id,
|
|
Type = MapEditorBackupType.ControlPoint2Edge,
|
|
Obj = new PositionBackup()
|
|
{
|
|
Id = Edges.ActivedEdges[0].Id,
|
|
X = Edges.ActivedEdges[0].ControlPoint2X,
|
|
Y = Edges.ActivedEdges[0].ControlPoint2Y,
|
|
}
|
|
});
|
|
NodesUndoableChanged.InvokeAsync(true);
|
|
}
|
|
Edges.ActivedEdges[0].UpdateControlPoint2(x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task SplitNode()
|
|
{
|
|
if (EditorBackup.Count > 0)
|
|
{
|
|
if (!await SaveChanged()) return;
|
|
}
|
|
|
|
if (MapData is null) { Snackbar.Add("Dữ liệu bản đồ rỗng", Severity.Warning); return; }
|
|
if (MapData.Active) { Snackbar.Add("Không thể tạo edge do bản đồ đang được Active", Severity.Warning); return; }
|
|
|
|
if (Nodes.ActivedNodes.Count > 1) { Snackbar.Add("Vui lòng chỉ chọn 1 Node để tách", Severity.Warning); return; }
|
|
if (Nodes.ActivedNodes.Count == 0) { Snackbar.Add("Vui lòng chọn 1 Node để tách", Severity.Warning); return; }
|
|
|
|
var nodeSplit = Nodes.ActivedNodes[0];
|
|
|
|
var edges = Edges.Where(edge => edge.StartNode.Id == nodeSplit.Id || edge.EndNode.Id == nodeSplit.Id).ToList();
|
|
if (edges.Count <= 1) { Snackbar.Add("Không thể tách node đơn", Severity.Warning); return; }
|
|
|
|
Dictionary<Guid, NodeDto> EdgeSplit = [];
|
|
for (int i = 1; i < edges.Count; i++)
|
|
{
|
|
var newNode = new NodeDto()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = string.Empty,
|
|
X = nodeSplit.X,
|
|
Y = nodeSplit.Y,
|
|
Theta = nodeSplit.Theta,
|
|
MapId = nodeSplit.MapId,
|
|
AllowedDeviationXy = nodeSplit.AllowedDeviationXy,
|
|
AllowedDeviationTheta = nodeSplit.AllowedDeviationTheta,
|
|
Actions = nodeSplit.Actions,
|
|
};
|
|
Nodes.Add(newNode);
|
|
edges[i].UpdateNode(nodeSplit.Id, Nodes[newNode.Id]);
|
|
EdgeSplit.Add(edges[i].Id, newNode);
|
|
}
|
|
Nodes.UnActivedNode();
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.SplitNode,
|
|
Id = nodeSplit.Id,
|
|
Obj = new SplitNodeBackup()
|
|
{
|
|
NodeId = nodeSplit.Id,
|
|
EdgeSplit = EdgeSplit,
|
|
},
|
|
});
|
|
await NodesUndoableChanged.InvokeAsync(true);
|
|
Snackbar.Add("Tách Node thành công", Severity.Success);
|
|
}
|
|
|
|
public async Task MergeNode()
|
|
{
|
|
if (EditorBackup.Count > 0)
|
|
{
|
|
if (!await SaveChanged()) return;
|
|
}
|
|
|
|
if (MapData is null) { Snackbar.Add("Dữ liệu bản đồ rỗng", Severity.Warning); return; }
|
|
if (MapData.Active) { Snackbar.Add("Không thể tạo edge do bản đồ đang được Active", Severity.Warning); return; }
|
|
|
|
if (Nodes.ActivedNodes.Count <= 1) { Snackbar.Add("Vui lòng chọn nhiều hơn 1 Node để gộp", Severity.Warning); return; }
|
|
if (Edges.ActivedEdges.Count > 0) { Snackbar.Add("Vui lòng không chọn Edge", Severity.Warning); return; }
|
|
|
|
MergeNodeBackup MergeNodeBackup = new()
|
|
{
|
|
NodeId = Nodes.ActivedNodes[0].Id,
|
|
};
|
|
List<NodeModel> activedNodes = [.. Nodes.ActivedNodes];
|
|
Nodes.UnActivedNode();
|
|
for (int i = 1; i < activedNodes.Count; i++)
|
|
{
|
|
var edges = Edges.Where(e => e.StartNode.Id == activedNodes[i].Id || e.EndNode.Id == activedNodes[i].Id).ToList();
|
|
if (edges.Count > 0)
|
|
{
|
|
foreach (var edge in edges)
|
|
{
|
|
edge.UpdateNode(activedNodes[i].Id, Nodes[activedNodes[0].Id]);
|
|
MergeNodeBackup.EdgesMerge.Add((edge.Id, activedNodes[i]));
|
|
Nodes.Remove(activedNodes[i]);
|
|
}
|
|
}
|
|
}
|
|
EditorBackup.Add(new()
|
|
{
|
|
Type = MapEditorBackupType.MergeNode,
|
|
Id = activedNodes[0].Id,
|
|
Obj = MergeNodeBackup,
|
|
});
|
|
await NodesUndoableChanged.InvokeAsync(true);
|
|
Snackbar.Add("Gộp Node thành công", Severity.Success);
|
|
}
|
|
|
|
public async Task ScanerActive(double x1, double y1, double x2, double y2)
|
|
{
|
|
List<EdgeModel> activeEdges = [];
|
|
List<NodeModel> activeNodes = [];
|
|
foreach (var edge in Edges)
|
|
{
|
|
if (MapEditorHelper.NodeInScanZone(edge.StartNode.X, edge.StartNode.Y, x1, y1, x2, y2) && MapEditorHelper.NodeInScanZone(edge.EndNode.X, edge.EndNode.Y, x1, y1, x2, y2))
|
|
{
|
|
activeEdges.Add(edge);
|
|
if (!activeNodes.Any(n => n.Id == edge.StartNode.Id)) activeNodes.Add(edge.StartNode);
|
|
if (!activeNodes.Any(n => n.Id == edge.EndNode.Id)) activeNodes.Add(edge.EndNode);
|
|
}
|
|
}
|
|
foreach (var node in Nodes)
|
|
{
|
|
if (!activeNodes.Any(n => n.Id == node.Id) && MapEditorHelper.NodeInScanZone(node.X, node.Y, x1, y1, x2, y2)) activeNodes.Add(node);
|
|
}
|
|
|
|
Edges.ActivedEdge(activeEdges);
|
|
Nodes.ActivedNode(activeNodes);
|
|
|
|
await MultiselectedNodeChanged.InvokeAsync(activeNodes.Count > 0);
|
|
await MultiselectedEdgeChanged.InvokeAsync(activeEdges.Count > 0);
|
|
}
|
|
|
|
public async Task CheckMap()
|
|
{
|
|
await MapIsChecking.InvokeAsync(true);
|
|
|
|
var mapSetting = await Http.GetFromJsonAsync<MessageResult<MapSettingDefaultDto>>($"api/MapsSetting/{MapData.Id}");
|
|
|
|
if (mapSetting is null) { Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error); return; }
|
|
else if (!mapSetting.IsSuccess) { Snackbar.Add(mapSetting.Message ?? "Lấy dữ liệu bản đồ không thành công", Severity.Error); return; }
|
|
else if (mapSetting.Data == null) { Snackbar.Add("Lỗi dữ liệu trả về", Severity.Error); return; }
|
|
|
|
foreach (var edge in Edges)
|
|
{
|
|
var caculateEdge = new EdgeCaculatorModel()
|
|
{
|
|
MapId = edge.MapId,
|
|
X1 = edge.X1,
|
|
Y1 = edge.Y1,
|
|
X2 = edge.X2,
|
|
Y2 = edge.Y2,
|
|
ControlPoint1X = edge.ControlPoint1X,
|
|
ControlPoint1Y = edge.ControlPoint1Y,
|
|
ControlPoint2X = edge.ControlPoint2X,
|
|
ControlPoint2Y = edge.ControlPoint2Y,
|
|
TrajectoryDegree = edge.TrajectoryDegree,
|
|
};
|
|
if (MapEditorHelper.GetEdgeLength(caculateEdge) < mapSetting.Data.EdgeMinLength) edge.SetError(true);
|
|
else edge.SetError(false);
|
|
}
|
|
|
|
List<ZoneModel> ErrorZones = [];
|
|
foreach (var zone in Zones)
|
|
{
|
|
if (MapEditorHelper.CalculateQuadrilateralArea(zone.X1, zone.Y1, zone.X2, zone.Y2, zone.X3, zone.Y3, zone.X4, zone.Y4) < mapSetting.Data.ZoneMinSquare)
|
|
{
|
|
ErrorZones.Add(zone);
|
|
}
|
|
}
|
|
|
|
List<NodeModel> ErrorNodes = [];
|
|
foreach (var node in Nodes)
|
|
{
|
|
if (ErrorNodes.Any(e => e.Id == node.Id)) continue;
|
|
foreach (var checkNode in Nodes)
|
|
{
|
|
if (checkNode.Id == node.Id) continue;
|
|
var distance = Math.Sqrt(Math.Pow(node.X - checkNode.X, 2) + Math.Pow(node.Y - checkNode.Y, 2));
|
|
|
|
if (distance < mapSetting.Data.EdgeMinLength)
|
|
{
|
|
if (!Edges.Any(e => e.StartNode.Id == node.Id && e.EndNode.Id == checkNode.Id || e.EndNode.Id == node.Id && e.StartNode.Id == checkNode.Id))
|
|
{
|
|
ErrorNodes.Add(node);
|
|
node.SetError(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!ErrorNodes.Any(e => e.Id == node.Id)) node.SetError(false);
|
|
}
|
|
|
|
await MapIsChecking.InvokeAsync(false);
|
|
Snackbar.Add("Kiểm tra xong", Severity.Success);
|
|
}
|
|
}
|