From 78165b3d99ce5f2c066a74f8968fbe4b730723c3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 11 May 2026 11:10:39 -0400 Subject: [PATCH] feat(ui/templates): replace flat tree model with TmplNode discriminated by kind --- .../Components/Pages/Design/Templates.razor | 94 +++++++++++++++++-- 1 file changed, 85 insertions(+), 9 deletions(-) diff --git a/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor b/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor index ed05b35..b246314 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor +++ b/src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor @@ -293,22 +293,98 @@ _loading = false; } - private record TmplTreeNode(Template Template, List Children); + private enum TmplNodeKind { Folder, Template, Composition } - private List _templateTreeRoots = new(); + private record TmplNode( + string Key, + TmplNodeKind Kind, + int EntityId, + string Label, + int? ParentFolderId, + int? OwnerTemplateId, + Template? Template, + TemplateComposition? Composition, + List Children); + + private List _treeRoots = new(); private void BuildTemplateTree() { - _templateTreeRoots = BuildTmplChildren(null); + // 1. Folder nodes keyed by id + var folderNodes = _folders.ToDictionary( + f => f.Id, + f => new TmplNode( + Key: $"f:{f.Id}", + Kind: TmplNodeKind.Folder, + EntityId: f.Id, + Label: f.Name, + ParentFolderId: f.ParentFolderId, + OwnerTemplateId: null, + Template: null, + Composition: null, + Children: new List())); + + // 2. Attach folder nodes by ParentFolderId + var roots = new List(); + foreach (var f in _folders.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase)) + { + var node = folderNodes[f.Id]; + if (f.ParentFolderId is int pid && folderNodes.TryGetValue(pid, out var parent)) + parent.Children.Add(node); + else + roots.Add(node); + } + + // 3. Template nodes with composition leaves + foreach (var t in _templates.OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase)) + { + var compChildren = t.Compositions + .OrderBy(c => c.InstanceName, StringComparer.OrdinalIgnoreCase) + .Select(c => new TmplNode( + Key: $"c:{c.Id}", + Kind: TmplNodeKind.Composition, + EntityId: c.Id, + Label: c.InstanceName, + ParentFolderId: null, + OwnerTemplateId: t.Id, + Template: null, + Composition: c, + Children: new List())) + .ToList(); + + var tNode = new TmplNode( + Key: $"t:{t.Id}", + Kind: TmplNodeKind.Template, + EntityId: t.Id, + Label: t.Name, + ParentFolderId: t.FolderId, + OwnerTemplateId: null, + Template: t, + Composition: null, + Children: compChildren); + + if (t.FolderId is int fid && folderNodes.TryGetValue(fid, out var parentFolder)) + parentFolder.Children.Add(tNode); + else + roots.Add(tNode); + } + + // 4. Sort each level: folders before templates, alphabetical + SortChildren(roots); + foreach (var node in folderNodes.Values) + SortChildren(node.Children); + + _treeRoots = roots; } - private List BuildTmplChildren(int? parentId) + private static void SortChildren(List children) { - return _templates - .Where(t => t.ParentTemplateId == parentId) - .OrderBy(t => t.Name) - .Select(t => new TmplTreeNode(t, BuildTmplChildren(t.Id))) - .ToList(); + children.Sort((a, b) => + { + var kindOrder = (int)a.Kind - (int)b.Kind; + if (kindOrder != 0) return kindOrder; + return string.Compare(a.Label, b.Label, StringComparison.OrdinalIgnoreCase); + }); } private async Task SelectTemplate(int templateId)