mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
Some intial clean-up for Current Page and test Filtering
Has some issues still... (in part needs to update toolkit package for debounce extension update...)
This commit is contained in:
@@ -11,7 +11,6 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class ActionBarViewModel : ObservableObject,
|
||||
IRecipient<UpdateActionBarPage>,
|
||||
IRecipient<UpdateActionBarMessage>
|
||||
{
|
||||
public ListItemViewModel? SelectedItem
|
||||
@@ -33,15 +32,11 @@ public partial class ActionBarViewModel : ObservableObject,
|
||||
[ObservableProperty]
|
||||
public partial bool ShouldShowContextMenu { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PageViewModel? CurrentPage { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<CommandContextItemViewModel> ContextActions { get; set; } = [];
|
||||
|
||||
public ActionBarViewModel()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<UpdateActionBarPage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UpdateActionBarMessage>(this);
|
||||
}
|
||||
|
||||
@@ -75,6 +70,4 @@ public partial class ActionBarViewModel : ObservableObject,
|
||||
// InvokeItemCommand is what this will be in Xaml due to source generator
|
||||
[RelayCommand]
|
||||
private void InvokeItem(CommandContextItemViewModel item) => WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.Command));
|
||||
|
||||
public void Receive(UpdateActionBarPage message) => CurrentPage = message.Page;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel
|
||||
// itself, in the sense that they get raised by PropChanged events from the
|
||||
// extension. However, we don't want to actually make them
|
||||
// [ObservableProperty]s, because PropChanged comes in off the UI thread,
|
||||
// and ObservableProperty is not smart enough to raisee the PropertyChanged
|
||||
// and ObservableProperty is not smart enough to raise the PropertyChanged
|
||||
// on the UI thread.
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
|
||||
@@ -80,11 +80,11 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel
|
||||
Title = model.Title;
|
||||
Subtitle = model.Subtitle;
|
||||
IconUri = model.Icon.Icon;
|
||||
MoreCommands = model.MoreCommands
|
||||
/*MoreCommands = model.MoreCommands
|
||||
.Where(contextItem => contextItem is ICommandContextItem)
|
||||
.Select(contextItem => (contextItem as ICommandContextItem)!)
|
||||
.Select(contextItem => new CommandContextItemViewModel(contextItem, Scheduler))
|
||||
.ToList();
|
||||
.ToList();*/
|
||||
|
||||
// Here, we're already theoretically in the async context, so we can
|
||||
// use Initialize straight up
|
||||
|
||||
@@ -80,4 +80,9 @@ public partial class ListItemViewModel(IListItem model, TaskScheduler scheduler)
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
|
||||
// TODO: Do we want filters to match descriptions and other properties? Tags, etc... Yes?
|
||||
public bool MatchesFilter(string filter) => Title.Contains(filter) || Name.Contains(filter);
|
||||
|
||||
public override string ToString() => $"{Name} ListItemViewModel";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Mvvm.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -14,10 +14,14 @@ namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class ListViewModel : PageViewModel
|
||||
{
|
||||
private readonly HashSet<ListItemViewModel> _itemCache = [];
|
||||
|
||||
// TODO: Do we want a base "ItemsPageViewModel" for anything that's going to have items?
|
||||
|
||||
// Observable from MVVM Toolkit will auto create public properties that use INotifyPropertyChange change
|
||||
// https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/observablegroupedcollections for grouping support
|
||||
[ObservableProperty]
|
||||
public partial ObservableGroupedCollection<string, ListItemViewModel> Items { get; set; } = [];
|
||||
public partial ObservableCollection<ListItemViewModel> Items { get; set; } = [];
|
||||
|
||||
private readonly ExtensionObject<IListPage> _model;
|
||||
|
||||
@@ -27,13 +31,44 @@ public partial class ListViewModel : PageViewModel
|
||||
_model = new(model);
|
||||
}
|
||||
|
||||
protected override void OnFilterUpdated(string filter)
|
||||
{
|
||||
//// TODO: Just temp testing, need to think about where we want to filter, as ACVS in View could be done, but then grouping need CVS, maybe we do grouping in view
|
||||
//// and manage filtering below, but we should be smarter about this and understand caching and other requirements...
|
||||
|
||||
// Remove all items out right if we clear the filter, otherwise, recheck the items already displayed.
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
{
|
||||
Items.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove any existing items which don't match the filter
|
||||
for (var i = Items.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!Items[i].MatchesFilter(filter))
|
||||
{
|
||||
Items.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any new items which do match the filter
|
||||
foreach (var item in _itemCache)
|
||||
{
|
||||
if ((filter == string.Empty || item.MatchesFilter(filter))
|
||||
&& !Items.Contains(item)) //// TODO: We should be smarter here somehow
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Model_ItemsChanged(object sender, ItemsChangedEventArgs args) => FetchItems();
|
||||
|
||||
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
||||
private void FetchItems()
|
||||
{
|
||||
ObservableGroup<string, ListItemViewModel> group = new(string.Empty);
|
||||
|
||||
// TEMPORARY: just plop all the items into a single group
|
||||
// see 9806fe5d8 for the last commit that had this with sections
|
||||
// TODO unsafe
|
||||
@@ -41,18 +76,20 @@ public partial class ListViewModel : PageViewModel
|
||||
{
|
||||
var newItems = _model.Unsafe!.GetItems();
|
||||
|
||||
Items.Clear();
|
||||
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
// TODO: When we fetch next page of items or refreshed items, we may need to check if we have an existing ViewModel in the cache?
|
||||
ListItemViewModel viewModel = new(item, Scheduler);
|
||||
viewModel.InitializeProperties();
|
||||
group.Add(viewModel);
|
||||
}
|
||||
_itemCache.Add(viewModel); // TODO: Figure out when we clear/remove things from cache...
|
||||
|
||||
// Am I really allowed to modify that observable collection on a BG
|
||||
// thread and have it just work in the UI??
|
||||
Items.AddGroup(group);
|
||||
if (viewModel.MatchesFilter(Filter))
|
||||
{
|
||||
// Am I really allowed to modify that observable collection on a BG
|
||||
// thread and have it just work in the UI??
|
||||
Items.Add(viewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record UpdateActionBarPage(PageViewModel? Page)
|
||||
public record NavigateToPageMessage(PageViewModel? Page)
|
||||
{
|
||||
}
|
||||
@@ -23,6 +23,10 @@ public partial class PageViewModel : ExtensionObjectViewModel
|
||||
[ObservableProperty]
|
||||
public partial string ErrorMessage { get; private set; } = string.Empty;
|
||||
|
||||
// This is set from the SearchBar
|
||||
[ObservableProperty]
|
||||
public partial string Filter { get; set; } = string.Empty;
|
||||
|
||||
// These are properties that are "observable" from the extension object
|
||||
// itself, in the sense that they get raised by PropChanged events from the
|
||||
// extension. However, we don't want to actually make them
|
||||
@@ -91,6 +95,14 @@ public partial class PageViewModel : ExtensionObjectViewModel
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnFilterChanged(string oldValue, string newValue) => OnFilterUpdated(newValue);
|
||||
|
||||
protected virtual void OnFilterUpdated(string filter)
|
||||
{
|
||||
// The base page has no notion of data, so we do nothing here...
|
||||
// subclasses should override.
|
||||
}
|
||||
|
||||
protected virtual void FetchProperty(string propertyName)
|
||||
{
|
||||
var model = this._pageModel.Unsafe;
|
||||
|
||||
@@ -11,14 +11,20 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class ShellViewModel(IServiceProvider _serviceProvider) : ObservableObject
|
||||
public partial class ShellViewModel(IServiceProvider _serviceProvider) : ObservableObject,
|
||||
IRecipient<NavigateToPageMessage>
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsLoaded { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PageViewModel? CurrentPage { get; set; }
|
||||
|
||||
[RelayCommand]
|
||||
public async Task<bool> LoadAsync()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||
|
||||
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>();
|
||||
await tlcManager!.LoadBuiltinsAsync();
|
||||
IsLoaded = true;
|
||||
@@ -40,4 +46,6 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider) : Observa
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Receive(NavigateToPageMessage message) => CurrentPage = message.Page;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Text="{x:Bind ViewModel.CurrentPage.Name, Mode=OneWay}" />
|
||||
Text="{x:Bind CurrentPageViewModel.Name, Mode=OneWay}" />
|
||||
<!-- TO DO: Replace with ItemsRepeater and bind "Primary commands"? -->
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
|
||||
@@ -3,15 +3,27 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.Views;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class ActionBar : UserControl
|
||||
public sealed partial class ActionBar : UserControl, ICurrentPageAware
|
||||
{
|
||||
public ActionBarViewModel ViewModel { get; set; } = new();
|
||||
|
||||
public PageViewModel? CurrentPageViewModel
|
||||
{
|
||||
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
|
||||
set => SetValue(CurrentPageViewModelProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for CurrentPage. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty CurrentPageViewModelProperty =
|
||||
DependencyProperty.Register(nameof(CurrentPageViewModel), typeof(PageViewModel), typeof(ActionBar), new PropertyMetadata(null));
|
||||
|
||||
public ActionBar()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
using System.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.Views;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using CoreVirtualKeyStates = Windows.UI.Core.CoreVirtualKeyStates;
|
||||
@@ -15,7 +18,7 @@ using VirtualKey = Windows.System.VirtualKey;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class SearchBar : UserControl
|
||||
public sealed partial class SearchBar : UserControl, ICurrentPageAware
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DispatcherQueueTimer"/> that we create to track keyboard input and throttle/debounce before we make queries.
|
||||
@@ -24,6 +27,16 @@ public sealed partial class SearchBar : UserControl
|
||||
|
||||
public bool Nested { get; set; }
|
||||
|
||||
public PageViewModel? CurrentPageViewModel
|
||||
{
|
||||
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
|
||||
set => SetValue(CurrentPageViewModelProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for CurrentPageViewModel. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty CurrentPageViewModelProperty =
|
||||
DependencyProperty.Register(nameof(CurrentPageViewModel), typeof(PageViewModel), typeof(SearchBar), new PropertyMetadata(null));
|
||||
|
||||
public SearchBar()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -87,11 +100,16 @@ public sealed partial class SearchBar : UserControl
|
||||
|
||||
private void FilterBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
// TODO: We could encapsulate this in a Behavior if we wanted to bind to the Filter property.
|
||||
_debounceTimer.Debounce(
|
||||
() =>
|
||||
{
|
||||
// TODO: Actually Plumb Filtering
|
||||
Debug.WriteLine($"Filter: {FilterBox.Text}");
|
||||
if (CurrentPageViewModel != null)
|
||||
{
|
||||
CurrentPageViewModel.Filter = FilterBox.Text;
|
||||
}
|
||||
},
|
||||
//// Couldn't find a good recommendation/resource for value here.
|
||||
//// This seems like a useful testing site for typing times: https://keyboardtester.info/keyboard-latency-test/
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<!-- TODO: Figure out what we want to do here for filtering/grouping and where -->
|
||||
<!-- https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.data.collectionviewsource -->
|
||||
<CollectionViewSource
|
||||
<!--<CollectionViewSource
|
||||
x:Name="ItemsCVS"
|
||||
IsSourceGrouped="True"
|
||||
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />
|
||||
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
|
||||
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
@@ -110,9 +111,9 @@
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<ProgressBar
|
||||
IsIndeterminate="True"
|
||||
<ProgressBar
|
||||
VerticalAlignment="Top"
|
||||
IsIndeterminate="True"
|
||||
Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay}" />
|
||||
<!-- not using Interactivity:Interaction.Behaviors due to wanting to do AoT -->
|
||||
<!--
|
||||
@@ -125,9 +126,9 @@
|
||||
x:Name="ItemsList"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_ItemClick"
|
||||
SelectionChanged="ItemsList_SelectionChanged"
|
||||
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ItemsCVS.View, Mode=OneWay}">
|
||||
ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}"
|
||||
SelectionChanged="ItemsList_SelectionChanged">
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
@@ -152,11 +153,14 @@
|
||||
VerticalAlignment="Center"
|
||||
IsIndeterminate="True" />-->
|
||||
</controls:Case>
|
||||
|
||||
|
||||
<controls:Case Value="Error">
|
||||
<StackPanel Orientation="Vertical" Margin="16">
|
||||
<TextBlock Text="Error on page" FontSize="18" Foreground="{ThemeResource SystemErrorTextColor}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.ErrorMessage, Mode=OneWay}" IsTextSelectionEnabled="True"/>
|
||||
<StackPanel Margin="16" Orientation="Vertical">
|
||||
<TextBlock
|
||||
FontSize="18"
|
||||
Foreground="{ThemeResource SystemErrorTextColor}"
|
||||
Text="Error on page" />
|
||||
<TextBlock IsTextSelectionEnabled="True" Text="{x:Bind ViewModel.ErrorMessage, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed partial class ListPage : Page,
|
||||
var result = (bool)lvm.InitializeCommand.ExecutionTask.GetResultOrDefault()!;
|
||||
|
||||
ViewModel = lvm;
|
||||
WeakReferenceMessenger.Default.Send<UpdateActionBarPage>(new(result ? lvm : null));
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(result ? lvm : null));
|
||||
LoadedState = result ? ViewModelLoadedState.Loaded : ViewModelLoadedState.Error;
|
||||
});
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public sealed partial class ListPage : Page,
|
||||
else
|
||||
{
|
||||
ViewModel = lvm;
|
||||
WeakReferenceMessenger.Default.Send<UpdateActionBarPage>(new(lvm));
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(lvm));
|
||||
LoadedState = ViewModelLoadedState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
<controls:SearchBar
|
||||
x:Name="SearchBox"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Top" />
|
||||
VerticalAlignment="Top"
|
||||
CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
@@ -31,6 +32,9 @@
|
||||
</Grid>
|
||||
|
||||
|
||||
<controls:ActionBar Grid.Row="2" VerticalAlignment="Top" />
|
||||
<controls:ActionBar
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Top"
|
||||
CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -80,7 +80,7 @@ public sealed partial class ShellPage :
|
||||
RootFrame.BackStack.Clear();
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.Send<UpdateActionBarPage>(new(pageViewModel));
|
||||
ViewModel.CurrentPage = pageViewModel;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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.UI.ViewModels;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Views;
|
||||
|
||||
public interface ICurrentPageAware
|
||||
{
|
||||
public PageViewModel? CurrentPageViewModel { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user