96bea1d478
Restyles the Blazor dashboard onto a portable token-based theme so it reads like an instrument panel: warm-paper background, hairline-ruled panels, IBM Plex type, monospace tabular numerics, and status carried by colour chips. Vendors theme.css + IBM Plex fonts, rewrites dashboard.css as a thin token-driven view layer, and swaps the Bootstrap navbar and status badges for the design-system app bar and chips. Also includes pending API-key management, Galaxy hierarchy projection, and constraint-enforcement work with their tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
107 lines
3.4 KiB
C#
107 lines
3.4 KiB
C#
using MxGateway.Contracts.Proto.Galaxy;
|
|
|
|
namespace MxGateway.Server.Galaxy;
|
|
|
|
public sealed class GalaxyHierarchyIndex
|
|
{
|
|
private GalaxyHierarchyIndex(
|
|
IReadOnlyList<GalaxyObjectView> objectViews,
|
|
IReadOnlyDictionary<int, GalaxyObjectView> objectViewsById,
|
|
IReadOnlyDictionary<string, GalaxyTagLookup> tagsByAddress)
|
|
{
|
|
ObjectViews = objectViews;
|
|
ObjectViewsById = objectViewsById;
|
|
TagsByAddress = tagsByAddress;
|
|
}
|
|
|
|
public static GalaxyHierarchyIndex Empty { get; } = new(
|
|
Array.Empty<GalaxyObjectView>(),
|
|
new Dictionary<int, GalaxyObjectView>(),
|
|
new Dictionary<string, GalaxyTagLookup>(StringComparer.OrdinalIgnoreCase));
|
|
|
|
public IReadOnlyList<GalaxyObjectView> ObjectViews { get; }
|
|
|
|
public IReadOnlyDictionary<int, GalaxyObjectView> ObjectViewsById { get; }
|
|
|
|
public IReadOnlyDictionary<string, GalaxyTagLookup> TagsByAddress { get; }
|
|
|
|
public static GalaxyHierarchyIndex Build(IReadOnlyList<GalaxyObject> objects)
|
|
{
|
|
if (objects.Count == 0)
|
|
{
|
|
return Empty;
|
|
}
|
|
|
|
Dictionary<int, GalaxyObject> objectsById = new();
|
|
foreach (GalaxyObject obj in objects)
|
|
{
|
|
objectsById.TryAdd(obj.GobjectId, obj);
|
|
}
|
|
|
|
List<GalaxyObjectView> views = new(objects.Count);
|
|
Dictionary<int, GalaxyObjectView> viewsById = new();
|
|
Dictionary<string, GalaxyTagLookup> tagsByAddress = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (GalaxyObject obj in objects)
|
|
{
|
|
string path = BuildContainedPath(obj, objectsById);
|
|
int depth = string.IsNullOrWhiteSpace(path) ? 0 : path.Count(character => character == '/');
|
|
GalaxyObjectView view = new(obj, path, depth);
|
|
views.Add(view);
|
|
viewsById.TryAdd(obj.GobjectId, view);
|
|
|
|
if (!string.IsNullOrWhiteSpace(obj.TagName))
|
|
{
|
|
tagsByAddress.TryAdd(obj.TagName, new GalaxyTagLookup(obj, Attribute: null, path));
|
|
}
|
|
|
|
foreach (GalaxyAttribute attribute in obj.Attributes)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(attribute.FullTagReference))
|
|
{
|
|
tagsByAddress.TryAdd(attribute.FullTagReference, new GalaxyTagLookup(obj, attribute, path));
|
|
}
|
|
}
|
|
}
|
|
|
|
return new GalaxyHierarchyIndex(
|
|
views,
|
|
viewsById,
|
|
tagsByAddress);
|
|
}
|
|
|
|
private static string BuildContainedPath(
|
|
GalaxyObject obj,
|
|
IReadOnlyDictionary<int, GalaxyObject> objectsById)
|
|
{
|
|
Stack<string> names = new();
|
|
HashSet<int> seen = [];
|
|
GalaxyObject? current = obj;
|
|
while (current is not null && seen.Add(current.GobjectId))
|
|
{
|
|
names.Push(ResolvePathSegment(current));
|
|
current = current.ParentGobjectId != 0
|
|
&& objectsById.TryGetValue(current.ParentGobjectId, out GalaxyObject? parent)
|
|
? parent
|
|
: null;
|
|
}
|
|
|
|
return string.Join('/', names.Where(name => !string.IsNullOrWhiteSpace(name)));
|
|
}
|
|
|
|
private static string ResolvePathSegment(GalaxyObject obj)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(obj.ContainedName))
|
|
{
|
|
return obj.ContainedName;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(obj.BrowseName))
|
|
{
|
|
return obj.BrowseName;
|
|
}
|
|
|
|
return obj.TagName;
|
|
}
|
|
}
|