diff --git a/RobotApp.Client/Pages/Home.razor b/RobotApp.Client/Pages/Home.razor
index e5a2b14..326a868 100644
--- a/RobotApp.Client/Pages/Home.razor
+++ b/RobotApp.Client/Pages/Home.razor
@@ -1,4 +1,5 @@
@page "/"
+@attribute [Authorize]
@using RobotApp.Client.Services
@using RobotApp.VDA5050.State
@using MudBlazor
diff --git a/RobotApp.Client/Pages/Order/EdgesPanel.razor b/RobotApp.Client/Pages/Order/EdgesPanel.razor
index 711fa93..dced021 100644
--- a/RobotApp.Client/Pages/Order/EdgesPanel.razor
+++ b/RobotApp.Client/Pages/Order/EdgesPanel.razor
@@ -117,14 +117,14 @@
}
- @if (edge.Radius > 0 && !edge.Expanded)
+ @if (!edge.HasTrajectory && edge.Radius > 0 && !edge.Expanded)
{
- Apply Curve
+ Apply Curve (generate node)
}
diff --git a/RobotApp.Client/Pages/Order/ImportOrderDialog.razor b/RobotApp.Client/Pages/Order/ImportOrderDialog.razor
new file mode 100644
index 0000000..42258d6
--- /dev/null
+++ b/RobotApp.Client/Pages/Order/ImportOrderDialog.razor
@@ -0,0 +1,104 @@
+@using System.Text.Json
+@using MudBlazor
+
+
+
+ Import Order JSON
+
+
+
+
+ @if (ShowWarning)
+ {
+
+ Only valid VDA 5050 Order JSON is accepted.
+ Invalid structure will be rejected.
+
+ }
+
+
+
+ @if (!string.IsNullOrEmpty(ErrorMessage))
+ {
+
+ @ErrorMessage
+
+ }
+
+
+
+
+ Cancel
+
+ Import
+
+
+
+@code {
+ [CascadingParameter] public IMudDialogInstance MudDialog { get; set; } = default!;
+
+ public string JsonText { get; set; } = "";
+ public string? ErrorMessage;
+ public bool ShowWarning { get; set; } = false;
+
+ private void Cancel() => MudDialog.Cancel();
+
+ private void ValidateAndImport()
+ {
+ ErrorMessage = null;
+ ShowWarning = false;
+
+ try
+ {
+ using var doc = JsonDocument.Parse(JsonText);
+ var root = doc.RootElement;
+
+ // ===== BASIC STRUCTURE CHECK =====
+ if (!root.TryGetProperty("nodes", out _) ||
+ !root.TryGetProperty("edges", out _))
+ throw new Exception("Missing 'nodes' or 'edges' field.");
+
+ var order = OrderMessage.FromSchemaObject(root);
+
+ ValidateOrder(order);
+
+ MudDialog.Close(DialogResult.Ok(order));
+ }
+ catch (JsonException)
+ {
+ ShowWarning = true;
+ ErrorMessage = "Invalid JSON format.";
+ }
+ catch (Exception ex)
+ {
+ ShowWarning = true;
+ ErrorMessage = ex.Message;
+ }
+ }
+
+ private void ValidateOrder(OrderMessage order)
+ {
+ if (order.Nodes.Count == 0)
+ throw new Exception("Order must contain at least one node.");
+
+ var nodeIds = order.Nodes.Select(n => n.NodeId).ToHashSet();
+
+ foreach (var e in order.Edges)
+ {
+ if (!nodeIds.Contains(e.StartNodeId) ||
+ !nodeIds.Contains(e.EndNodeId))
+ throw new Exception(
+ $"Edge '{e.EdgeId}' references unknown node."
+ );
+ }
+ }
+}
diff --git a/RobotApp.Client/Pages/Order/JsonOutputPanel.razor b/RobotApp.Client/Pages/Order/JsonOutputPanel.razor
index 5cb2ccd..513e27c 100644
--- a/RobotApp.Client/Pages/Order/JsonOutputPanel.razor
+++ b/RobotApp.Client/Pages/Order/JsonOutputPanel.razor
@@ -5,7 +5,16 @@
-
+
+
+ Import JSON
+
+
+
-
+
-
@@ -45,10 +53,11 @@
[Parameter] public string OrderJson { get; set; } = "";
[Parameter] public bool Copied { get; set; }
[Parameter] public bool? SendSuccess { get; set; }
+
[Parameter] public EventCallback OnCopy { get; set; }
[Parameter] public EventCallback OnSend { get; set; }
+ [Parameter] public EventCallback OnImport { get; set; }
- // ================= SEND BUTTON STATE =================
private string SendButtonText =>
SendSuccess switch
{
diff --git a/RobotApp.Client/Pages/Order/OrderMess.razor b/RobotApp.Client/Pages/Order/OrderMess.razor
index 93a3b15..84f5eb5 100644
--- a/RobotApp.Client/Pages/Order/OrderMess.razor
+++ b/RobotApp.Client/Pages/Order/OrderMess.razor
@@ -50,7 +50,8 @@
Copied="@copied"
SendSuccess="@sendSuccess"
OnCopy="CopyJsonToClipboard"
- OnSend="SendOrderToServer" />
+ OnSend="SendOrderToServer"
+ OnImport="OpenImportDialog" />
@@ -85,6 +86,25 @@
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
+ private async Task OpenImportDialog()
+ {
+ var dialog = await DialogService.ShowAsync(
+ "Import Order JSON",
+ new DialogOptions
+ {
+ FullWidth = true,
+ MaxWidth = MaxWidth.Large
+ });
+
+ var result = await dialog.Result;
+
+ if (!result.Canceled && result.Data is OrderMessage imported)
+ {
+ Order = imported;
+ RebuildOrderJson();
+ StateHasChanged();
+ }
+ }
private void OnOrderChanged()
{
@@ -151,7 +171,7 @@
Order.Nodes.Add(newNode);
edge.EndNodeId = newNode.NodeId;
- edge.Expanded = true;
+ edge.MarkExpanded(); // ✅
ResequenceNodes();
}
diff --git a/RobotApp.Client/Services/UiEdge.cs b/RobotApp.Client/Services/UiEdge.cs
index 3c92d67..061fe7a 100644
--- a/RobotApp.Client/Services/UiEdge.cs
+++ b/RobotApp.Client/Services/UiEdge.cs
@@ -1,5 +1,6 @@
using RobotApp.VDA5050.InstantAction;
using RobotApp.VDA5050.Order;
+using System.Text.Json;
using System.Text.Json.Serialization;
namespace RobotApp.Client.Services;
@@ -7,13 +8,37 @@ namespace RobotApp.Client.Services;
// ======================================================
// EDGE UI
// ======================================================
-public class UiEdge : VDA5050.Order.Edge
+public class UiEdge
{
- public double Radius { get; set; } = 0.0;
- public Quadrant Quadrant { get; set; } = Quadrant.I;
+ public string EdgeId { get; set; } = "";
+ public int SequenceId { get; set; }
+ public bool Released { get; set; } = true;
- // Đánh dấu đã expand chưa
- public bool Expanded { get; set; } = false;
+ public string StartNodeId { get; set; } = "";
+ public string EndNodeId { get; set; } = "";
+
+ // ===== CURVE (EDITOR GENERATED) =====
+ public double Radius { get; set; } = 0;
+ public Quadrant Quadrant { get; set; }
+
+ // ===== IMPORTED TRAJECTORY =====
+ public bool HasTrajectory { get; set; } = false;
+ public UiTrajectory? Trajectory { get; set; }
+
+ // ===== UI STATE =====
+ public bool Expanded { get; private set; } = false;
+
+ public void MarkExpanded()
+ {
+ Expanded = true;
+ }
+}
+
+public class UiTrajectory
+{
+ public int Degree { get; set; }
+ public double[] KnotVector { get; set; } = Array.Empty();
+ public List ControlPoints { get; set; } = new();
}
public enum Quadrant
@@ -138,7 +163,111 @@ public class OrderMessage
}
};
}
+ public static OrderMessage FromSchemaObject(JsonElement root)
+ {
+ var order = new OrderMessage
+ {
+ HeaderId = root.GetProperty("headerId").GetInt32(),
+ Timestamp = root.GetProperty("timestamp").GetString(),
+ Version = root.GetProperty("version").GetString(),
+ Manufacturer = root.GetProperty("manufacturer").GetString(),
+ SerialNumber = root.GetProperty("serialNumber").GetString(),
+ OrderId = root.GetProperty("orderId").GetString(),
+ OrderUpdateId = root.GetProperty("orderUpdateId").GetInt32()
+ };
+ // ================= NODES =================
+ foreach (var n in root.GetProperty("nodes").EnumerateArray())
+ {
+ var node = new Node
+ {
+ NodeId = n.GetProperty("nodeId").GetString()!,
+ SequenceId = n.GetProperty("sequenceId").GetInt32(),
+ Released = n.GetProperty("released").GetBoolean(),
+
+ NodePosition = new NodePosition
+ {
+ X = n.GetProperty("nodePosition").GetProperty("x").GetDouble(),
+ Y = n.GetProperty("nodePosition").GetProperty("y").GetDouble(),
+ Theta = n.GetProperty("nodePosition").GetProperty("theta").GetDouble(),
+ AllowedDeviationXY = n.GetProperty("nodePosition").GetProperty("allowedDeviationXY").GetDouble(),
+ AllowedDeviationTheta = n.GetProperty("nodePosition").GetProperty("allowedDeviationTheta").GetDouble(),
+ MapId = n.GetProperty("nodePosition").GetProperty("mapId").GetString()
+ },
+
+ Actions = ParseActions(n)
+ };
+
+ order.Nodes.Add(node);
+ }
+
+ foreach (var e in root.GetProperty("edges").EnumerateArray())
+ {
+ var edge = new UiEdge
+ {
+ EdgeId = e.GetProperty("edgeId").GetString()!,
+ SequenceId = e.GetProperty("sequenceId").GetInt32(),
+ Released = e.GetProperty("released").GetBoolean(),
+ StartNodeId = e.GetProperty("startNodeId").GetString()!,
+ EndNodeId = e.GetProperty("endNodeId").GetString()!,
+ };
+
+ // ===== IMPORT TRAJECTORY =====
+ if (e.TryGetProperty("trajectory", out var traj))
+ {
+ edge.HasTrajectory = true;
+ edge.Trajectory = new UiTrajectory
+ {
+ Degree = traj.GetProperty("degree").GetInt32(),
+ KnotVector = traj.GetProperty("knotVector")
+ .EnumerateArray()
+ .Select(x => x.GetDouble())
+ .ToArray(),
+
+ ControlPoints = traj.GetProperty("controlPoints")
+ .EnumerateArray()
+ .Select(p => new Point(
+ p.GetProperty("x").GetDouble(),
+ p.GetProperty("y").GetDouble()
+ ))
+ .ToList()
+ };
+
+ // 🔥 IMPORTED CURVE → LOCK APPLY
+ edge.MarkExpanded();
+ }
+
+ order.Edges.Add(edge);
+ }
+
+ return order;
+ }
+
+ // ================= ACTION PARSER =================
+ private static VDA5050.InstantAction.Action[] ParseActions(JsonElement parent)
+ {
+ if (!parent.TryGetProperty("actions", out var acts))
+ return Array.Empty();
+
+ return acts.EnumerateArray().Select(a =>
+ new VDA5050.InstantAction.Action
+ {
+ ActionId = a.GetProperty("actionId").GetString(),
+ ActionType = a.GetProperty("actionType").GetString(),
+ BlockingType = a.GetProperty("blockingType").GetString(),
+
+ ActionParameters = a.TryGetProperty("actionParameters", out var ps)
+ ? ps.EnumerateArray()
+ .Select(p => new ActionParameter
+ {
+ Key = p.GetProperty("key").GetString(),
+ Value = p.GetProperty("value").GetString()
+ })
+ .ToArray()
+ : Array.Empty()
+ }
+ ).ToArray();
+ }
public object ToSchemaObject()
{
int seq = 0;
@@ -199,7 +328,36 @@ public class OrderMessage
// ================= EDGES =================
edges = Edges.Select(e =>
{
- // ---------- ĐƯỜNG THẲNG ----------
+ // =================================================
+ // 1️⃣ IMPORTED TRAJECTORY (ƯU TIÊN CAO NHẤT)
+ // =================================================
+ if (e.HasTrajectory && e.Trajectory != null)
+ {
+ return new
+ {
+ edgeId = e.EdgeId,
+ sequenceId = seq++,
+ released = true,
+
+ startNodeId = e.StartNodeId,
+ endNodeId = e.EndNodeId,
+
+ trajectory = new
+ {
+ degree = e.Trajectory.Degree,
+ knotVector = e.Trajectory.KnotVector,
+ controlPoints = e.Trajectory.ControlPoints
+ .Select(p => new { x = p.X, y = p.Y })
+ .ToArray()
+ },
+
+ actions = Array.Empty