feat(centralui): tree rendering + lazy load + selection in OpcUaBrowserDialog
This commit is contained in:
@@ -22,7 +22,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="opcua-browser-tree">
|
<div class="opcua-browser-tree">
|
||||||
<!-- Task 16 fills this in -->
|
@if (_rootNodes.Count == 0 && _failure is null)
|
||||||
|
{
|
||||||
|
<em class="text-muted">Loading…</em>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ul class="list-unstyled mb-0">
|
||||||
|
@foreach (var node in _rootNodes)
|
||||||
|
{
|
||||||
|
<TreeRow Node="node" OnToggle="ToggleAsync" OnSelect="Select" SelectedNodeId="@_selectedNodeId" />
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
@@ -56,6 +68,28 @@
|
|||||||
private string _manualNodeId = "";
|
private string _manualNodeId = "";
|
||||||
private BrowseFailure? _failure;
|
private BrowseFailure? _failure;
|
||||||
private string _failureMessage = "";
|
private string _failureMessage = "";
|
||||||
|
private List<TreeNode> _rootNodes = new();
|
||||||
|
|
||||||
|
public sealed class TreeNode
|
||||||
|
{
|
||||||
|
public TreeNode(string nodeId, string displayName, BrowseNodeClass nodeClass, bool hasChildren)
|
||||||
|
{
|
||||||
|
NodeId = nodeId;
|
||||||
|
DisplayName = displayName;
|
||||||
|
NodeClass = nodeClass;
|
||||||
|
HasChildren = hasChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NodeId { get; }
|
||||||
|
public string DisplayName { get; }
|
||||||
|
public BrowseNodeClass NodeClass { get; }
|
||||||
|
public bool HasChildren { get; }
|
||||||
|
|
||||||
|
public List<TreeNode>? Children { get; set; } // null = not loaded yet
|
||||||
|
public bool Expanded { get; set; }
|
||||||
|
public bool Loading { get; set; }
|
||||||
|
public bool Truncated { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ShowAsync()
|
public async Task ShowAsync()
|
||||||
{
|
{
|
||||||
@@ -67,7 +101,66 @@
|
|||||||
|
|
||||||
private async Task LoadRootAsync()
|
private async Task LoadRootAsync()
|
||||||
{
|
{
|
||||||
// Task 16
|
_failure = null;
|
||||||
|
_rootNodes = new();
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
var result = await BrowseService.BrowseChildrenAsync(SiteId, DataConnectionId, parentNodeId: null);
|
||||||
|
if (result.Failure is not null)
|
||||||
|
{
|
||||||
|
SetFailure(result.Failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rootNodes = result.Children.Select(c => new TreeNode(c.NodeId, c.DisplayName, c.NodeClass, c.HasChildren)).ToList();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ToggleAsync(TreeNode node)
|
||||||
|
{
|
||||||
|
if (!node.HasChildren) return;
|
||||||
|
|
||||||
|
if (node.Expanded)
|
||||||
|
{
|
||||||
|
node.Expanded = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Children is null)
|
||||||
|
{
|
||||||
|
node.Loading = true;
|
||||||
|
StateHasChanged();
|
||||||
|
var result = await BrowseService.BrowseChildrenAsync(SiteId, DataConnectionId, node.NodeId);
|
||||||
|
node.Loading = false;
|
||||||
|
|
||||||
|
if (result.Failure is not null)
|
||||||
|
{
|
||||||
|
SetFailure(result.Failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Children = result.Children
|
||||||
|
.Select(c => new TreeNode(c.NodeId, c.DisplayName, c.NodeClass, c.HasChildren))
|
||||||
|
.ToList();
|
||||||
|
node.Truncated = result.Truncated;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Expanded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Select(TreeNode node)
|
||||||
|
{
|
||||||
|
if (node.NodeClass != BrowseNodeClass.Variable) return;
|
||||||
|
_selectedNodeId = node.NodeId;
|
||||||
|
_manualNodeId = node.NodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Task 17 will replace this body with the full BrowseFailureKind switch
|
||||||
|
// that maps each failure kind to a friendly UI message.
|
||||||
|
private void SetFailure(BrowseFailure failure)
|
||||||
|
{
|
||||||
|
_failure = failure;
|
||||||
|
_failureMessage = failure.Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task RetryRootLoad() => LoadRootAsync();
|
private Task RetryRootLoad() => LoadRootAsync();
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol
|
||||||
|
<li>
|
||||||
|
<span style="cursor: @(Node.HasChildren ? "pointer" : "default");" @onclick="() => OnToggle.InvokeAsync(Node)">
|
||||||
|
@if (Node.HasChildren)
|
||||||
|
{
|
||||||
|
<span>@(Node.Expanded ? "▼" : "▶")</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span style="display:inline-block; width:1em;"> </span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
@if (Node.NodeClass == BrowseNodeClass.Variable)
|
||||||
|
{
|
||||||
|
<a href="javascript:void(0)"
|
||||||
|
class="@(Node.NodeId == SelectedNodeId ? "fw-bold text-primary" : "")"
|
||||||
|
@onclick="() => OnSelect.InvokeAsync(Node)"
|
||||||
|
@ondblclick="() => OnSelect.InvokeAsync(Node)">
|
||||||
|
@Node.DisplayName <small class="text-muted">(@Node.NodeId)</small>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-muted">@Node.DisplayName</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Node.Loading)
|
||||||
|
{
|
||||||
|
<em class="ms-2 text-muted">loading…</em>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Node.Expanded && Node.Children is not null)
|
||||||
|
{
|
||||||
|
<ul class="list-unstyled ms-4">
|
||||||
|
@foreach (var child in Node.Children)
|
||||||
|
{
|
||||||
|
<TreeRow Node="child" OnToggle="OnToggle" OnSelect="OnSelect" SelectedNodeId="@SelectedNodeId" />
|
||||||
|
}
|
||||||
|
@if (Node.Truncated)
|
||||||
|
{
|
||||||
|
<li><small class="text-warning">Results truncated — use manual entry if your tag isn't listed.</small></li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public OpcUaBrowserDialog.TreeNode Node { get; set; } = default!;
|
||||||
|
[Parameter] public EventCallback<OpcUaBrowserDialog.TreeNode> OnToggle { get; set; }
|
||||||
|
[Parameter] public EventCallback<OpcUaBrowserDialog.TreeNode> OnSelect { get; set; }
|
||||||
|
[Parameter] public string? SelectedNodeId { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user