diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainPage/MainListPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainPage/MainListPage.cs new file mode 100644 index 0000000000..b5afd7d559 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainPage/MainListPage.cs @@ -0,0 +1,40 @@ +// 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.Extensions; +using Microsoft.CmdPal.Extensions.Helpers; +using Microsoft.CmdPal.UI.ViewModels; + +namespace Microsoft.CmdPal.UI.Pages; + +/// +/// This class encapsulates the data we load from built-in providers and extensions to use within the same extension-UI system for a . +/// TODO: Need to think about how we structure/interop for the page -> section -> item between the main setup, the extensions, and our viewmodels. +/// +public partial class MainListPage : DynamicListPage +{ + private readonly ISection[] _sections; + + // TODO: Thinking we may want a separate MainViewModel from the ShellViewModel and/or a CommandService/Provider which holds the TopLevelCommands and anything that needs to access those functions... + public MainListPage(ShellViewModel shellViewModel) + { + _sections = [new MainListSection() + { + Items = shellViewModel.TopLevelCommands.Select(w => w.Unsafe).Where(li => li != null).ToArray(), + } + ]; + } + + public override ISection[] GetItems() => _sections; +} + +//// TODO: Temporary until we sort out proper PageViewModel and SectionViewModel containers/setup +#pragma warning disable SA1402 // File may only contain a single type +public partial class MainListSection : ISection +#pragma warning restore SA1402 // File may only contain a single type +{ + public required IListItem[] Items { get; set; } + + public string Title => "Commands"; // TODO: Localization +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/NavigateToListMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/NavigateToListMessage.cs new file mode 100644 index 0000000000..4b5bc56653 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/NavigateToListMessage.cs @@ -0,0 +1,16 @@ +// 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; + +// Want to know what a record is? here is a TLDR +// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record + +/// +/// Message to instruct UI to navigate to a list page. +/// +/// The for the list page to navigate to. +public record NavigateToListMessage(ListViewModel ViewModel) +{ +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj index 29a1e9184b..3561639191 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj @@ -9,4 +9,9 @@ + + + + + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionObject`1.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionObject`1.cs new file mode 100644 index 0000000000..04e6af883c --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionObject`1.cs @@ -0,0 +1,25 @@ +// 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.Models; + +public class ExtensionObject // where T : IInspectable +{ + private readonly T _value; + + public ExtensionObject(T value) + { + _value = value; + } + + // public T? Safe { + // get { + // try { + // if (_value!.Equals(_value)) return _value; + // } catch (COMException){ /* log something */ } + // return default; + // } + // } + public T Unsafe => _value; +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/CommandProviderWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/CommandProviderWrapper.cs new file mode 100644 index 0000000000..ad93d322a1 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/CommandProviderWrapper.cs @@ -0,0 +1,78 @@ +// 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.Services; +using Microsoft.CmdPal.Extensions; + +namespace Microsoft.CmdPal.UI.ViewModels; + +public sealed class CommandProviderWrapper +{ + public bool IsExtension => extensionWrapper != null; + + private readonly bool isValid; + + private readonly ICommandProvider _commandProvider; + + private readonly IExtensionWrapper? extensionWrapper; + private IListItem[] _topLevelItems = []; + + public IListItem[] TopLevelItems => _topLevelItems; + + public CommandProviderWrapper(ICommandProvider provider) + { + _commandProvider = provider; + isValid = true; + } + + public CommandProviderWrapper(IExtensionWrapper extension) + { + extensionWrapper = extension; + var extensionImpl = extension.GetExtensionObject(); + if (extensionImpl?.GetProvider(ProviderType.Commands) is not ICommandProvider provider) + { + throw new ArgumentException("extension didn't actually implement ICommandProvider"); + } + + _commandProvider = provider; + isValid = true; + } + + public async Task LoadTopLevelCommands() + { + if (!isValid) + { + return; + } + + var t = new Task(() => _commandProvider.TopLevelCommands()); + t.Start(); + var commands = await t.ConfigureAwait(false); + + // On a BG thread here + if (commands != null) + { + _topLevelItems = commands; + } + } + + /* This is a View/ExtensionHost piece + * public void AllowSetForeground(bool allow) + { + if (!IsExtension) + { + return; + } + + var iextn = extensionWrapper?.GetExtensionObject(); + unsafe + { + PInvoke.CoAllowSetForegroundWindow(iextn); + } + }*/ + + public override bool Equals(object? obj) => obj is CommandProviderWrapper wrapper && isValid == wrapper.isValid; + + public override int GetHashCode() => _commandProvider.GetHashCode(); +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListItemViewModel.cs index 65ef13561a..ec828a52b4 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListItemViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListItemViewModel.cs @@ -3,15 +3,33 @@ // See the LICENSE file in the project root for more information. using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.CmdPal.Extensions; +using Microsoft.CmdPal.Models; namespace Microsoft.CmdPal.UI.ViewModels; public partial class ListItemViewModel : ObservableObject { - // Observable from MVVM Toolkit will auto create public properties that use INotifyPropertyChange - [ObservableProperty] - private string _header = string.Empty; + private readonly ExtensionObject _listItemModel; - [ObservableProperty] - private string _subHeader = string.Empty; + public string Title => _listItemModel.Unsafe.Title; + + public string Subtitle => _listItemModel.Unsafe.Subtitle; + + /// + /// Gets the path for the icon to load in the View layer. TODO: Converter/Cache + /// + public string IconUri => _listItemModel.Unsafe.Icon.Icon; + + public ITag[] Tags => _listItemModel.Unsafe.Tags; + + public bool HasTags => Tags.Length > 0; + + public ListItemViewModel(IListItem model) + { + _listItemModel = new(model); + _listItemModel.Unsafe.PropChanged += Model_PropChanged; + } + + private void Model_PropChanged(object sender, PropChangedEventArgs args) => OnPropertyChanged(args.PropertyName); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListViewModel.cs index 096b3ebfa8..b904a19a08 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ListViewModel.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; +using Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.UI.ViewModels.Messages; namespace Microsoft.CmdPal.UI.ViewModels; @@ -17,12 +18,22 @@ public partial class ListViewModel : ObservableObject [ObservableProperty] private ObservableCollection _items = []; + public ListViewModel(IListPage model) + { + foreach (var section in model.GetItems()) + { + // TODO: Ignoring sections for now + foreach (var item in section.Items) + { + _items.Add(new(item)); + } + } + } + // InvokeItemCommand is what this will be in Xaml due to source generator [RelayCommand] private void InvokeItem(ListItemViewModel item) { WeakReferenceMessenger.Default.Send(new(item)); - - Debug.WriteLine("Hello!" + item.Header); } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ShellViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ShellViewModel.cs index ede37c465c..c87dd69497 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ShellViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ViewModels/ShellViewModel.cs @@ -2,18 +2,58 @@ // 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.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using Microsoft.CmdPal.Extensions; +using Microsoft.CmdPal.Models; +using Microsoft.CmdPal.UI.Pages; +using Microsoft.CmdPal.UI.ViewModels.Messages; namespace Microsoft.CmdPal.UI.ViewModels; public partial class ShellViewModel : ObservableObject { + [ObservableProperty] + private bool _isLoaded = false; + + public ObservableCollection ActionsProvider { get; set; } = []; + + public ObservableCollection> TopLevelCommands { get; set; } = []; + + private readonly IEnumerable _builtInCommands; + + public ShellViewModel(IEnumerable builtInCommands) + { + _builtInCommands = builtInCommands; + } + [RelayCommand] public async Task LoadAsync() { - await Task.Delay(2000); + // Load Built In Commands First + foreach (var provider in _builtInCommands) + { + CommandProviderWrapper wrapper = new(provider); + ActionsProvider.Add(wrapper); + await LoadTopLevelCommandsFromProvider(wrapper); + } + + IsLoaded = true; + + // TODO: would want to hydrate this from our services provider in the View layer, need to think about construction here... + WeakReferenceMessenger.Default.Send(new(new(new MainListPage(this)))); return true; } + + private async Task LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider) + { + await commandProvider.LoadTopLevelCommands(); + foreach (var i in commandProvider.TopLevelItems) + { + TopLevelCommands.Add(new(i)); + } + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs index 3c168f0b34..f342a0eab4 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs @@ -2,6 +2,12 @@ // 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.Ext.Bookmarks; +using Microsoft.CmdPal.Ext.Calc; +using Microsoft.CmdPal.Ext.Settings; +using Microsoft.CmdPal.Extensions; +using Microsoft.CmdPal.UI.ViewModels; +using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; // To learn more about WinUI, the WinUI project structure, @@ -13,6 +19,16 @@ namespace Microsoft.CmdPal.UI; /// public partial class App : Application { + /// + /// Gets the current instance in use. + /// + public static new App Current => (App)Application.Current; + + /// + /// Gets the instance to resolve application services. + /// + public IServiceProvider Services { get; } + /// /// Initializes a new instance of the class. /// Initializes the singleton application object. This is the first line of authored code @@ -20,6 +36,8 @@ public partial class App : Application /// public App() { + Services = ConfigureServices(); + this.InitializeComponent(); } @@ -34,4 +52,22 @@ public partial class App : Application } private Window? _window; + + /// + /// Configures the services for the application + /// + private static ServiceProvider ConfigureServices() + { + ServiceCollection services = new(); + + // Built-in Commands + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // ViewModels + services.AddSingleton((services) => new(services.GetServices())); + + return services.BuildServiceProvider(); + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml index 179d16e226..15085ecdf2 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml @@ -1,17 +1,17 @@ - + - - + + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml.cs index 43477be003..5dc7b4b1eb 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListDetailPage.xaml.cs @@ -16,7 +16,7 @@ namespace Microsoft.CmdPal.UI; /// public sealed partial class ListDetailPage : Page { - public ListItemViewModel ViewModel { get; set; } = new ListItemViewModel(); + public ListItemViewModel? ViewModel { get; set; } public ListDetailPage() { diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml index 4ea8277f38..a34ce8d9df 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml @@ -15,11 +15,11 @@ - + - - + + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs index 36c58c393e..633bd89025 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml.cs @@ -4,6 +4,7 @@ using Microsoft.CmdPal.UI.ViewModels; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; namespace Microsoft.CmdPal.UI; @@ -12,21 +13,28 @@ namespace Microsoft.CmdPal.UI; /// public sealed partial class ListPage : Page { - public ListViewModel ViewModel { get; set; } = new(); + public ListViewModel? ViewModel { get; set; } public ListPage() { this.InitializeComponent(); - ViewModel.Items.Add(new ListItemViewModel { Header = "Hello", SubHeader = "World" }); - ViewModel.Items.Add(new ListItemViewModel { Header = "Clint", SubHeader = "Rutkas" }); - ViewModel.Items.Add(new ListItemViewModel { Header = "Michael", SubHeader = "Hawker" }); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is ListViewModel lvm) + { + ViewModel = lvm; + } + + base.OnNavigatedTo(e); } private void ItemsView_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args) { if (args.InvokedItem is ListItemViewModel item) { - ViewModel.InvokeItemCommand.Execute(item); + ViewModel?.InvokeItemCommand.Execute(item); } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/LoadingPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/LoadingPage.xaml.cs index c9437e6d83..52145d59ed 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/LoadingPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/LoadingPage.xaml.cs @@ -34,15 +34,10 @@ public sealed partial class LoadingPage : Page { await shellVM.LoadCommand.ExecutionTask!; - if (shellVM.LoadCommand.ExecutionTask.Status == TaskStatus.RanToCompletion) + if (shellVM.LoadCommand.ExecutionTask.Status != TaskStatus.RanToCompletion) { - await _queue.EnqueueAsync(() => - { - Frame.Navigate(typeof(ListPage), new ListViewModel(), new DrillInNavigationTransitionInfo()); - }); + // TODO: Handle failure case } - - // TODO: Handle failure case }); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj index b46a8d1e87..e2d7b3f62e 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj @@ -35,10 +35,25 @@ + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + +