mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
CmdPal: Add context commands for pinning nested commands (#45673)
_targets #45572_ This change allows our contact menu factory to actually create and add additional context menu commands for pinning commands to the top level. Now for any command provider built with the latest SDK that return subcommands with an ID, we will add additional context menu commands that allows you to pin that command to the top level. <img width="540" height="181" alt="image" src="https://github.com/user-attachments/assets/6c2cfe3c-4143-44d1-9308-bfc71db4c842" /> <img width="729" height="317" alt="image" src="https://github.com/user-attachments/assets/4ff75c9f-1f35-4c1e-a03e-6fab5cbab423" /> related to https://github.com/microsoft/PowerToys/issues/45191 related to https://github.com/microsoft/PowerToys/issues/45201 This PR notably does not remove pinning from the apps extension. I thought that made sense to do as a follow-up PR for the sake of reviewability. --- description from #45676 which was merged into this Removes the code that the apps provider was using to support pinning apps to the top level list of commands. Now the all apps provider just uses the global support for pinning commands to the top level. This does have the side effect of removing the separation of pinned apps from unpinned apps on the All Apps page. However, we all pretty much agree that wasn't a particularly widely used feature, and it's safe to remove. With this, we can finally call this issue done 🎉 closes https://github.com/microsoft/PowerToys/issues/45191
This commit is contained in:
@@ -166,5 +166,5 @@ public interface IAppHostService
|
||||
|
||||
AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost);
|
||||
|
||||
CommandProviderContext GetProviderContextForCommand(object? command, CommandProviderContext? currentContext);
|
||||
ICommandProviderContext GetProviderContextForCommand(object? command, ICommandProviderContext? currentContext);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public partial class CommandContextItemViewModel : CommandItemViewModel, IContex
|
||||
public bool HasRequestedShortcut => RequestedShortcut is not null && (RequestedShortcut.Value != nullKeyChord);
|
||||
|
||||
public CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context)
|
||||
: base(new(contextItem), context)
|
||||
: base(new(contextItem), context, contextMenuFactory: null)
|
||||
{
|
||||
Model = new(contextItem);
|
||||
IsContextMenuItem = true;
|
||||
|
||||
@@ -103,7 +103,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
public CommandItemViewModel(
|
||||
ExtensionObject<ICommandItem> item,
|
||||
WeakReference<IPageContext> errorContext,
|
||||
IContextMenuFactory? contextMenuFactory = null)
|
||||
IContextMenuFactory? contextMenuFactory)
|
||||
: base(errorContext)
|
||||
{
|
||||
_commandItemModel = item;
|
||||
@@ -464,6 +464,30 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
.ForEach(c => c.SafeCleanup());
|
||||
}
|
||||
|
||||
public void RefreshMoreCommands()
|
||||
{
|
||||
Task.Run(RefreshMoreCommandsSynchronous);
|
||||
}
|
||||
|
||||
private void RefreshMoreCommandsSynchronous()
|
||||
{
|
||||
try
|
||||
{
|
||||
BuildAndInitMoreCommands();
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
UpdateProperty(nameof(AllCommands));
|
||||
UpdateProperty(nameof(SecondaryCommand));
|
||||
UpdateProperty(nameof(SecondaryCommandName));
|
||||
UpdateProperty(nameof(HasMoreCommands));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle any exceptions that might occur during the refresh process
|
||||
CoreLogger.LogError("Error refreshing MoreCommands in CommandItemViewModel", ex);
|
||||
ShowException(ex, _commandItemModel?.Unsafe?.Title);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UnsafeCleanup()
|
||||
{
|
||||
base.UnsafeCleanup();
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class CommandPaletteContentPageViewModel : ContentPageViewModel
|
||||
{
|
||||
public CommandPaletteContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host, CommandProviderContext providerContext)
|
||||
public CommandPaletteContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host, ICommandProviderContext providerContext)
|
||||
: base(model, scheduler, host, providerContext)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ public class CommandPalettePageViewModelFactory
|
||||
: IPageViewModelFactoryService
|
||||
{
|
||||
private readonly TaskScheduler _scheduler;
|
||||
private readonly IContextMenuFactory? _contextMenuFactory;
|
||||
private readonly IContextMenuFactory _contextMenuFactory;
|
||||
|
||||
public CommandPalettePageViewModelFactory(TaskScheduler scheduler, IContextMenuFactory? contextMenuFactory)
|
||||
public CommandPalettePageViewModelFactory(TaskScheduler scheduler, IContextMenuFactory contextMenuFactory)
|
||||
{
|
||||
_scheduler = scheduler;
|
||||
_contextMenuFactory = contextMenuFactory;
|
||||
}
|
||||
|
||||
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, CommandProviderContext providerContext)
|
||||
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, ICommandProviderContext providerContext)
|
||||
{
|
||||
return page switch
|
||||
{
|
||||
|
||||
@@ -4,9 +4,21 @@
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public sealed class CommandProviderContext
|
||||
public static class CommandProviderContext
|
||||
{
|
||||
public required string ProviderId { get; init; }
|
||||
public static ICommandProviderContext Empty { get; } = new EmptyCommandProviderContext();
|
||||
|
||||
public static CommandProviderContext Empty { get; } = new() { ProviderId = "<EMPTY>" };
|
||||
private sealed class EmptyCommandProviderContext : ICommandProviderContext
|
||||
{
|
||||
public string ProviderId => "<EMPTY>";
|
||||
|
||||
public bool SupportsPinning => false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICommandProviderContext
|
||||
{
|
||||
string ProviderId { get; }
|
||||
|
||||
bool SupportsPinning { get; }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public sealed class CommandProviderWrapper
|
||||
public sealed class CommandProviderWrapper : ICommandProviderContext
|
||||
{
|
||||
public bool IsExtension => Extension is not null;
|
||||
|
||||
@@ -47,12 +47,17 @@ public sealed class CommandProviderWrapper
|
||||
|
||||
public string ProviderId => string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
|
||||
|
||||
public bool SupportsPinning { get; private set; }
|
||||
|
||||
public TopLevelItemPageContext TopLevelPageContext { get; }
|
||||
|
||||
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
|
||||
{
|
||||
// This ctor is only used for in-proc builtin commands. So the Unsafe!
|
||||
// calls are pretty dang safe actually.
|
||||
_commandProvider = new(provider);
|
||||
_taskScheduler = mainThread;
|
||||
TopLevelPageContext = new TopLevelItemPageContext(this, _taskScheduler);
|
||||
|
||||
// Hook the extension back into us
|
||||
ExtensionHost = new CommandPaletteHost(provider);
|
||||
@@ -77,6 +82,7 @@ public sealed class CommandProviderWrapper
|
||||
{
|
||||
_taskScheduler = mainThread;
|
||||
_commandProviderCache = commandProviderCache;
|
||||
TopLevelPageContext = new TopLevelItemPageContext(this, _taskScheduler);
|
||||
|
||||
Extension = extension;
|
||||
ExtensionHost = new CommandPaletteHost(extension);
|
||||
@@ -121,7 +127,7 @@ public sealed class CommandProviderWrapper
|
||||
return settings.GetProviderSettings(this);
|
||||
}
|
||||
|
||||
public async Task LoadTopLevelCommands(IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
|
||||
public async Task LoadTopLevelCommands(IServiceProvider serviceProvider)
|
||||
{
|
||||
if (!isValid)
|
||||
{
|
||||
@@ -157,8 +163,14 @@ public sealed class CommandProviderWrapper
|
||||
UnsafePreCacheApiAdditions(two);
|
||||
}
|
||||
|
||||
// Load pinned commands from saved settings
|
||||
var pinnedCommands = LoadPinnedCommands(model, providerSettings);
|
||||
ICommandItem[] pinnedCommands = [];
|
||||
if (model is ICommandProvider4 four)
|
||||
{
|
||||
SupportsPinning = true;
|
||||
|
||||
// Load pinned commands from saved settings
|
||||
pinnedCommands = LoadPinnedCommands(four, providerSettings);
|
||||
}
|
||||
|
||||
Id = model.Id;
|
||||
DisplayName = model.DisplayName;
|
||||
@@ -177,7 +189,7 @@ public sealed class CommandProviderWrapper
|
||||
Settings = new(model.Settings, this, _taskScheduler);
|
||||
|
||||
// We do need to explicitly initialize commands though
|
||||
InitializeCommands(commands, fallbacks, pinnedCommands, serviceProvider, pageContext);
|
||||
InitializeCommands(commands, fallbacks, pinnedCommands, serviceProvider);
|
||||
|
||||
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
|
||||
}
|
||||
@@ -208,14 +220,20 @@ public sealed class CommandProviderWrapper
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, ICommandItem[] pinnedCommands, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
|
||||
private void InitializeCommands(
|
||||
ICommandItem[] commands,
|
||||
IFallbackCommandItem[] fallbacks,
|
||||
ICommandItem[] pinnedCommands,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
var settings = serviceProvider.GetService<SettingsModel>()!;
|
||||
var contextMenuFactory = serviceProvider.GetService<IContextMenuFactory>()!;
|
||||
var providerSettings = GetProviderSettings(settings);
|
||||
var ourContext = GetProviderContext();
|
||||
var pageContext = new WeakReference<IPageContext>(TopLevelPageContext);
|
||||
var makeAndAdd = (ICommandItem? i, bool fallback) =>
|
||||
{
|
||||
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
|
||||
CommandItemViewModel commandItemViewModel = new(new(i), pageContext, contextMenuFactory: contextMenuFactory);
|
||||
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ourContext, settings, providerSettings, serviceProvider, i);
|
||||
topLevelViewModel.InitializeProperties();
|
||||
|
||||
@@ -244,27 +262,24 @@ public sealed class CommandProviderWrapper
|
||||
}
|
||||
}
|
||||
|
||||
private ICommandItem[] LoadPinnedCommands(ICommandProvider model, ProviderSettings providerSettings)
|
||||
private ICommandItem[] LoadPinnedCommands(ICommandProvider4 model, ProviderSettings providerSettings)
|
||||
{
|
||||
var pinnedItems = new List<ICommandItem>();
|
||||
|
||||
if (model is ICommandProvider4 provider4)
|
||||
foreach (var pinnedId in providerSettings.PinnedCommandIds)
|
||||
{
|
||||
foreach (var pinnedId in providerSettings.PinnedCommandIds)
|
||||
try
|
||||
{
|
||||
try
|
||||
var commandItem = model.GetCommandItem(pinnedId);
|
||||
if (commandItem is not null)
|
||||
{
|
||||
var commandItem = provider4.GetCommandItem(pinnedId);
|
||||
if (commandItem is not null)
|
||||
{
|
||||
pinnedItems.Add(commandItem);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Failed to load pinned command {pinnedId}: {e.Message}");
|
||||
pinnedItems.Add(commandItem);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Failed to load pinned command {pinnedId}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return pinnedItems.ToArray();
|
||||
@@ -298,11 +313,22 @@ public sealed class CommandProviderWrapper
|
||||
}
|
||||
}
|
||||
|
||||
public CommandProviderContext GetProviderContext()
|
||||
public void UnpinCommand(string commandId, IServiceProvider serviceProvider)
|
||||
{
|
||||
return new() { ProviderId = ProviderId };
|
||||
var settings = serviceProvider.GetService<SettingsModel>()!;
|
||||
var providerSettings = GetProviderSettings(settings);
|
||||
|
||||
if (providerSettings.PinnedCommandIds.Remove(commandId))
|
||||
{
|
||||
SettingsModel.SaveSettings(settings);
|
||||
|
||||
// Raise CommandsChanged so the TopLevelCommandManager reloads our commands
|
||||
this.CommandsChanged?.Invoke(this, new ItemsChangedEventArgs(-1));
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandProviderContext GetProviderContext() => this;
|
||||
|
||||
public override bool Equals(object? obj) => obj is CommandProviderWrapper wrapper && isValid == wrapper.isValid;
|
||||
|
||||
public override int GetHashCode() => _commandProvider.GetHashCode();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.
|
||||
|
||||
@@ -11,7 +11,6 @@ using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Common.Text;
|
||||
using Microsoft.CmdPal.Ext.Apps;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Commands;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Properties;
|
||||
@@ -409,11 +408,13 @@ public sealed partial class MainListPage : DynamicListPage,
|
||||
var allNewApps = AllAppsCommandProvider.Page.GetItems().Cast<AppListItem>().ToList();
|
||||
|
||||
// We need to remove pinned apps from allNewApps so they don't show twice.
|
||||
var pinnedApps = PinnedAppsManager.Instance.GetPinnedAppIdentifiers();
|
||||
// Pinned app command IDs are stored in ProviderSettings.PinnedCommandIds.
|
||||
_settings.ProviderSettings.TryGetValue(AllAppsCommandProvider.WellKnownId, out var providerSettings);
|
||||
var pinnedCommandIds = providerSettings?.PinnedCommandIds;
|
||||
|
||||
if (pinnedApps.Length > 0)
|
||||
if (pinnedCommandIds is not null && pinnedCommandIds.Count > 0)
|
||||
{
|
||||
newApps = allNewApps.Where(w => pinnedApps.IndexOf(w.AppIdentifier) < 0);
|
||||
newApps = allNewApps.Where(li => li.Command != null && !pinnedCommandIds.Contains(li.Command.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public ContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host, CommandProviderContext providerContext)
|
||||
public ContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host, ICommandProviderContext providerContext)
|
||||
: base(model, scheduler, host, providerContext)
|
||||
{
|
||||
_model = new(model);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.
|
||||
|
||||
@@ -8,6 +8,8 @@ public class GlobalLogPageContext : IPageContext
|
||||
{
|
||||
public TaskScheduler Scheduler { get; private init; }
|
||||
|
||||
ICommandProviderContext IPageContext.ProviderContext => CommandProviderContext.Empty;
|
||||
|
||||
public void ShowException(Exception ex, string? extensionHint)
|
||||
{ /*do nothing*/
|
||||
}
|
||||
|
||||
18
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Icons.cs
Normal file
18
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Icons.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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 Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public static class Icons
|
||||
{
|
||||
public static IconInfo PinIcon => new("\uE718"); // Pin icon
|
||||
|
||||
public static IconInfo UnpinIcon => new("\uE77A"); // Unpin icon
|
||||
|
||||
public static IconInfo SettingsIcon => new("\uE713"); // Settings icon
|
||||
|
||||
public static IconInfo EditIcon => new("\uE70F"); // Edit icon
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context, IContextMenuFactory? contextMenuFactory = null)
|
||||
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context, IContextMenuFactory contextMenuFactory)
|
||||
: base(new(model), context, contextMenuFactory)
|
||||
{
|
||||
Model = new ExtensionObject<IListItem>(model);
|
||||
|
||||
@@ -36,7 +36,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
private readonly ExtensionObject<IListPage> _model;
|
||||
|
||||
private readonly Lock _listLock = new();
|
||||
private readonly IContextMenuFactory? _contextMenuFactory;
|
||||
private readonly IContextMenuFactory _contextMenuFactory;
|
||||
|
||||
private InterlockedBoolean _isLoading;
|
||||
private bool _isFetching;
|
||||
@@ -96,12 +96,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, CommandProviderContext providerContext, IContextMenuFactory? contextMenuFactory)
|
||||
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, ICommandProviderContext providerContext, IContextMenuFactory contextMenuFactory)
|
||||
: base(model, scheduler, host, providerContext)
|
||||
{
|
||||
_model = new(model);
|
||||
_contextMenuFactory = contextMenuFactory;
|
||||
EmptyContent = new(new(null), PageContext, _contextMenuFactory);
|
||||
EmptyContent = new(new(null), PageContext, contextMenuFactory: null);
|
||||
}
|
||||
|
||||
private void FiltersPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
@@ -243,7 +243,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
continue;
|
||||
}
|
||||
|
||||
var viewModel = new ListItemViewModel(item, new(this));
|
||||
var viewModel = new ListItemViewModel(item, new(this), _contextMenuFactory);
|
||||
|
||||
// If an item fails to load, silently ignore it.
|
||||
if (viewModel.SafeFastInit())
|
||||
@@ -636,7 +636,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
UpdateProperty(nameof(SearchText));
|
||||
UpdateProperty(nameof(InitialSearchText));
|
||||
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext, _contextMenuFactory);
|
||||
EmptyContent.SlowInitializeProperties();
|
||||
|
||||
Filters?.PropertyChanged -= FiltersPropertyChanged;
|
||||
@@ -732,7 +732,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
SearchText = model.SearchText;
|
||||
break;
|
||||
case nameof(EmptyContent):
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext, contextMenuFactory: null);
|
||||
EmptyContent.SlowInitializeProperties();
|
||||
break;
|
||||
case nameof(Filters):
|
||||
@@ -806,7 +806,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
base.UnsafeCleanup();
|
||||
|
||||
EmptyContent?.SafeCleanup();
|
||||
EmptyContent = new(new(null), PageContext); // necessary?
|
||||
EmptyContent = new(new(null), PageContext, contextMenuFactory: null); // necessary?
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
filterCancellationTokenSource?.Cancel();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record UnpinCommandItemMessage(string ProviderId, string CommandId)
|
||||
{
|
||||
}
|
||||
@@ -76,9 +76,9 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
||||
|
||||
public IconInfoViewModel Icon { get; protected set; }
|
||||
|
||||
public CommandProviderContext ProviderContext { get; protected set; }
|
||||
public ICommandProviderContext ProviderContext { get; protected set; }
|
||||
|
||||
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost, CommandProviderContext providerContext)
|
||||
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost, ICommandProviderContext providerContext)
|
||||
: base(scheduler)
|
||||
{
|
||||
InitializeSelfAsPageContext();
|
||||
@@ -267,6 +267,8 @@ public interface IPageContext
|
||||
void ShowException(Exception ex, string? extensionHint = null);
|
||||
|
||||
TaskScheduler Scheduler { get; }
|
||||
|
||||
ICommandProviderContext ProviderContext { get; }
|
||||
}
|
||||
|
||||
public interface IPageViewModelFactoryService
|
||||
@@ -278,5 +280,5 @@ public interface IPageViewModelFactoryService
|
||||
/// <param name="nested">Indicates whether the page is not the top-level page.</param>
|
||||
/// <param name="host">The command palette host that will host the page (for status messages)</param>
|
||||
/// <returns>A new instance of the page view model.</returns>
|
||||
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, CommandProviderContext providerContext);
|
||||
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, ICommandProviderContext providerContext);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
public partial class TopLevelCommandManager : ObservableObject,
|
||||
IRecipient<ReloadCommandsMessage>,
|
||||
IRecipient<PinCommandItemMessage>,
|
||||
IPageContext,
|
||||
IRecipient<UnpinCommandItemMessage>,
|
||||
IDisposable
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
@@ -34,8 +34,6 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
private readonly Lock _commandProvidersLock = new();
|
||||
private readonly SupersedingAsyncGate _reloadCommandsGate;
|
||||
|
||||
TaskScheduler IPageContext.Scheduler => _taskScheduler;
|
||||
|
||||
public TopLevelCommandManager(IServiceProvider serviceProvider, ICommandProviderCache commandProviderCache)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
@@ -43,6 +41,7 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
|
||||
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<PinCommandItemMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UnpinCommandItemMessage>(this);
|
||||
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
|
||||
}
|
||||
|
||||
@@ -103,9 +102,7 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
// May be called from a background thread
|
||||
private async Task<IEnumerable<TopLevelViewModel>> LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider)
|
||||
{
|
||||
WeakReference<IPageContext> weakSelf = new(this);
|
||||
|
||||
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
|
||||
await commandProvider.LoadTopLevelCommands(_serviceProvider);
|
||||
|
||||
var commands = await Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -152,8 +149,7 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
/// <returns>an awaitable task</returns>
|
||||
private async Task UpdateCommandsForProvider(CommandProviderWrapper sender, IItemsChangedEventArgs args)
|
||||
{
|
||||
WeakReference<IPageContext> weakSelf = new(this);
|
||||
await sender.LoadTopLevelCommands(_serviceProvider, weakSelf);
|
||||
await sender.LoadTopLevelCommands(_serviceProvider);
|
||||
|
||||
List<TopLevelViewModel> newItems = [.. sender.TopLevelItems];
|
||||
foreach (var i in sender.FallbackItems)
|
||||
@@ -421,7 +417,13 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
wrapper?.PinCommand(message.CommandId, _serviceProvider);
|
||||
}
|
||||
|
||||
private CommandProviderWrapper? LookupProvider(string providerId)
|
||||
public void Receive(UnpinCommandItemMessage message)
|
||||
{
|
||||
var wrapper = LookupProvider(message.ProviderId);
|
||||
wrapper?.UnpinCommand(message.CommandId, _serviceProvider);
|
||||
}
|
||||
|
||||
public CommandProviderWrapper? LookupProvider(string providerId)
|
||||
{
|
||||
lock (_commandProvidersLock)
|
||||
{
|
||||
@@ -430,12 +432,6 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
}
|
||||
}
|
||||
|
||||
void IPageContext.ShowException(Exception ex, string? extensionHint)
|
||||
{
|
||||
var message = DiagnosticsHelper.BuildExceptionMessage(ex, extensionHint ?? "TopLevelCommandManager");
|
||||
CommandPaletteHost.Instance.Log(message);
|
||||
}
|
||||
|
||||
internal bool IsProviderActive(string id)
|
||||
{
|
||||
lock (_commandProvidersLock)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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 Microsoft.CmdPal.Common.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Used as the PageContext for top-level items. Top level items are displayed
|
||||
/// on the MainListPage, which _we_ own. We need to have a placeholder page
|
||||
/// context for each provider that still connects those top-level items to the
|
||||
/// CommandProvider they came from.
|
||||
/// </summary>
|
||||
public partial class TopLevelItemPageContext : IPageContext
|
||||
{
|
||||
public TaskScheduler Scheduler { get; private set; }
|
||||
|
||||
public ICommandProviderContext ProviderContext { get; private set; }
|
||||
|
||||
TaskScheduler IPageContext.Scheduler => Scheduler;
|
||||
|
||||
ICommandProviderContext IPageContext.ProviderContext => ProviderContext;
|
||||
|
||||
internal TopLevelItemPageContext(CommandProviderWrapper provider, TaskScheduler scheduler)
|
||||
{
|
||||
ProviderContext = provider.GetProviderContext();
|
||||
Scheduler = scheduler;
|
||||
}
|
||||
|
||||
public void ShowException(Exception ex, string? extensionHint = null)
|
||||
{
|
||||
var message = DiagnosticsHelper.BuildExceptionMessage(ex, extensionHint ?? $"TopLevelItemPageContext({ProviderContext.ProviderId})");
|
||||
CommandPaletteHost.Instance.Log(message);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly CommandItemViewModel _commandItemViewModel;
|
||||
|
||||
public CommandProviderContext ProviderContext { get; private set; }
|
||||
public ICommandProviderContext ProviderContext { get; private set; }
|
||||
|
||||
private string IdFromModel => IsFallback && !string.IsNullOrWhiteSpace(_fallbackId) ? _fallbackId : _commandItemViewModel.Command.Id;
|
||||
|
||||
@@ -189,7 +189,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx
|
||||
CommandItemViewModel item,
|
||||
bool isFallback,
|
||||
CommandPaletteHost extensionHost,
|
||||
CommandProviderContext commandProviderContext,
|
||||
ICommandProviderContext commandProviderContext,
|
||||
SettingsModel settings,
|
||||
ProviderSettings providerSettings,
|
||||
IServiceProvider serviceProvider,
|
||||
|
||||
Reference in New Issue
Block a user