diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/OpcUaBrowserDialog.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/OpcUaBrowserDialog.razor index 381397d1..04f0042d 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/OpcUaBrowserDialog.razor +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/OpcUaBrowserDialog.razor @@ -22,7 +22,19 @@ }
- + @if (_rootNodes.Count == 0 && _failure is null) + { + Loading… + } + else + { + + }

@@ -56,6 +68,28 @@ private string _manualNodeId = ""; private BrowseFailure? _failure; private string _failureMessage = ""; + private List _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? 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() { @@ -67,7 +101,66 @@ 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(); diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/TreeRow.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/TreeRow.razor new file mode 100644 index 00000000..5ae9038b --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/TreeRow.razor @@ -0,0 +1,53 @@ +@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol +
  • + + @if (Node.HasChildren) + { + @(Node.Expanded ? "▼" : "▶") + } + else + { +   + } + + + @if (Node.NodeClass == BrowseNodeClass.Variable) + { + + @Node.DisplayName (@Node.NodeId) + + } + else + { + @Node.DisplayName + } + + @if (Node.Loading) + { + loading… + } + + @if (Node.Expanded && Node.Children is not null) + { +
      + @foreach (var child in Node.Children) + { + + } + @if (Node.Truncated) + { +
    • Results truncated — use manual entry if your tag isn't listed.
    • + } +
    + } +
  • + +@code { + [Parameter] public OpcUaBrowserDialog.TreeNode Node { get; set; } = default!; + [Parameter] public EventCallback OnToggle { get; set; } + [Parameter] public EventCallback OnSelect { get; set; } + [Parameter] public string? SelectedNodeId { get; set; } +}