/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs
281 строка
10 KB
Michael Jolley
CmdPal: Refactor TopLevelCommandManager to use IExtensionService implementations (#48417)
23 июн 2026, 00:51
Не верифицирован
23 июн 2026, 00:51
cd7465e
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Common.Services; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Properties; using Microsoft.CmdPal.UI.ViewModels.Services; namespace Microsoft.CmdPal.UI.ViewModels; public partial class ProviderSettingsViewModel : ObservableObject { private static readonly IconInfoViewModel EmptyIcon = new(null); private static readonly CompositeFormat ExtensionSubtextFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext); private static readonly CompositeFormat ExtensionSubtextSingularFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_singular); private static readonly CompositeFormat ExtensionSubtextWithFallbackFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_with_fallback); private static readonly CompositeFormat ExtensionSubtextWithFallbackSingularCommandFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_with_fallback_singular_command); private static readonly CompositeFormat ExtensionSubtextWithFallbackSingularFallbackFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_with_fallback_singular_fallback); private static readonly CompositeFormat ExtensionSubtextWithFallbackSingularBothFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_with_fallback_singular_both); private static readonly CompositeFormat ExtensionSubtextDisabledFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_disabled); private readonly CommandProviderWrapper _provider; private readonly ISettingsService _settingsService; private readonly Lock _initializeSettingsLock = new(); private ProviderSettings _providerSettings; private Task? _initializeSettingsTask; public ProviderSettingsViewModel( CommandProviderWrapper provider, ProviderSettings providerSettings, ISettingsService settingsService) { _provider = provider; _providerSettings = providerSettings; _settingsService = settingsService; LoadingSettings = _provider.Settings?.HasSettings ?? false; BuildFallbackViewModels(); } public string DisplayName => _provider.DisplayName; /// <summary> /// Stable, non-localized identifier from the underlying provider (e.g. /// "com.microsoft.cmdpal.builtin.calculator"). Exposed for UI tests /// to target the per-provider <see cref="ProviderSettingsViewModel"/> /// row via <c>AutomationProperties.AutomationId</c>. /// </summary> public string Id => _provider.Id; public string ExtensionName => _provider.Extension?.ExtensionDisplayName ?? Resources.builtin_extension_name; public string ExtensionSubtext { get { if (!IsEnabled) { return string.Format(CultureInfo.CurrentCulture, ExtensionSubtextDisabledFormat, ExtensionName, Resources.builtin_disabled_extension); } int commandCount = TopLevelCommands.Count; if (HasFallbackCommands) { int fallbackCount = _provider.FallbackItems?.Length ?? 0; bool commandSingular = commandCount == 1; bool fallbackSingular = fallbackCount == 1; CompositeFormat format = (commandSingular, fallbackSingular) switch { (true, true) => ExtensionSubtextWithFallbackSingularBothFormat, (true, false) => ExtensionSubtextWithFallbackSingularCommandFormat, (false, true) => ExtensionSubtextWithFallbackSingularFallbackFormat, (false, false) => ExtensionSubtextWithFallbackFormat, }; return string.Format(CultureInfo.CurrentCulture, format, ExtensionName, commandCount, fallbackCount); } else { CompositeFormat format = commandCount == 1 ? ExtensionSubtextSingularFormat : ExtensionSubtextFormat; return string.Format(CultureInfo.CurrentCulture, format, ExtensionName, commandCount); } } } [MemberNotNullWhen(true, nameof(Extension))] public bool IsFromExtension => _provider.Extension is not null; public IExtensionWrapper? Extension => _provider.Extension; public string ExtensionVersion => IsFromExtension ? $"{Extension.Version.Major}.{Extension.Version.Minor}.{Extension.Version.Build}.{Extension.Version.Revision}" : string.Empty; public IconInfoViewModel Icon => IsEnabled ? _provider.Icon : EmptyIcon; [ObservableProperty] public partial bool LoadingSettings { get; set; } public bool IsEnabled { get => _providerSettings.IsEnabled; set { if (value != _providerSettings.IsEnabled) { var newSettings = _providerSettings with { IsEnabled = value }; _settingsService.UpdateSettings(s => s with { ProviderSettings = s.ProviderSettings.SetItem(_provider.ProviderId, newSettings), }); _providerSettings = newSettings; WeakReferenceMessenger.Default.Send<ProviderEnabledStateChangedMessage>(new(_provider.ProviderId, value)); OnPropertyChanged(nameof(IsEnabled)); OnPropertyChanged(nameof(ExtensionSubtext)); OnPropertyChanged(nameof(Icon)); } if (value == true) { _provider.CommandsChanged -= Provider_CommandsChanged; _provider.CommandsChanged += Provider_CommandsChanged; } } } /// <summary> /// Gets a value indicating whether returns true if we have a settings page /// that's initialized, or we are still working on initializing that /// settings page. If we don't have a settings object, or that settings /// object doesn't have a settings page, then we'll return false. /// </summary> public bool HasSettings { get { if (_provider.Settings is null) { return false; } if (_provider.Settings.Initialized) { return _provider.Settings.HasSettings; } // settings still need to be loaded. return LoadingSettings; } } /// <summary> /// Gets will return the settings page, if we have one, and have initialized it. /// If we haven't initialized it, this will kick off a thread to start /// initializing it. /// </summary> public ContentPageViewModel? SettingsPage { get { if (_provider.Settings is null) { return null; } if (_provider.Settings.Initialized) { LoadingSettings = false; return _provider.Settings.SettingsPage; } // Don't load the settings if we're already working on it lock (_initializeSettingsLock) { _initializeSettingsTask ??= Task.Run(InitializeSettingsPage); } return null; } } [field: AllowNull] public List<TopLevelViewModel> TopLevelCommands { get { if (field is null) { field = BuildTopLevelViewModels(); } return field; } } private List<TopLevelViewModel> BuildTopLevelViewModels() { var thisProvider = _provider; var providersCommands = thisProvider.TopLevelItems; // Remember! This comes in on the UI thread! return [.. providersCommands]; } [field: AllowNull] public List<FallbackSettingsViewModel> FallbackCommands { get; set; } = []; public bool HasFallbackCommands => _provider.FallbackItems?.Length > 0; private void BuildFallbackViewModels() { var thisProvider = _provider; var providersFallbackCommands = thisProvider.FallbackItems; List<FallbackSettingsViewModel> fallbackViewModels = new(providersFallbackCommands.Length); foreach (var fallbackItem in providersFallbackCommands) { if (_providerSettings.FallbackCommands.TryGetValue(fallbackItem.Id, out var fallbackSettings)) { fallbackViewModels.Add(new FallbackSettingsViewModel(fallbackItem, fallbackSettings, this, _settingsService)); } else { fallbackViewModels.Add(new FallbackSettingsViewModel(fallbackItem, new(), this, _settingsService)); } } FallbackCommands = fallbackViewModels; } internal void UpdateFallbackSettings(string id, FallbackSettings settings) { var newProviderSettings = _providerSettings with { FallbackCommands = _providerSettings.FallbackCommands.SetItem(id, settings), }; _providerSettings = newProviderSettings; _settingsService.UpdateSettings( s => s with { ProviderSettings = s.ProviderSettings.SetItem(_provider.ProviderId, newProviderSettings), }, hotReload: false); } private void InitializeSettingsPage() { if (_provider.Settings is null) { return; } _provider.Settings.SafeInitializeProperties(); _provider.Settings.DoOnUiThread(() => { // Changing these properties will try to update XAML, and that has // to be handled on the UI thread, so we need to raise them on the // UI thread LoadingSettings = false; OnPropertyChanged(nameof(HasSettings)); OnPropertyChanged(nameof(LoadingSettings)); OnPropertyChanged(nameof(SettingsPage)); }); } private void Provider_CommandsChanged(CommandProviderWrapper sender, CommandPalette.Extensions.IItemsChangedEventArgs args) { OnPropertyChanged(nameof(ExtensionSubtext)); OnPropertyChanged(nameof(TopLevelCommands)); } }