@* Recursive, read-only UNS browse-tree renderer. Modelled on DriverBrowseTree: per-node indent, expand chevron and Bootstrap/theme styling. This component owns no state and calls no service — it reads node.Expanded/Loading/Error/ Children and raises callbacks; the host page owns expansion + lazy loading. *@ @using ZB.MOM.WW.OtOpcUa.AdminUI.Uns @foreach (var root in Roots) { @RenderNode(root, 0) } @code { /// The top-level UNS nodes to render (typically the enterprise roots). [Parameter, EditorRequired] public IReadOnlyList Roots { get; set; } = default!; /// Raised for the primary "add child" action of a node (e.g. "+ Area" on a cluster, "+ Tag" on equipment). [Parameter] public EventCallback OnAddChild { get; set; } /// Raised for the equipment-only "+ Virtual tag" action, kept distinct from OnAddChild ("+ Tag"). [Parameter] public EventCallback OnAddVirtualTag { get; set; } /// Raised when the user edits a node (Area/Line/Equipment/Tag/VirtualTag). [Parameter] public EventCallback OnEdit { get; set; } /// Raised when the user deletes a node (Area/Line/Equipment/Tag/VirtualTag). [Parameter] public EventCallback OnDelete { get; set; } /// Raised when the user toggles a node's expansion; the host owns the resulting state + lazy load. [Parameter] public EventCallback OnToggleExpand { get; set; } /// Optional case-insensitive substring filter applied to visible children by DisplayName. [Parameter] public string? Filter { get; set; } private RenderFragment RenderNode(UnsNode node, int depth) => __builder => { var indent = $"padding-left:{depth * 18}px"; var hasExpander = node.Children.Count > 0 || node.HasLazyChildren;
@if (hasExpander) { } else { } @node.DisplayName @if (node.ChildCount > 0) { @node.ChildCount } @RenderActions(node)
@if (node.Expanded && node.Loading) {
Loading…
} else if (node.Expanded && node.Error is not null) {
@node.Error
} else if (node.Expanded) { @foreach (var child in FilterChildren(node)) { @RenderNode(child, depth + 1) } } }; private RenderFragment RenderActions(UnsNode node) => __builder => { switch (node.Kind) { case UnsNodeKind.Enterprise: // No actions on the enterprise root. break; case UnsNodeKind.Cluster: ⚙ settings break; case UnsNodeKind.Area: break; case UnsNodeKind.Line: break; case UnsNodeKind.Equipment: break; case UnsNodeKind.Tag: case UnsNodeKind.VirtualTag: break; } }; private IEnumerable FilterChildren(UnsNode node) { var f = Filter?.Trim(); if (string.IsNullOrEmpty(f)) { return node.Children; } return node.Children.Where(c => c.DisplayName.Contains(f, StringComparison.OrdinalIgnoreCase)); } }