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 }