@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
@OverlayIsStr
@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); }