@using RobotNet.MapShares.Models
@using RobotNet.WebApp.Maps.Components.Editor.Edge
@using RobotNet.WebApp.Maps.Components.Editor.Node
@using RobotNet.WebApp.Maps.Components.Editor.Zone
@inject IJSRuntime JSRuntime
@inject IHttpClientFactory HttpClientFactory
@inject IDialogService Dialog
@inject ISnackbar Snackbar
@code {
[Parameter]
public bool ShowGrid { get; set; }
[Parameter]
public bool ShowName { get; set; }
[Parameter]
public bool ShowMapSlam { get; set; }
[Parameter]
public bool MapIsActive { get; set; }
[Parameter]
public ZoneType ZoneType { get; set; }
[Parameter]
public EditorState EditorState { get; set; }
[Parameter]
public EventCallback MultiselectedEdgeChanged { get; set; }
[Parameter]
public EventCallback MultiselectedNodeChanged { get; set; }
[Parameter]
public EventCallback ZoneselectedChanged { get; set; }
[Parameter]
public EventCallback NodesUndoableChanged { get; set; }
[Parameter]
public EventCallback KeyPress { get; set; }
[Parameter]
public EventCallback MapIsChecking { get; set; }
private MapMousePosition MapInfoRef = null!;
private EdgeStraightCreating EdgeStraightCreatingRef = null!;
private EdgeCurveCreating EdgeCurveCreatingRef = null!;
private MapEdge MapEdgesRef = null!;
private MapGrid MapGridRef = null!;
private EdgeControlPoint EdgeControlPointRef = null!;
private MapScaner MapScanerRef = null!;
private ZoneCreating ZoneCreatingRef = null!;
private MapZone MapZoneRef = null!;
private ZoneControlPoint ZoneControlPointRef = null!;
private MapCopy MapCopyRef = null!;
private DotNetObjectReference 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 Scale = 1;
private double Left;
private double Top;
private double FitScale = 1;
private double Resolution = 1;
private double OriginX;
private double OriginY;
private double ImageWidth;
private double ImageHeight;
private MapDataDto MapData = new();
public MapEdgeModel Edges { get; set; } = [];
public MapNodeModel Nodes { get; set; } = [];
public MapZoneModel Zones { get; set; } = [];
public MapElementModel Elements { get; set; } = [];
private HttpClient Http = default!;
private bool OverlayIsVisible = false;
private string OverlayIsStr = "Loading...";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (!firstRender) return;
Http = HttpClientFactory.CreateClient("MapManagerAPI");
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("AddMouseMoveEventListener", DotNetObj, ViewContainerRef, nameof(MouseMoveOnMapContainer));
await JSRuntime.InvokeVoidAsync("AddEventListener", DotNetObj, ViewContainerRef, "click", nameof(ViewContainerClick));
await JSRuntime.InvokeVoidAsync("AddKeyUpEventListener", DotNetObj, ViewContainerRef, nameof(ViewContainerKeyUp));
await JSRuntime.InvokeVoidAsync("AddMouseWheelEventListener", DotNetObj, MapContainerRef, nameof(MouseWheelOnMapContainer));
await JSRuntime.InvokeVoidAsync("AddMouseUpEventListener", DotNetObj, MapContainerRef, nameof(MouseUpOnMapContainer));
await JSRuntime.InvokeVoidAsync("AddMouseDownEventListener", DotNetObj, MapContainerRef, nameof(MouseDownOnMapContainer));
await JSRuntime.InvokeVoidAsync("AddTouchMoveEventListener", DotNetObj, ViewContainerRef, nameof(TouchMoveOnMapContainer));
}
private async Task LoadMap()
{
try
{
OverlayIsVisible = true;
StateHasChanged();
var mapDataResult = await Http.GetFromJsonAsync>($"api/MapsData/{MapData.Id}");
if (mapDataResult is not null && mapDataResult.Data is not null)
{
MapIsActive = mapDataResult.Data.Active;
await LoadMap(mapDataResult.Data);
OverlayIsVisible = false;
}
else OverlayIsStr = "Map Not Existed";
StateHasChanged();
}
catch
{
OverlayIsStr = "Map Not Existed";
StateHasChanged();
return;
}
}
public async Task LoadMap(MapDataDto? mapData)
{
if (mapData == null)
{
MapData = new();
Nodes.ReplaceAll([]);
Edges.ReplaceAll([]);
Zones.ReplaceAll([]);
Elements.ReplaceAll([]);
Resolution = 1.0;
OriginY = 0;
OriginX = 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
{
MapData = mapData;
Nodes.ReplaceAll(MapData.Nodes);
Edges.ReplaceAll(MapData.Edges.Select(edge => new EdgeModel(edge, Nodes[edge.StartNodeId], Nodes[edge.EndNodeId])));
Zones.ReplaceAll(MapData.Zones);
Elements.ReplaceAll(MapData.Elements.Select(element => new ElementModel(element, Nodes[element.NodeId])));
Resolution = mapData.Resolution;
OriginY = -mapData.ImageHeight * Resolution - mapData.OriginY;
OriginX = mapData.OriginX;
ImageHeight = mapData.ImageHeight * Resolution;
ImageWidth = mapData.ImageWidth * Resolution;
MapGridRef.Resize(OriginX, OriginY, ImageHeight, ImageWidth);
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();
}
}
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 - (MapData is null ? 0 : MapData.OriginY);
await SetViewMovement(Left - centerXBefore * deltaY, Top - (ImageHeight - centerYBefore) * deltaY);
await JSRuntime.InvokeVoidAsync("SetMapSvgRect", MapContainerRef, ImageWidth * Scale, ImageHeight * Scale);
}
[JSInvokable]
public async Task ViewContainerClick() => await ViewContainerRef.FocusAsync();
[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 - (MapData is null ? 0 : MapData.OriginY);
await SetViewMovement(Left - mouseX * scaleChange, Top - (ImageHeight - mouseY) * scaleChange);
}
[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 (EditorState == EditorState.CreateCurveEdge || EditorState == EditorState.CreateDoubleCurveEdge) await EdgeCurveCreatingRef.Update(CursorX, CursorY);
else if (EditorState == EditorState.CreateZone) await ZoneCreatingRef.Update(CursorX, CursorY);
switch (buttons)
{
case 1:
if (MapIsActive) break;
switch (EditorState)
{
case EditorState.CreateStraighEdge:
await EdgeStraightCreatingRef.Update(CursorX, CursorY);
break;
case EditorState.CreateCurveEdge:
case EditorState.CreateDoubleCurveEdge:
await EdgeCurveCreatingRef.Update(CursorX, CursorY);
break;
case EditorState.Scaner:
await MapScanerRef.Update(CursorX, CursorY);
break;
case EditorState.NavigationEdit:
if (ctrlKey) NodePositionMove(CursorX, CursorY);
break;
case EditorState.SettingZone:
if (ctrlKey) ZoneUpdateShape(CursorX, CursorY);
break;
case EditorState.Move:
if (ctrlKey)
{
if (Edges.ActivedEdges.Count > 0) EdgesPositionMove(CursorX, CursorY);
else NodesPositionMove(CursorX, CursorY);
}
break;
case EditorState.Copy:
if (ctrlKey) MapCopyRef.UpdateMove(CursorX, CursorY);
break;
}
break;
case 4:
await SetViewMovement(Left + movementX, Top + movementY);
break;
}
}
[JSInvokable]
public async Task ViewContainerKeyUp(string code, string key, bool altKey, bool ctrlKey, bool shiftKey)
{
if (MapIsActive) return;
switch (code)
{
case "Escape":
await EditStateChange();
break;
case "Delete":
_ = InvokeAsync(async Task () =>
{
if (EditorState == EditorState.NavigationEdit || EditorState == EditorState.Scaner) await DeleteEdge();
else if (EditorState == EditorState.SettingZone) await DeleteZone();
});
break;
case "KeyZ":
if (ctrlKey) UndoEditorBackup();
break;
case "KeyS":
if (ctrlKey) await SaveChanged();
break;
case "KeyM":
if (EditorState != EditorState.Scaner) return;
if (ctrlKey)
{
if (EditorBackup.Count > 0)
{
var save = await SaveChanged();
if (!save) break;
}
await SetCursor("grab");
await KeyPress.InvokeAsync(EditorState.Move);
}
break;
case "KeyC":
if (EditorState != EditorState.Scaner) return;
if (ctrlKey) await KeyPress.InvokeAsync(EditorState.Copy);
break;
}
StateHasChanged();
}
[JSInvokable]
public async Task MouseUpOnMapContainer(int button, bool altKey, bool ctrlKey, bool shiftKey)
{
if (button == 0 && !MapIsActive)
{
switch (EditorState)
{
case EditorState.CreateStraighEdge:
await CreateEdge(EdgeStraightCreatingRef.X1, EdgeStraightCreatingRef.Y1, EdgeStraightCreatingRef.X2, EdgeStraightCreatingRef.Y2);
await EdgeStraightCreatingRef.Hidden();
break;
case EditorState.CreateCurveEdge:
case EditorState.CreateDoubleCurveEdge:
await EdgeCurveCreatingRef.FinishSubEdgeAsync(CursorX, CursorY);
if (EdgeCurveCreatingRef.CreateStep == EdgeCurveCreating.CreateCurveEdgeStep.Finish)
{
var edgeCreateModel = EdgeCurveCreatingRef.GetEdgeCreateModel();
await CreateEdge(edgeCreateModel.X1, edgeCreateModel.Y1, edgeCreateModel.X2, edgeCreateModel.Y2, edgeCreateModel.TrajectoryDegree, edgeCreateModel.ControlPoint1X, edgeCreateModel.ControlPoint1Y, edgeCreateModel.ControlPoint2X, edgeCreateModel.ControlPoint2Y);
await EdgeCurveCreatingRef.Hidden();
}
break;
case EditorState.Scaner:
await ScanerActive(MapScanerRef.X1, MapScanerRef.Y1, MapScanerRef.X2, MapScanerRef.Y2);
await MapScanerRef.Hidden();
break;
case EditorState.CreateZone:
if (ZoneCreatingRef.Step == 4)
{
await CreateZone(ZoneCreatingRef.X1, ZoneCreatingRef.Y1, ZoneCreatingRef.X2, ZoneCreatingRef.Y2, ZoneCreatingRef.X3, ZoneCreatingRef.Y3, ZoneCreatingRef.X4, ZoneCreatingRef.Y4, ZoneType);
await ZoneCreatingRef.Hidden();
}
break;
case EditorState.SettingZone:
if (Zones.ZoneActived is not null && Zones.ZoneActived.ActiveNode != 5) Zones.ZoneActived.ActiveNode = 5;
break;
case EditorState.NavigationEdit:
if (Edges.ActivedEdges.Count == 1)
{
Edges.ActivedEdges[0].ActivedControlPoint1 = false;
Edges.ActivedEdges[0].ActivedControlPoint2 = false;
}
Nodes.SelectedNode = null;
break;
case EditorState.Move:
await SetCursor("grab");
break;
}
StateHasChanged();
}
}
[JSInvokable]
public async Task MouseDownOnMapContainer(int button, bool altKey, bool ctrlKey, bool shiftKey)
{
if (button == 0 && !MapIsActive)
{
var startNode = MapEditorHelper.GetClosesNode(CursorX, CursorY, Nodes.Select(node => new NodeDto() { X = node.X, Y = node.Y }).ToList());
switch (EditorState)
{
case EditorState.CreateStraighEdge:
await EdgeStraightCreatingRef.StartCreateAsync(startNode?.X ?? CursorX, startNode?.Y ?? CursorY);
break;
case EditorState.CreateCurveEdge:
await EdgeCurveCreatingRef.CreateSubEdgeAsync(startNode?.X ?? CursorX, startNode?.Y ?? CursorY);
break;
case EditorState.CreateDoubleCurveEdge:
await EdgeCurveCreatingRef.CreateSubEdgeAsync(startNode?.X ?? CursorX, startNode?.Y ?? CursorY, TrajectoryDegree.Three);
break;
case EditorState.Scaner:
await MapScanerRef.CreateAsync(CursorX, CursorY);
break;
case EditorState.CreateZone:
await ZoneCreatingRef.CreateAsync(CursorX, CursorY);
break;
case EditorState.SettingZone:
if (Zones.ZoneActived is not null) Zones.ZoneActived.SetStartMovePosition(CursorX, CursorY);
break;
case EditorState.Move:
await SetCursor("grabbing");
Edges.SetStartMovePosition(CursorX, CursorY);
Nodes.SetStartMovePosition(CursorX, CursorY);
break;
case EditorState.Copy:
MapCopyRef.SetStartMovePosition(CursorX, CursorY);
break;
}
}
}
private async Task OnSelectedEdgeChanged(EdgeModel? model)
{
if (model is not null && EditorState == EditorState.NavigationEdit)
{
if (model.TrajectoryDegree == TrajectoryDegree.One) EdgeControlPointRef.SetControl(null);
else EdgeControlPointRef.SetControl(model);
Edges.ActivedEdge([model]);
await MultiselectedEdgeChanged.InvokeAsync(Edges.ActivedEdges.Count > 0);
}
else EdgeControlPointRef.SetControl(null);
}
private void OnActivedZoneChanged((ZoneModel model, bool state) data)
{
if (EditorState == EditorState.SettingZone)
{
if (data.state)
{
ZoneControlPointRef.SetControl(data.model);
_ = ZoneselectedChanged.InvokeAsync(true);
return;
}
}
ZoneControlPointRef.SetControl(null);
}
public async Task EditStateChange()
{
await EdgeCurveCreatingRef.Hidden();
await EdgeStraightCreatingRef.Hidden();
await ZoneCreatingRef.Hidden();
MapCopyRef.OnShow(false, []);
Zones.UnActivedZone();
EdgeControlPointRef.SetControl(null);
if (EditorState == EditorState.Copy) await CreateMapCopy();
if (EditorState != EditorState.Move)
{
Edges.UnActivedEdge();
Nodes.UnActivedNode();
}
if (EditorState != EditorState.Copy) await SaveChanged();
if (EditorState != EditorState.Copy && EditorState != EditorState.Move) await SetCursor("initial");
await MultiselectedEdgeChanged.InvokeAsync(Edges.ActivedEdges.Count() > 0);
await MultiselectedNodeChanged.InvokeAsync(Nodes.ActivedNodes.Count() > 0);
await ZoneselectedChanged.InvokeAsync(false);
StateHasChanged();
}
public async Task CreateMapCopy()
{
if (Edges.ActivedEdges.Count > 0)
{
if (EditorBackup.Count > 0)
{
var save = await SaveChanged();
if (!save) return;
}
MapCopyRef.OnShow(true, Edges.ActivedEdges);
EditorBackup.Add(new()
{
Type = MapEditorBackupType.Copy,
});
await NodesUndoableChanged.InvokeAsync(true);
await SetCursor("copy");
}
}
public async Task SetCursor(string data) => await JSRuntime.InvokeVoidAsync("ElementSetAttribute", MapContainerRef, "cursor", data);
}