mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
Create a Microsoft.CmdPal.Core.ViewModels project (#40560)
_targets #40504_ Major refactoring for #40113 This moves a large swath of the codebase to a `.Core` project. "Core" doesn't have any explicit dependencies on "extensions", settings or the current `MainListPage`. It's just a filterable list of stuff. This should let us make this component a bit more reusable. This is half of a PR. As I did this, I noticed a particular bit of code for TopLevelVViewModels and CommandPaletteHost that was _very rough_. Solving it in this PR would make "move everything to a new project" much harder to review. So I'm submitting two PRs simultaneously, so we can see the changes separately, then merge together.
This commit is contained in:
@@ -1,146 +0,0 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
|
||||
: CommandItemViewModel(new(model), context)
|
||||
{
|
||||
public new ExtensionObject<IListItem> Model { get; } = new(model);
|
||||
|
||||
public List<TagViewModel>? Tags { get; set; }
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public bool HasTags => (Tags?.Count ?? 0) > 0;
|
||||
|
||||
public string TextToSuggest { get; private set; } = string.Empty;
|
||||
|
||||
public string Section { get; private set; } = string.Empty;
|
||||
|
||||
public DetailsViewModel? Details { get; private set; }
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Details))]
|
||||
public bool HasDetails => Details != null;
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
if (IsInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This sets IsInitialized = true
|
||||
base.InitializeProperties();
|
||||
|
||||
var li = Model.Unsafe;
|
||||
if (li == null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
UpdateTags(li.Tags);
|
||||
|
||||
TextToSuggest = li.TextToSuggest;
|
||||
Section = li.Section ?? string.Empty;
|
||||
var extensionDetails = li.Details;
|
||||
if (extensionDetails != null)
|
||||
{
|
||||
Details = new(extensionDetails, PageContext);
|
||||
Details.InitializeProperties();
|
||||
UpdateProperty(nameof(Details));
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(TextToSuggest));
|
||||
UpdateProperty(nameof(Section));
|
||||
}
|
||||
|
||||
protected override void FetchProperty(string propertyName)
|
||||
{
|
||||
base.FetchProperty(propertyName);
|
||||
|
||||
var model = this.Model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(Tags):
|
||||
UpdateTags(model.Tags);
|
||||
break;
|
||||
case nameof(TextToSuggest):
|
||||
this.TextToSuggest = model.TextToSuggest ?? string.Empty;
|
||||
break;
|
||||
case nameof(Section):
|
||||
this.Section = model.Section ?? string.Empty;
|
||||
break;
|
||||
case nameof(Details):
|
||||
var extensionDetails = model.Details;
|
||||
Details = extensionDetails != null ? new(extensionDetails, PageContext) : null;
|
||||
Details?.InitializeProperties();
|
||||
UpdateProperty(nameof(Details));
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
|
||||
// TODO: Do we want filters to match descriptions and other properties? Tags, etc... Yes?
|
||||
// TODO: Do we want to save off the score here so we can sort by it in our ListViewModel?
|
||||
public bool MatchesFilter(string filter) => StringMatcher.FuzzySearch(filter, Title).Success || StringMatcher.FuzzySearch(filter, Subtitle).Success;
|
||||
|
||||
public override string ToString() => $"{Name} ListItemViewModel";
|
||||
|
||||
public override bool Equals(object? obj) => obj is ListItemViewModel vm && vm.Model.Equals(this.Model);
|
||||
|
||||
public override int GetHashCode() => Model.GetHashCode();
|
||||
|
||||
private void UpdateTags(ITag[]? newTagsFromModel)
|
||||
{
|
||||
var newTags = newTagsFromModel?.Select(t =>
|
||||
{
|
||||
var vm = new TagViewModel(t, PageContext);
|
||||
vm.InitializeProperties();
|
||||
return vm;
|
||||
})
|
||||
.ToList() ?? [];
|
||||
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
{
|
||||
// Tags being an ObservableCollection instead of a List lead to
|
||||
// many COM exception issues.
|
||||
Tags = new(newTags);
|
||||
|
||||
UpdateProperty(nameof(Tags));
|
||||
UpdateProperty(nameof(HasTags));
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UnsafeCleanup()
|
||||
{
|
||||
base.UnsafeCleanup();
|
||||
|
||||
// Tags don't have event handlers or anything to cleanup
|
||||
Tags?.ForEach(t => t.SafeCleanup());
|
||||
Details?.SafeCleanup();
|
||||
|
||||
var model = Model.Unsafe;
|
||||
if (model != null)
|
||||
{
|
||||
// We don't need to revoke the PropChanged event handler here,
|
||||
// because we are just overriding CommandItem's FetchProperty and
|
||||
// piggy-backing off their PropChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user