Skip to content

Commit

Permalink
Improve dependency tree UX: quick search not only highlights, but als…
Browse files Browse the repository at this point in the history
…o filters
  • Loading branch information
tom-englert committed Oct 19, 2023
1 parent 19e40d1 commit 8e964d1
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Throttle />
<PropertyChanged />
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>
1 change: 1 addition & 0 deletions src/NuGetMonitor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.7.2196" PrivateAssets="all" />
<PackageReference Include="NuGet.Protocol" Version="6.7.0" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="all" />
<PackageReference Include="Throttle.Fody" Version="1.7.0" PrivateAssets="all" />
<PackageReference Include="TomsToolbox.Wpf.Styles" Version="2.9.0" />
<PackageReference Include="VSIX-SdkProjectAdapter" Version="3.0.0" PrivateAssets="all" />
</ItemGroup>
Expand Down
36 changes: 26 additions & 10 deletions src/View/DependencyTree/DependencyTreeControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<UserControl.DataContext>
<local:DependencyTreeViewModel />
</UserControl.DataContext>
Expand All @@ -29,10 +29,7 @@
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal" Margin="{StaticResource NodeMargin}">
<toms:HighlightingTextBlock x:Name="Identity"
Text="{Binding PackageIdentity}"
SearchText="{Binding ElementName=SearchText, Path=Text}"
HighLightBrush="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<TextBlock x:Name="Identity" Text="{Binding PackageIdentity}" />
<TextBlock Text="{Binding Issues}" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
Expand All @@ -41,6 +38,23 @@
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="FirstLevelNodeTemplate"
DataType="{x:Type local:ChildNode}"
ItemsSource="{Binding Children}"
ItemTemplate="{StaticResource NodeTemplate}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal" Margin="{StaticResource NodeMargin}">
<toms:HighlightingTextBlock x:Name="Identity"
Text="{Binding PackageIdentity}"
SearchText="{Binding ElementName=SearchText, Path=Text}"
HighLightBrush="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<TextBlock Text="{Binding Issues}" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" />
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid FocusManager.FocusedElement="{Binding ElementName=TreeView}">
Expand All @@ -53,12 +67,14 @@
<Separator />
<StackPanel Orientation="Horizontal">
<imaging:CrispImage Width="16" Height="16" Moniker="{x:Static imageCatalog:KnownMonikers.QuickFind}" Margin="4,0" />
<TextBox x:Name="SearchText" Width="200" Style="{DynamicResource {x:Static ToolBar.TextBoxStyleKey}}" />
<TextBox x:Name="SearchText" Width="200" Style="{DynamicResource {x:Static ToolBar.TextBoxStyleKey}}" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</ToolBar>
<TreeView x:Name="TreeView"
BorderThickness="0 1 0 0"
ItemsSource="{Binding TransitivePackages}">
ItemsSource="{Binding TransitivePackages}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
Expand All @@ -67,13 +83,13 @@
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:RootNode}"
ItemsSource="{Binding Children}"
ItemTemplate="{StaticResource NodeTemplate}">
ItemTemplate="{StaticResource FirstLevelNodeTemplate}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="False" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<TextBlock Margin="{StaticResource NodeMargin}" FontSize="14">
<TextBlock Margin="{StaticResource NodeMargin}" FontWeight="Bold">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} [{1}]">
<MultiBinding.Bindings>
Expand Down
43 changes: 38 additions & 5 deletions src/View/DependencyTree/DependencyTreeViewModel.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -59,19 +63,36 @@ private IEnumerable<string> 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<ChildNode> 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!
Expand All @@ -90,14 +111,23 @@ 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();

Load().FireAndForget();
}

public async Task Load()
private async Task Load()
{
try
{
Expand All @@ -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
{
Expand Down

0 comments on commit 8e964d1

Please sign in to comment.