123 lines
4.6 KiB
C#
123 lines
4.6 KiB
C#
using ZB.MOM.WW.GalaxyRepository.Grpc;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
|
|
/// <summary>
|
|
/// One node in the dashboard Browse hierarchy tree. Wraps a Galaxy object
|
|
/// and its child objects; the object's attributes are the leaf "tags" the
|
|
/// operator can right-click and add to the subscription panel.
|
|
/// </summary>
|
|
public sealed class DashboardBrowseNode
|
|
{
|
|
/// <summary>The underlying Galaxy object for this node.</summary>
|
|
public required GalaxyObject Object { get; init; }
|
|
|
|
/// <summary>Child objects contained by this object, sorted areas-first then by name.</summary>
|
|
public List<DashboardBrowseNode> Children { get; } = [];
|
|
|
|
/// <summary>The label shown for this node in the tree.</summary>
|
|
public string DisplayName =>
|
|
!string.IsNullOrWhiteSpace(Object.BrowseName) ? Object.BrowseName
|
|
: !string.IsNullOrWhiteSpace(Object.ContainedName) ? Object.ContainedName
|
|
: Object.TagName;
|
|
|
|
/// <summary>True when this node is a Galaxy area rather than an instance object.</summary>
|
|
public bool IsArea => Object.IsArea;
|
|
|
|
/// <summary>The object's attributes — the browsable tags.</summary>
|
|
public IReadOnlyList<GalaxyAttribute> Attributes => Object.Attributes;
|
|
|
|
/// <summary>True when the node has child objects or attributes to expand.</summary>
|
|
public bool HasChildren => Children.Count > 0 || Object.Attributes.Count > 0;
|
|
|
|
/// <summary>Whether this node has at least one matching descendant, per the
|
|
/// server's <c>child_has_children</c> projector hint. Controls whether the UI
|
|
/// shows an expand triangle before children have actually loaded.</summary>
|
|
public bool HasChildrenHint { get; init; }
|
|
|
|
/// <summary>The lazy-load state for this node's children.</summary>
|
|
public BrowseLoadState LoadState { get; set; } = BrowseLoadState.NotLoaded;
|
|
|
|
/// <summary>Short error string if the last load attempt failed; null otherwise.</summary>
|
|
public string? LoadError { get; set; }
|
|
}
|
|
|
|
/// <summary>Lazy-load lifecycle of a browse node's children.</summary>
|
|
public enum BrowseLoadState
|
|
{
|
|
/// <summary>Children have not been requested yet.</summary>
|
|
NotLoaded,
|
|
|
|
/// <summary>A load is in progress.</summary>
|
|
Loading,
|
|
|
|
/// <summary>Children have been loaded into <see cref="DashboardBrowseNode.Children"/>.</summary>
|
|
Loaded,
|
|
|
|
/// <summary>The last load attempt failed; see <see cref="DashboardBrowseNode.LoadError"/>.</summary>
|
|
Error,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the dashboard Browse tree from the flat Galaxy object list held
|
|
/// by <c>IGalaxyHierarchyCache</c>. Pure and side-effect free so the
|
|
/// parent/child linkage and ordering rules are unit-testable.
|
|
/// </summary>
|
|
public static class DashboardBrowseTreeBuilder
|
|
{
|
|
/// <summary>Builds the root nodes of the Browse tree.</summary>
|
|
/// <param name="objects">The flat Galaxy object list.</param>
|
|
/// <returns>The root nodes, sorted areas-first then alphabetically.</returns>
|
|
public static IReadOnlyList<DashboardBrowseNode> Build(IReadOnlyList<GalaxyObject> objects)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(objects);
|
|
|
|
Dictionary<int, DashboardBrowseNode> nodes = new(objects.Count);
|
|
foreach (GalaxyObject galaxyObject in objects)
|
|
{
|
|
// Last write wins on a duplicate gobject id — Galaxy ids are unique
|
|
// in practice, but guard so the dictionary build never throws.
|
|
nodes[galaxyObject.GobjectId] = new DashboardBrowseNode { Object = galaxyObject };
|
|
}
|
|
|
|
List<DashboardBrowseNode> roots = [];
|
|
foreach (DashboardBrowseNode node in nodes.Values)
|
|
{
|
|
int parentId = node.Object.ParentGobjectId;
|
|
if (parentId != 0
|
|
&& parentId != node.Object.GobjectId
|
|
&& nodes.TryGetValue(parentId, out DashboardBrowseNode? parent))
|
|
{
|
|
parent.Children.Add(node);
|
|
}
|
|
else
|
|
{
|
|
roots.Add(node);
|
|
}
|
|
}
|
|
|
|
SortRecursive(roots);
|
|
return roots;
|
|
}
|
|
|
|
private static void SortRecursive(List<DashboardBrowseNode> nodes)
|
|
{
|
|
nodes.Sort(CompareNodes);
|
|
foreach (DashboardBrowseNode node in nodes)
|
|
{
|
|
SortRecursive(node.Children);
|
|
}
|
|
}
|
|
|
|
// Areas sort before instance objects; within a group, by display name.
|
|
private static int CompareNodes(DashboardBrowseNode left, DashboardBrowseNode right)
|
|
{
|
|
if (left.IsArea != right.IsArea)
|
|
{
|
|
return left.IsArea ? -1 : 1;
|
|
}
|
|
|
|
return string.Compare(left.DisplayName, right.DisplayName, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|