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
+
+
+
+
+
+
+
+
+
+