diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TemplateFolderTree.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TemplateFolderTree.razor index f63db15e..877384d7 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TemplateFolderTree.razor +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TemplateFolderTree.razor @@ -67,6 +67,12 @@ [Parameter] public HashSet? SelectedKeys { get; set; } [Parameter] public EventCallback> SelectedKeysChanged { get; set; } [Parameter] public string Filter { get; set; } = ""; + /// + /// Keys (f:{id}/t:{id}) whose ancestor folders should start expanded + /// when no is active — the picker uses it to open on a + /// preselected template. An active filter's own reveal set takes precedence. + /// + [Parameter] public IReadOnlyCollection? RevealKeys { get; set; } [Parameter] public RenderFragment? NodeExtras { get; set; } [Parameter] public RenderFragment? NodeContent { get; set; } [Parameter] public RenderFragment? ContextMenu { get; set; } @@ -165,7 +171,7 @@ { _visibleRoots = _allRoots; _filterRevealed = null; - _initiallyExpanded = null; + _initiallyExpanded = BuildRevealPredicate(); return; } @@ -185,6 +191,29 @@ _initiallyExpanded = n => revealed.Contains(n.Key); } + /// + /// Builds the no-filter expansion predicate from : every + /// ancestor of a wanted key (the key's own node stays as-is). Null when nothing + /// was asked for, which keeps existing callers on the previous behaviour. + /// + private Func? BuildRevealPredicate() + { + if (RevealKeys is not { Count: > 0 }) return null; + var wanted = new HashSet(RevealKeys, StringComparer.Ordinal); + var ancestors = new HashSet(StringComparer.Ordinal); + var path = new List(); + foreach (var root in _allRoots) CollectAncestors(root, wanted, ancestors, path); + return ancestors.Count == 0 ? null : n => ancestors.Contains(n.Key); + } + + private static void CollectAncestors(TemplateTreeNode node, HashSet wanted, HashSet acc, List path) + { + if (wanted.Contains(node.Key)) acc.UnionWith(path); + path.Add(node.Key); + foreach (var c in node.Children) CollectAncestors(c, wanted, acc, path); + path.RemoveAt(path.Count - 1); + } + private static TemplateTreeNode? CopyMatching(TemplateTreeNode node, string needle, HashSet revealed) { var selfMatch = node.Name.Contains(needle, StringComparison.OrdinalIgnoreCase); diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/TemplateFolderTreeRevealKeysTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/TemplateFolderTreeRevealKeysTests.cs new file mode 100644 index 00000000..fd66cd54 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/TemplateFolderTreeRevealKeysTests.cs @@ -0,0 +1,59 @@ +using Bunit; +using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared; +using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; + +namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared; + +/// +/// Covers the additive RevealKeys parameter: the tree opens the ancestors of +/// a caller-named node when no filter is active, and an active filter still wins. +/// +public class TemplateFolderTreeRevealKeysTests : BunitContext +{ + public TemplateFolderTreeRevealKeysTests() + { + JSInterop.Mode = JSRuntimeMode.Loose; + } + + private static (List folders, List