337 lines
11 KiB
Plaintext
337 lines
11 KiB
Plaintext
@page "/robot-order"
|
|
|
|
@rendermode InteractiveWebAssemblyNoPrerender
|
|
|
|
@using System.Text.Json
|
|
@using System.Text.Json.Serialization
|
|
|
|
@inject IJSRuntime JS
|
|
@inject IDialogService DialogService
|
|
@inject HttpClient Http
|
|
@inject ISnackbar Snackbar
|
|
|
|
<MudMainContent Class="pa-0 ma-0">
|
|
<div style="height:100vh; overflow:hidden;">
|
|
<MudContainer MaxWidth="MaxWidth.False"
|
|
Class="pa-4"
|
|
Style="max-width:100%; height:100%; display:flex; flex-direction:column;">
|
|
<MudGrid Spacing="4" Class="flex-grow-1" Style="overflow:hidden;">
|
|
|
|
<!-- ================= LEFT ================= -->
|
|
<MudItem xs="12" md="7" Class="d-flex flex-column h-100" Style="gap:16px;">
|
|
<MudGrid Spacing="4" Class="flex-grow-1" Style="overflow:hidden;">
|
|
|
|
<MudItem xs="12" md="6" Class="h-100">
|
|
<NodesPanel Order="Order"
|
|
OnAddNode="AddNode"
|
|
OnRemoveNode="RemoveNode"
|
|
OnEditNode="OpenEditNodeDialog"
|
|
OnAddAction="AddAction"
|
|
OnRemoveAction="@(w => RemoveAction(w.Node, w.Action))"
|
|
OnAddActionParameter="AddActionParameter"
|
|
OnRemoveActionParameter="@(w => RemoveActionParameter(w.Action, w.Parameter))"
|
|
OnOrderChanged="OnOrderChanged" />
|
|
</MudItem>
|
|
|
|
<MudItem xs="12" md="6" Class="h-100">
|
|
<EdgesPanel Order="Order"
|
|
OnAddEdge="AddEdge"
|
|
OnRemoveEdge="RemoveEdge"
|
|
OnOrderChanged="OnOrderChanged" />
|
|
</MudItem>
|
|
|
|
</MudGrid>
|
|
</MudItem>
|
|
|
|
<!-- ================= RIGHT ================= -->
|
|
<MudItem xs="12" md="5" Class="h-100">
|
|
<JsonOutputPanel @bind-OrderJson="@OrderJson"
|
|
Copied="@copied"
|
|
SendSuccess="@sendSuccess"
|
|
CancelSuccess="@cancelSuccess"
|
|
OnCopy="CopyJsonToClipboard"
|
|
OnSend="SendOrderToServer"
|
|
OnImport="OpenImportDialog"
|
|
OnCancel="CancelOrder" />
|
|
</MudItem>
|
|
|
|
</MudGrid>
|
|
</MudContainer>
|
|
</div>
|
|
</MudMainContent>
|
|
|
|
@code {
|
|
// ================= STATE =================
|
|
private OrderMessage Order { get; set; } = new();
|
|
private string OrderJson = ""; // 🔥 CACHE JSON (QUAN TRỌNG)
|
|
private bool copied;
|
|
private bool? sendSuccess;
|
|
private bool? cancelSuccess;
|
|
private CancellationTokenSource? _copyCts;
|
|
|
|
// ================= INIT =================
|
|
protected override void OnInitialized()
|
|
{
|
|
RebuildOrderJson();
|
|
}
|
|
|
|
// ================= CORE FIX =================
|
|
private void RebuildOrderJson()
|
|
{
|
|
OrderJson = JsonSerializer.Serialize(
|
|
Order.ToSchemaObject(),
|
|
new JsonSerializerOptions
|
|
{
|
|
WriteIndented = true,
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
});
|
|
}
|
|
private async Task OpenImportDialog()
|
|
{
|
|
var dialog = await DialogService.ShowAsync<ImportOrderDialog>(
|
|
"Import Order JSON",
|
|
new DialogOptions
|
|
{
|
|
FullWidth = true,
|
|
MaxWidth = MaxWidth.Large
|
|
});
|
|
|
|
var result = await dialog.Result;
|
|
|
|
if (result is not null && !result.Canceled && result.Data is OrderMsg imported)
|
|
{
|
|
Order.Import(imported);
|
|
RebuildOrderJson();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private void OnOrderChanged()
|
|
{
|
|
RebuildOrderJson(); // 🔥 JSON luôn rebuild
|
|
StateHasChanged(); // 🔥 ép render
|
|
}
|
|
|
|
// ================= NODE =================
|
|
void AddNode()
|
|
{
|
|
Order.Nodes.Add(new Node
|
|
{
|
|
NodeId = $"NODE_{Order.Nodes.Count + 1}",
|
|
SequenceId = Order.Nodes.Count,
|
|
Released = true,
|
|
NodePosition = new VDA5050.Order.NodePosition { MapId = "MAP_01" }
|
|
});
|
|
}
|
|
|
|
void RemoveNode(Node node)
|
|
{
|
|
Order.Nodes.Remove(node);
|
|
Order.Edges.RemoveAll(e => e.StartNodeId == node.NodeId || e.EndNodeId == node.NodeId);
|
|
ResequenceNodes();
|
|
}
|
|
|
|
void ResequenceNodes()
|
|
{
|
|
for (int i = 0; i < Order.Nodes.Count; i++)
|
|
Order.Nodes[i].SequenceId = i;
|
|
}
|
|
|
|
// ================= EDGE =================
|
|
void AddEdge()
|
|
{
|
|
if (Order.Nodes.Count == 0)
|
|
return;
|
|
|
|
var start = Order.Nodes[0].NodeId;
|
|
var end = Order.Nodes.Count > 1
|
|
? Order.Nodes[1].NodeId
|
|
: start; // 👈 1 node thì start = end
|
|
|
|
Order.Edges.Add(new VDA5050.Order.Edge
|
|
{
|
|
EdgeId = $"EDGE_{Order.Edges.Count + 1}",
|
|
StartNodeId = start,
|
|
EndNodeId = end
|
|
});
|
|
}
|
|
|
|
|
|
void RemoveEdge(VDA5050.Order.Edge edge)
|
|
{
|
|
Order.Edges.Remove(edge);
|
|
}
|
|
// ================= ACTION =================
|
|
void AddAction(Node node)
|
|
{
|
|
var list = node.Actions?.ToList() ?? new();
|
|
list.Add(new VDA5050.InstantAction.Action
|
|
{
|
|
ActionId = Guid.NewGuid().ToString(),
|
|
ActionType = ActionType.startPause.ToString(),
|
|
BlockingType = "NONE",
|
|
ActionParameters = Array.Empty<ActionParameter>()
|
|
});
|
|
node.Actions = list.ToArray();
|
|
}
|
|
|
|
void RemoveAction(Node node, VDA5050.InstantAction.Action action)
|
|
{
|
|
node.Actions = node.Actions?.Where(a => a != action).ToArray()
|
|
?? Array.Empty<VDA5050.InstantAction.Action>();
|
|
}
|
|
|
|
void AddActionParameter(VDA5050.InstantAction.Action act)
|
|
{
|
|
var list = (act.ActionParameters ?? Array.Empty<ActionParameter>()).ToList();
|
|
list.Add(new UiActionParameter());
|
|
act.ActionParameters = list.ToArray();
|
|
}
|
|
|
|
void RemoveActionParameter(VDA5050.InstantAction.Action act, ActionParameter param)
|
|
{
|
|
act.ActionParameters =
|
|
act.ActionParameters?.Where(p => p != param).ToArray()
|
|
?? Array.Empty<ActionParameter>();
|
|
}
|
|
|
|
// ================= SEND / COPY =================
|
|
async Task SendOrderToServer()
|
|
{
|
|
// reset trạng thái trước khi gửi
|
|
sendSuccess = null;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
var orderMsg = JsonSerializer.Deserialize<OrderMsg>(OrderJson,
|
|
new JsonSerializerOptions
|
|
{
|
|
WriteIndented = true,
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
});
|
|
if (orderMsg is null)
|
|
{
|
|
Snackbar.Add("Unable to convert JSON to Order", Severity.Warning);
|
|
return;
|
|
}
|
|
if (orderMsg.Nodes.Length < 1)
|
|
{
|
|
Snackbar.Add("The order must contain at least one node (number of nodes > 0)", Severity.Warning);
|
|
return;
|
|
}
|
|
if (orderMsg.Nodes.Length - 1 != orderMsg.Edges.Length)
|
|
{
|
|
Snackbar.Add("Order must have a number of edges equal to the number of nodes minus 1", Severity.Warning);
|
|
return;
|
|
}
|
|
|
|
foreach(var edge in orderMsg.Edges)
|
|
{
|
|
if (!orderMsg.Nodes.Any(n => n.NodeId == edge.StartNodeId))
|
|
{
|
|
Snackbar.Add($"The edge {edge.EdgeId} references a startNodeId {edge.StartNodeId} that does not exist in the list of nodes", Severity.Warning);
|
|
return;
|
|
}
|
|
if (!orderMsg.Nodes.Any(n => n.NodeId == edge.EndNodeId))
|
|
{
|
|
Snackbar.Add($"The edge {edge.EdgeId} references a startNodeId {edge.EndNodeId} that does not exist in the list of nodes", Severity.Warning);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var response = await Http.PostAsJsonAsync("/api/order",orderMsg);
|
|
|
|
sendSuccess = response.IsSuccessStatusCode;
|
|
}
|
|
catch(JsonException jsonEx)
|
|
{
|
|
Snackbar.Add($"Json to Order failed: {jsonEx.Message}", Severity.Warning);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Send Order failed: {ex.Message}", Severity.Warning);
|
|
sendSuccess = false;
|
|
}
|
|
|
|
StateHasChanged();
|
|
|
|
// 🔥 AUTO RESET SAU 2 GIÂY
|
|
_ = Task.Run(async () =>
|
|
{
|
|
await Task.Delay(2000);
|
|
|
|
// quay về trạng thái Send
|
|
sendSuccess = null;
|
|
|
|
await InvokeAsync(StateHasChanged);
|
|
});
|
|
}
|
|
|
|
async Task CancelOrder()
|
|
{
|
|
// reset trạng thái trước khi gửi
|
|
cancelSuccess = null;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
var res = await Http.PostAsync("/api/order/cancel", null);
|
|
cancelSuccess = res.IsSuccessStatusCode;
|
|
}
|
|
catch
|
|
{
|
|
cancelSuccess = false;
|
|
}
|
|
|
|
StateHasChanged();
|
|
|
|
// 🔥 AUTO RESET SAU 2 GIÂY
|
|
_ = Task.Run(async () =>
|
|
{
|
|
await Task.Delay(2000);
|
|
cancelSuccess = null;
|
|
await InvokeAsync(StateHasChanged);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
async Task CopyJsonToClipboard()
|
|
{
|
|
_copyCts?.Cancel();
|
|
_copyCts = new();
|
|
|
|
await JS.InvokeVoidAsync("navigator.clipboard.writeText", OrderJson);
|
|
|
|
copied = true;
|
|
StateHasChanged();
|
|
|
|
try { await Task.Delay(1500, _copyCts.Token); } catch { }
|
|
copied = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
// ================= DIALOG =================
|
|
async Task OpenEditNodeDialog(Node node)
|
|
{
|
|
var parameters = new DialogParameters<EditNodeDialog>
|
|
{
|
|
{ x => x.Node, node }
|
|
};
|
|
|
|
var options = new DialogOptions
|
|
{
|
|
CloseButton = true,
|
|
FullWidth = true,
|
|
MaxWidth = MaxWidth.Large
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<EditNodeDialog>(
|
|
$"Edit Node: {node.NodeId}", parameters, options);
|
|
|
|
await dialog.Result;
|
|
OnOrderChanged(); // 🔥 cập nhật JSON sau dialog
|
|
}
|
|
}
|