Stash working on #426

Goal here is to merge TopLevelCommandItemWrapper -> TopLevelViewModel.

TLVM will have the IListItem implementation. It'll implement it by exposing
properties off the CIVM.

That way, we can wrap up the toplevel CI from the extension into a CIVM,
stash the properties into the CIVM,
then use the TLVM safely.

Otherwise, the way the SUI works today is super unsafe. In fact, TLCIW is
generally unsafe, just didn't realize it. It wasn't copying jack

Stashing cause I have other stuff that needs to get done today
This commit is contained in:
Mike Griese
2025-03-11 16:06:39 -05:00
parent b9df74d227
commit f1067c290f
4 changed files with 152 additions and 44 deletions

View File

@@ -9,7 +9,7 @@ using Windows.Storage.Streams;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class IconDataViewModel : ObservableObject
public partial class IconDataViewModel : ObservableObject, IIconData
{
private readonly ExtensionObject<IIconData> _model = new(null);
@@ -25,6 +25,8 @@ public partial class IconDataViewModel : ObservableObject
// first. Hence why we're sticking this into an ExtensionObject
public ExtensionObject<IRandomAccessStreamReference> Data { get; private set; } = new(null);
IRandomAccessStreamReference? IIconData.Data => Data.Unsafe;
public IconDataViewModel(IIconData? icon)
{
_model = new(icon);

View File

@@ -8,7 +8,7 @@ using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class IconInfoViewModel : ObservableObject
public partial class IconInfoViewModel : ObservableObject, IIconInfo
{
private readonly ExtensionObject<IIconInfo> _model = new(null);
@@ -28,6 +28,10 @@ public partial class IconInfoViewModel : ObservableObject
public bool IsSet => _model.Unsafe != null;
IIconData? IIconInfo.Dark => Dark;
IIconData? IIconInfo.Light => Light;
public IconInfoViewModel(IIconInfo? icon)
{
_model = new(icon);

View File

@@ -16,7 +16,8 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class TopLevelCommandManager : ObservableObject,
IRecipient<ReloadCommandsMessage>
IRecipient<ReloadCommandsMessage>,
IPageContext
{
private readonly IServiceProvider _serviceProvider;
private readonly TaskScheduler _taskScheduler;
@@ -24,6 +25,8 @@ public partial class TopLevelCommandManager : ObservableObject,
private readonly List<CommandProviderWrapper> _builtInCommands = [];
private readonly List<CommandProviderWrapper> _extensionCommandProviders = [];
TaskScheduler IPageContext.Scheduler => _taskScheduler;
public TopLevelCommandManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
@@ -31,7 +34,7 @@ public partial class TopLevelCommandManager : ObservableObject,
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
}
public ObservableCollection<TopLevelCommandItemWrapper> TopLevelCommands { get; set; } = [];
public ObservableCollection<TopLevelViewModel> TopLevelCommands { get; set; } = [];
[ObservableProperty]
public partial bool IsLoading { get; private set; } = true;
@@ -60,13 +63,18 @@ public partial class TopLevelCommandManager : ObservableObject,
{
await commandProvider.LoadTopLevelCommands();
var settings = _serviceProvider.GetService<SettingsModel>()!;
var makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), this);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, settings, _serviceProvider);
TopLevelCommandItemWrapper wrapper = new(
new(i), fallback, commandProvider.ExtensionHost, commandProvider.ProviderId, _serviceProvider);
lock (TopLevelCommands)
{
TopLevelCommands.Add(wrapper);
TopLevelCommands.Add(topLevelViewModel);
}
};
@@ -310,4 +318,10 @@ public partial class TopLevelCommandManager : ObservableObject,
public void Receive(ReloadCommandsMessage message) =>
ReloadAllCommandsAsync().ConfigureAwait(false);
void IPageContext.ShowException(Exception ex, string? extensionHint)
{
var errorMessage = $"A bug occurred in {$"the \"{extensionHint}\"" ?? "an unknown's"} extension's code:\n{ex.Message}\n{ex.Source}\n{ex.StackTrace}\n\n";
CommandPaletteHost.Instance.Log(errorMessage);
}
}

View File

@@ -2,95 +2,183 @@
// 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 Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public sealed partial class TopLevelViewModel : ObservableObject
public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
private readonly SettingsModel _settings;
private readonly IServiceProvider _serviceProvider;
private readonly CommandItemViewModel _commandItemViewModel;
private readonly string _idFromModel = string.Empty;
// TopLevelCommandItemWrapper is a ListItem, but it's in-memory for the app already.
// We construct it either from data that we pulled from the cache, or from the
// extension, but the data in it is all in our process now.
private readonly TopLevelCommandItemWrapper _item;
private readonly string _generatedId = string.Empty;
private HotkeySettings? _hotkey;
public IconInfoViewModel Icon { get; private set; }
private CommandAlias? Alias { get; set; }
public string Title => _item.Title;
[ObservableProperty]
public partial ObservableCollection<Tag> Tags { get; set; } = [];
public string Subtitle => _item.Subtitle;
public string Id => string.IsNullOrEmpty(_idFromModel) ? _generatedId : _idFromModel;
string ICommandItem.Title => _commandItemViewModel.Title;
string ICommandItem.Subtitle => _commandItemViewModel.Subtitle;
IIconInfo ICommandItem.Icon => _commandItemViewModel.Icon;
ITag[] IListItem.Tags => Tags.ToArray();
IDetails? IListItem.Details => null;
string IListItem.Section => string.Empty;
string IListItem.TextToSuggest => string.Empty;
ICommand? ICommandItem.Command => _commandItemViewModel.Command.Model.Unsafe;
IContextItem?[] ICommandItem.MoreCommands => _commandItemViewModel.MoreCommands.Select(i => i.Model.Unsafe).ToArray()
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
public HotkeySettings? Hotkey
{
get => _item.Hotkey;
get => _hotkey;
set
{
_serviceProvider.GetService<HotkeyManager>()!.UpdateHotkey(_item.Id, value);
_item.Hotkey = value;
_serviceProvider.GetService<HotkeyManager>()!.UpdateHotkey(Id, value);
UpdateHotkey();
UpdateTags();
Save();
}
}
private string _aliasText;
public string AliasText
{
get => _aliasText;
get => Alias?.Alias ?? string.Empty;
set
{
if (SetProperty(ref _aliasText, value))
if (string.IsNullOrEmpty(value))
{
UpdateAlias();
Alias = null;
}
else
{
if (Alias is CommandAlias a)
{
a.Alias = value;
}
else
{
Alias = new CommandAlias(value, Id);
}
}
HandleChangeAlias();
}
}
private bool _isDirectAlias;
public bool IsDirectAlias
{
get => _isDirectAlias;
get => Alias?.IsDirect ?? false;
set
{
if (SetProperty(ref _isDirectAlias, value))
if (Alias is CommandAlias a)
{
UpdateAlias();
a.IsDirect = value;
}
HandleChangeAlias();
}
}
public TopLevelViewModel(TopLevelCommandItemWrapper item, SettingsModel settings, IServiceProvider serviceProvider)
public TopLevelViewModel(
CommandItemViewModel item,
SettingsModel settings,
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_settings = settings;
_item = item;
Icon = new(item.Icon ?? item.Command?.Icon);
Icon.InitializeProperties();
_commandItemViewModel = item;
var aliases = _serviceProvider.GetService<AliasManager>()!;
_isDirectAlias = _item.Alias?.IsDirect ?? false;
_aliasText = _item.Alias?.Alias ?? string.Empty;
item.PropertyChanged += Item_PropertyChanged;
UpdateAlias();
}
private void Item_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!string.IsNullOrEmpty(e.PropertyName))
{
PropChanged?.Invoke(this, new PropChangedEventArgs(e.PropertyName));
}
}
private void Save() => SettingsModel.SaveSettings(_settings);
private void UpdateAlias()
private void HandleChangeAlias()
{
if (string.IsNullOrWhiteSpace(_aliasText))
{
_item.UpdateAlias(null);
}
else
{
var newAlias = new CommandAlias(_aliasText, _item.Id, _isDirectAlias);
_item.UpdateAlias(newAlias);
}
SetAlias(Alias);
Save();
}
public void SetAlias(CommandAlias? newAlias)
{
_serviceProvider.GetService<AliasManager>()!.UpdateAlias(Id, newAlias);
UpdateAlias();
UpdateTags();
}
private void UpdateAlias()
{
// Add tags for the alias, if we have one.
var aliases = _serviceProvider.GetService<AliasManager>();
if (aliases != null)
{
Alias = aliases.AliasFromId(Id);
}
}
private void UpdateHotkey()
{
var settings = _serviceProvider.GetService<SettingsModel>()!;
var hotkey = settings.CommandHotkeys.Where(hk => hk.CommandId == Id).FirstOrDefault();
if (hotkey != null)
{
_hotkey = hotkey.Hotkey;
}
}
private void UpdateTags()
{
var tags = new List<Tag>();
if (Hotkey != null)
{
tags.Add(new Tag() { Text = Hotkey.ToString() });
}
if (Alias != null)
{
tags.Add(new Tag() { Text = Alias.SearchPrefix });
}
Task.Factory.StartNew(
() =>
{
ListHelpers.InPlaceUpdateList(Tags, tags);
},
CancellationToken.None,
TaskCreationOptions.None,
_commandItemViewModel.PageContext.Scheduler);
}
}