/ src / modules / cmdpal / Microsoft.CmdPal.UI.ViewModels / SettingsExtensionsViewModel.cs
SettingsExtensionsViewModel.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System.Collections.ObjectModel;
  6  using System.Collections.Specialized;
  7  using System.Globalization;
  8  using System.Text;
  9  using CommunityToolkit.Mvvm.ComponentModel;
 10  using CommunityToolkit.Mvvm.Input;
 11  using CommunityToolkit.Mvvm.Messaging;
 12  
 13  using Microsoft.CmdPal.Core.ViewModels.Messages;
 14  using Microsoft.CmdPal.UI.ViewModels.Messages;
 15  using Microsoft.CommandPalette.Extensions.Toolkit;
 16  
 17  namespace Microsoft.CmdPal.UI.ViewModels;
 18  
 19  /// <summary>
 20  /// Provides filtering over the list of provider settings view models.
 21  /// Intended to be used by the UI to bind a TextBox (SearchText) and an ItemsRepeater (FilteredProviders).
 22  /// </summary>
 23  public partial class SettingsExtensionsViewModel : ObservableObject
 24  {
 25      private static readonly CompositeFormat LabelNumberExtensionFound
 26          = CompositeFormat.Parse(Properties.Resources.builtin_settings_extension_n_extensions_found!);
 27  
 28      private static readonly CompositeFormat LabelNumberExtensionInstalled
 29          = CompositeFormat.Parse(Properties.Resources.builtin_settings_extension_n_extensions_installed!);
 30  
 31      private readonly ObservableCollection<ProviderSettingsViewModel> _source;
 32      private readonly TaskScheduler _uiScheduler;
 33  
 34      public ObservableCollection<ProviderSettingsViewModel> FilteredProviders { get; } = [];
 35  
 36      private string _searchText = string.Empty;
 37  
 38      public string SearchText
 39      {
 40          get => _searchText;
 41          set
 42          {
 43              if (_searchText != value)
 44              {
 45                  _searchText = value;
 46                  OnPropertyChanged();
 47                  ApplyFilter();
 48              }
 49          }
 50      }
 51  
 52      public string ItemCounterText
 53      {
 54          get
 55          {
 56              var hasQuery = !string.IsNullOrWhiteSpace(_searchText);
 57              var count = hasQuery ? FilteredProviders.Count : _source.Count;
 58              var format = hasQuery ? LabelNumberExtensionFound : LabelNumberExtensionInstalled;
 59              return string.Format(CultureInfo.CurrentCulture, format, count);
 60          }
 61      }
 62  
 63      public bool ShowManualReloadOverlay
 64      {
 65          get;
 66          private set
 67          {
 68              if (field != value)
 69              {
 70                  field = value;
 71                  OnPropertyChanged();
 72              }
 73          }
 74      }
 75  
 76      public bool ShowNoResultsPanel => !string.IsNullOrWhiteSpace(_searchText) && FilteredProviders.Count == 0;
 77  
 78      public bool HasResults => !ShowNoResultsPanel;
 79  
 80      public IRelayCommand ReloadExtensionsCommand { get; }
 81  
 82      public SettingsExtensionsViewModel(ObservableCollection<ProviderSettingsViewModel> source, TaskScheduler uiScheduler)
 83      {
 84          _source = source;
 85          _uiScheduler = uiScheduler;
 86          _source.CollectionChanged += Source_CollectionChanged;
 87          ApplyFilter();
 88  
 89          ReloadExtensionsCommand = new RelayCommand(ReloadExtensions);
 90  
 91          WeakReferenceMessenger.Default.Register<ReloadFinishedMessage>(this, (_, _) =>
 92          {
 93              Task.Factory.StartNew(() => ShowManualReloadOverlay = false, CancellationToken.None, TaskCreationOptions.None, _uiScheduler);
 94          });
 95      }
 96  
 97      private void Source_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
 98      {
 99          ApplyFilter();
100      }
101  
102      private void ApplyFilter()
103      {
104          var query = _searchText;
105          var filtered = ListHelpers.FilterList(_source, query, Matches);
106          ListHelpers.InPlaceUpdateList(FilteredProviders, filtered);
107          OnPropertyChanged(nameof(ItemCounterText));
108          OnPropertyChanged(nameof(HasResults));
109          OnPropertyChanged(nameof(ShowNoResultsPanel));
110      }
111  
112      private static int Matches(string query, ProviderSettingsViewModel item)
113      {
114          if (string.IsNullOrWhiteSpace(query))
115          {
116              return 100;
117          }
118  
119          return Contains(item.DisplayName, query)
120                 || Contains(item.ExtensionName, query)
121                 || Contains(item.ExtensionSubtext, query)
122              ? 100
123              : 0;
124      }
125  
126      private static bool Contains(string? haystack, string needle)
127      {
128          return !string.IsNullOrEmpty(haystack) && haystack.Contains(needle, StringComparison.OrdinalIgnoreCase);
129      }
130  
131      [RelayCommand]
132      private void OpenStoreWithExtension(string? query)
133      {
134          const string extensionsAssocUri = "ms-windows-store://assoc/?Tags=AppExtension-com.microsoft.commandpalette";
135          ShellHelpers.OpenInShell(extensionsAssocUri);
136      }
137  
138      private void ReloadExtensions()
139      {
140          ShowManualReloadOverlay = true;
141          WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
142          WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>();
143      }
144  }