From 8e964d1dcee950bf37276c613680b06b2a183345 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Thu, 19 Oct 2023 16:01:07 +0200 Subject: [PATCH] Improve dependency tree UX: quick search not only highlights, but also filters --- src/FodyWeavers.xml | 1 + src/NuGetMonitor.csproj | 1 + .../DependencyTree/DependencyTreeControl.xaml | 36 +++++++++++----- .../DependencyTree/DependencyTreeViewModel.cs | 43 ++++++++++++++++--- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/FodyWeavers.xml b/src/FodyWeavers.xml index c371fb7..4408623 100644 --- a/src/FodyWeavers.xml +++ b/src/FodyWeavers.xml @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/src/NuGetMonitor.csproj b/src/NuGetMonitor.csproj index e35cdaa..db7dabc 100644 --- a/src/NuGetMonitor.csproj +++ b/src/NuGetMonitor.csproj @@ -24,6 +24,7 @@ + diff --git a/src/View/DependencyTree/DependencyTreeControl.xaml b/src/View/DependencyTree/DependencyTreeControl.xaml index 66d66dd..b79622a 100644 --- a/src/View/DependencyTree/DependencyTreeControl.xaml +++ b/src/View/DependencyTree/DependencyTreeControl.xaml @@ -8,9 +8,9 @@ xmlns:styles="urn:TomsToolbox.Wpf.Styles" xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" xmlns:imageCatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" - xmlns:view="clr-namespace:NuGetMonitor.View" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> + d:DesignHeight="450" d:DesignWidth="800" + TextOptions.TextFormattingMode="Display"> @@ -29,10 +29,7 @@ - + @@ -41,6 +38,23 @@ + + + + + + + + + @@ -53,12 +67,14 @@ - + + ItemsSource="{Binding TransitivePackages}" + VirtualizingStackPanel.IsVirtualizing="True" + VirtualizingStackPanel.VirtualizationMode="Recycling"> - + diff --git a/src/View/DependencyTree/DependencyTreeViewModel.cs b/src/View/DependencyTree/DependencyTreeViewModel.cs index 76fe1ef..d1a33f6 100644 --- a/src/View/DependencyTree/DependencyTreeViewModel.cs +++ b/src/View/DependencyTree/DependencyTreeViewModel.cs @@ -1,12 +1,16 @@ using Community.VisualStudio.Toolkit; using NuGetMonitor.Services; using System.ComponentModel; +using System.Windows.Data; using System.Windows.Input; using Microsoft.VisualStudio.Shell; using NuGet.Frameworks; using NuGetMonitor.Models; using TomsToolbox.Wpf; using NuGet.Packaging.Core; +using PropertyChanged; +using Throttle; +using TomsToolbox.Essentials; namespace NuGetMonitor.View.DependencyTree; @@ -59,19 +63,36 @@ private IEnumerable GetIssueItems() internal sealed partial class RootNode : INotifyPropertyChanged { private readonly TransitiveDependencies _transitiveDependencies; + private readonly ListCollectionView _children; public RootNode(TransitiveDependencies transitiveDependencies) { _transitiveDependencies = transitiveDependencies; + + var children = _transitiveDependencies.ParentsByChild + .OrderBy(item => item.Key.PackageIdentity) + .Select(item => new ChildNode(item.Key, _transitiveDependencies.ParentsByChild)) + .ToArray(); + + _children = new ListCollectionView(children); } public string ProjectName => _transitiveDependencies.ProjectName; public NuGetFramework TargetFramework => _transitiveDependencies.TargetFramework; - public IEnumerable Children => _transitiveDependencies.ParentsByChild - .OrderBy(item => item.Key.PackageIdentity) - .Select(item => new ChildNode(item.Key, _transitiveDependencies.ParentsByChild)); + public ICollectionView Children => _children; + + public void SetFilter(string? searchText) + { + if (searchText.IsNullOrWhiteSpace()) + { + _children.Filter = null; + return; + } + + _children.Filter = item => ((ChildNode)item).PackageIdentity.ToString().IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0; + } } #pragma warning disable CA1812 // Avoid uninstantiated internal classes => used in xaml! @@ -90,6 +111,15 @@ public DependencyTreeViewModel() public ICommand RefreshCommand => new DelegateCommand(Refresh); + [OnChangedMethod(nameof(OnSearchTextChanged))] + public string? SearchText { get; set; } + + [Throttled(typeof(TomsToolbox.Wpf.Throttle), 200)] + private void OnSearchTextChanged() + { + TransitivePackages?.ForEach(item => item.SetFilter(SearchText)); + } + private void Refresh() { ProjectService.ClearCache(); @@ -97,7 +127,7 @@ private void Refresh() Load().FireAndForget(); } - public async Task Load() + private async Task Load() { try { @@ -115,7 +145,10 @@ public async Task Load() TransitivePackages = transitivePackages .OrderBy(item => item.ProjectName) .ThenBy(item => item.TargetFramework.ToString()) - .Select(item => new RootNode(item)).ToArray(); + .Select(item => new RootNode(item)) + .ToArray(); + + OnSearchTextChanged(); } finally {