mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-08 20:27:36 +02:00
CmdPal: Remove ShellViewModel's dependency on MainListPage (#40482)
targets #40479 Kinda mental that the viewmodel had this hard dependency. So instead I added a service for providing the root page for the app. Theoretically, you could add a different `IRootPageService`, and give out a different root page. ref #40113
This commit is contained in:
@@ -19,13 +19,13 @@ public interface IExtensionService
|
|||||||
|
|
||||||
Task SignalStopExtensionsAsync();
|
Task SignalStopExtensionsAsync();
|
||||||
|
|
||||||
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
|
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
|
||||||
|
|
||||||
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
|
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
|
||||||
|
|
||||||
public void EnableExtension(string extensionUniqueId);
|
void EnableExtension(string extensionUniqueId);
|
||||||
|
|
||||||
public void DisableExtension(string extensionUniqueId);
|
void DisableExtension(string extensionUniqueId);
|
||||||
|
|
||||||
///// <summary>
|
///// <summary>
|
||||||
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature
|
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Common.Services;
|
||||||
|
|
||||||
|
public interface IRootPageService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the root page of the command palette. Return any IPage implementation that
|
||||||
|
/// represents the root view of this instance of the command palette.
|
||||||
|
/// </summary>
|
||||||
|
Microsoft.CommandPalette.Extensions.IPage GetRootPage();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-loads any necessary data or state before the root page is loaded.
|
||||||
|
/// This will be awaited before the root page and the user can do anything,
|
||||||
|
/// so ideally it should be quick and not block the UI thread for long.
|
||||||
|
/// </summary>
|
||||||
|
Task PreLoadAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do any loading work that can be done after the root page is loaded and
|
||||||
|
/// displayed to the user.
|
||||||
|
/// This is run asynchronously, on a background thread.
|
||||||
|
/// </summary>
|
||||||
|
Task PostLoadRootPageAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a top-level command is performed. The context is the
|
||||||
|
/// sender context for the invoked command. This is typically the IListItem
|
||||||
|
/// or ICommandContextItem that was used to invoke the command.
|
||||||
|
/// </summary>
|
||||||
|
void OnPerformTopLevelCommand(object? context);
|
||||||
|
}
|
||||||
@@ -9,11 +9,9 @@ using CommunityToolkit.Mvvm.Input;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.CmdPal.Common.Services;
|
using Microsoft.CmdPal.Common.Services;
|
||||||
using Microsoft.CmdPal.UI.ViewModels.MainPage;
|
|
||||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using WinRT;
|
using WinRT;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||||
@@ -21,7 +19,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
|||||||
public partial class ShellViewModel : ObservableObject,
|
public partial class ShellViewModel : ObservableObject,
|
||||||
IRecipient<PerformCommandMessage>
|
IRecipient<PerformCommandMessage>
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IRootPageService _rootPageService;
|
||||||
private readonly TaskScheduler _scheduler;
|
private readonly TaskScheduler _scheduler;
|
||||||
private readonly Lock _invokeLock = new();
|
private readonly Lock _invokeLock = new();
|
||||||
private Task? _handleInvokeTask;
|
private Task? _handleInvokeTask;
|
||||||
@@ -60,17 +58,17 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MainListPage? _mainListPage;
|
private IPage? _rootPage;
|
||||||
|
|
||||||
private IExtensionWrapper? _activeExtension;
|
private IExtensionWrapper? _activeExtension;
|
||||||
private bool _isNested;
|
private bool _isNested;
|
||||||
|
|
||||||
public bool IsNested { get => _isNested; }
|
public bool IsNested { get => _isNested; }
|
||||||
|
|
||||||
public ShellViewModel(IServiceProvider serviceProvider, TaskScheduler scheduler)
|
public ShellViewModel(TaskScheduler scheduler, IRootPageService rootPageService)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_scheduler = scheduler;
|
_scheduler = scheduler;
|
||||||
|
_rootPageService = rootPageService;
|
||||||
_currentPage = new LoadingPageViewModel(null, _scheduler);
|
_currentPage = new LoadingPageViewModel(null, _scheduler);
|
||||||
|
|
||||||
// Register to receive messages
|
// Register to receive messages
|
||||||
@@ -80,24 +78,27 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task<bool> LoadAsync()
|
public async Task<bool> LoadAsync()
|
||||||
{
|
{
|
||||||
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>();
|
// First, do any loading that the root page service needs to do before we can
|
||||||
await tlcManager!.LoadBuiltinsAsync();
|
// display the root page. For example, this might include loading
|
||||||
|
// the built-in commands, or loading the settings.
|
||||||
|
await _rootPageService.PreLoadAsync();
|
||||||
|
|
||||||
IsLoaded = true;
|
IsLoaded = true;
|
||||||
|
|
||||||
// Built-ins have loaded. We can display our page at this point.
|
// Now that the basics are set up, we can load the root page.
|
||||||
_mainListPage = new MainListPage(_serviceProvider);
|
_rootPage = _rootPageService.GetRootPage();
|
||||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_mainListPage)));
|
|
||||||
|
|
||||||
|
// This sends a message to us to load the root page view model.
|
||||||
|
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_rootPage)));
|
||||||
|
|
||||||
|
// Now that the root page is loaded, do any post-load work that the root page service needs to do.
|
||||||
|
// This runs asynchronously, on a background thread.
|
||||||
|
// This might include starting extensions, for example.
|
||||||
|
// Note: We don't await this, so that we can return immediately.
|
||||||
|
// This is important because we don't want to block the UI thread.
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
|
await _rootPageService.PostLoadRootPageAsync();
|
||||||
tlcManager.LoadExtensionsCommand.Execute(null);
|
|
||||||
|
|
||||||
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
|
|
||||||
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
|
||||||
{
|
|
||||||
// TODO: Handle failure case
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -172,15 +173,7 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
|
|
||||||
public void PerformTopLevelCommand(PerformCommandMessage message)
|
public void PerformTopLevelCommand(PerformCommandMessage message)
|
||||||
{
|
{
|
||||||
if (_mainListPage == null)
|
_rootPageService.OnPerformTopLevelCommand(message.Context);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.Context is IListItem listItem)
|
|
||||||
{
|
|
||||||
_mainListPage.UpdateHistory(listItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(PerformCommandMessage message)
|
public void Receive(PerformCommandMessage message)
|
||||||
@@ -255,10 +248,11 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
{
|
{
|
||||||
Logger.LogDebug($"Navigating to page");
|
Logger.LogDebug($"Navigating to page");
|
||||||
|
|
||||||
var isMainPage = command is MainListPage;
|
var isMainPage = command == _rootPage;
|
||||||
|
_isNested = !isMainPage;
|
||||||
|
|
||||||
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
|
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
|
||||||
var pageViewModel = GetViewModelForPage(page, !isMainPage, host);
|
var pageViewModel = GetViewModelForPage(page, _isNested, host);
|
||||||
if (pageViewModel == null)
|
if (pageViewModel == null)
|
||||||
{
|
{
|
||||||
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
|
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
|
||||||
@@ -267,8 +261,6 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
|
|
||||||
// Kick off async loading of our ViewModel
|
// Kick off async loading of our ViewModel
|
||||||
LoadPageViewModel(pageViewModel);
|
LoadPageViewModel(pageViewModel);
|
||||||
_isNested = !isMainPage;
|
|
||||||
|
|
||||||
OnUIThread(() => { WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)); });
|
OnUIThread(() => { WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)); });
|
||||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel, message.WithAnimation));
|
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel, message.WithAnimation));
|
||||||
|
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ public partial class App : Application
|
|||||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||||
services.AddSingleton<TrayIconService>();
|
services.AddSingleton<TrayIconService>();
|
||||||
|
|
||||||
|
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||||
services.AddSingleton(new TelemetryForwarder());
|
services.AddSingleton(new TelemetryForwarder());
|
||||||
|
|
||||||
// ViewModels
|
// ViewModels
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// 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 ManagedCommon;
|
||||||
|
using Microsoft.CmdPal.Common.Services;
|
||||||
|
using Microsoft.CmdPal.UI.ViewModels;
|
||||||
|
using Microsoft.CmdPal.UI.ViewModels.MainPage;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
namespace Microsoft.CmdPal.UI;
|
||||||
|
|
||||||
|
internal sealed class PowerToysRootPageService : IRootPageService
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private Lazy<MainListPage> _mainListPage;
|
||||||
|
|
||||||
|
public PowerToysRootPageService(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
|
||||||
|
_mainListPage = new Lazy<MainListPage>(() =>
|
||||||
|
{
|
||||||
|
return new MainListPage(_serviceProvider);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PreLoadAsync()
|
||||||
|
{
|
||||||
|
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||||
|
await tlcManager.LoadBuiltinsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Microsoft.CommandPalette.Extensions.IPage GetRootPage()
|
||||||
|
{
|
||||||
|
return _mainListPage.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PostLoadRootPageAsync()
|
||||||
|
{
|
||||||
|
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||||
|
|
||||||
|
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
|
||||||
|
tlcManager.LoadExtensionsCommand.Execute(null);
|
||||||
|
|
||||||
|
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
|
||||||
|
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||||
|
{
|
||||||
|
// TODO: Handle failure case
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPerformTopLevelCommand(object? context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (context is IListItem listItem)
|
||||||
|
{
|
||||||
|
_mainListPage.Value.UpdateHistory(listItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Failed to update history in PowerToysRootPageService");
|
||||||
|
Logger.LogError(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user