mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Adding list content
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public interface IListViewModel : IDisposable
|
||||
{
|
||||
ObservableCollection<ListItemViewModel> FilteredItems { get; set; }
|
||||
|
||||
event TypedEventHandler<IListViewModel, object>? ItemsUpdated;
|
||||
|
||||
bool IsNested { get; set; }
|
||||
|
||||
bool ShowEmptyContent { get; }
|
||||
|
||||
bool IsGridView { get; }
|
||||
|
||||
IGridPropertiesViewModel? GridProperties { get; }
|
||||
|
||||
bool ShowDetails { get; }
|
||||
|
||||
string SearchText { get; }
|
||||
|
||||
string InitialSearchText { get; }
|
||||
|
||||
CommandItemViewModel EmptyContent { get; }
|
||||
|
||||
bool IsInitialized { get; }
|
||||
|
||||
void InitializeProperties();
|
||||
|
||||
void LoadMoreIfNeeded();
|
||||
}
|
||||
@@ -15,12 +15,8 @@ using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class ListViewModel : PageViewModel, IDisposable
|
||||
public partial class ListViewModel : PageViewModel, IListViewModel
|
||||
{
|
||||
// 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]
|
||||
@@ -37,7 +33,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
private InterlockedBoolean _isLoading;
|
||||
private bool _isFetching;
|
||||
|
||||
public event TypedEventHandler<ListViewModel, object>? ItemsUpdated;
|
||||
public event TypedEventHandler<IListViewModel, object>? ItemsUpdated;
|
||||
|
||||
public bool ShowEmptyContent =>
|
||||
IsInitialized &&
|
||||
|
||||
@@ -21,8 +21,20 @@ public partial class CommandPaletteContentPageViewModel : ContentPageViewModel
|
||||
IFormContent form => new ContentFormViewModel(form, context),
|
||||
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context),
|
||||
ITreeContent tree => new ContentTreeViewModel(tree, context),
|
||||
IListContent listContent => new ContentListViewModel(listContent, context),
|
||||
_ => null,
|
||||
};
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
protected override void OnSearchTextBoxUpdated(string searchTextBox)
|
||||
{
|
||||
base.OnSearchTextBoxUpdated(searchTextBox);
|
||||
|
||||
// Propagate to any list content VMs so they can filter/update
|
||||
foreach (var c in Content.OfType<ContentListViewModel>())
|
||||
{
|
||||
c.UpdateSearchTextBox(searchTextBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,715 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class ContentListViewModel : ContentViewModel, IListViewModel
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
|
||||
|
||||
public bool IsNested { get; set; }
|
||||
|
||||
private ObservableCollection<ListItemViewModel> Items { get; set; } = [];
|
||||
|
||||
private readonly ExtensionObject<IListContent> _model;
|
||||
|
||||
private readonly Lock _listLock = new();
|
||||
|
||||
private InterlockedBoolean _isLoading;
|
||||
private bool _isFetching;
|
||||
private bool _isInitialized;
|
||||
|
||||
public event TypedEventHandler<IListViewModel, object>? ItemsUpdated;
|
||||
|
||||
public bool ShowEmptyContent =>
|
||||
IsInitialized &&
|
||||
FilteredItems.Count == 0 &&
|
||||
(!_isFetching) &&
|
||||
_isLoading.Value == false;
|
||||
|
||||
public bool IsGridView { get; private set; }
|
||||
|
||||
public IGridPropertiesViewModel? GridProperties { get; private set; }
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public bool ShowDetails { get; private set; }
|
||||
|
||||
private string _modelPlaceholderText = string.Empty;
|
||||
|
||||
public string SearchText { get; private set; } = string.Empty;
|
||||
|
||||
public string InitialSearchText { get; private set; } = string.Empty;
|
||||
|
||||
public CommandItemViewModel EmptyContent { get; private set; }
|
||||
|
||||
public bool IsMainPage { get; init; }
|
||||
|
||||
private bool _isDynamic;
|
||||
|
||||
private Task? _initializeItemsTask;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private CancellationTokenSource? _fetchItemsCancellationTokenSource;
|
||||
|
||||
private ListItemViewModel? _lastSelectedItem;
|
||||
|
||||
public string SearchTextBox { get; set; } = string.Empty;
|
||||
|
||||
public bool IsInitialized
|
||||
{
|
||||
get => _isInitialized; protected set
|
||||
{
|
||||
_isInitialized = value;
|
||||
UpdateEmptyContent();
|
||||
}
|
||||
}
|
||||
|
||||
public ContentListViewModel(IListContent model, WeakReference<IPageContext> context)
|
||||
: base(context)
|
||||
{
|
||||
_model = new(model);
|
||||
EmptyContent = new(new(null), PageContext);
|
||||
}
|
||||
|
||||
// TODO: Does this need to hop to a _different_ thread, so that we don't block the extension while we're fetching?
|
||||
private void Model_ItemsChanged(object sender, IItemsChangedEventArgs args) => FetchItems();
|
||||
|
||||
protected void OnSearchTextBoxUpdated(string searchTextBox)
|
||||
{
|
||||
//// TODO: Just temp testing, need to think about where we want to filter, as AdvancedCollectionView in View could be done, but then grouping need CollectionViewSource, maybe we do grouping in view
|
||||
//// and manage filtering below, but we should be smarter about this and understand caching and other requirements...
|
||||
//// Investigate if we re-use src\modules\cmdpal\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\ListHelpers.cs InPlaceUpdateList and FilterList?
|
||||
|
||||
// Dynamic pages will handler their own filtering. They will tell us if
|
||||
// something needs to change, by raising ItemsChanged.
|
||||
if (_isDynamic)
|
||||
{
|
||||
// We're getting called on the UI thread.
|
||||
// Hop off to a BG thread to update the extension.
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_model.Unsafe is IDynamicListPage dynamic)
|
||||
{
|
||||
dynamic.SearchText = searchTextBox;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex, "ContentList");
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// But for all normal pages, we should run our fuzzy match on them.
|
||||
lock (_listLock)
|
||||
{
|
||||
ApplyFilterUnderLock();
|
||||
}
|
||||
|
||||
ItemsUpdated?.Invoke(this, EventArgs.Empty);
|
||||
UpdateEmptyContent();
|
||||
_isLoading.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCurrentFilter(string currentFilterId)
|
||||
{
|
||||
// We're getting called on the UI thread.
|
||||
// Hop off to a BG thread to update the extension.
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_model.Unsafe is IListPage listPage)
|
||||
{
|
||||
listPage.Filters?.CurrentFilterId = currentFilterId;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex, "Content List");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
||||
private void FetchItems()
|
||||
{
|
||||
// Cancel any previous FetchItems operation
|
||||
_fetchItemsCancellationTokenSource?.Cancel();
|
||||
_fetchItemsCancellationTokenSource?.Dispose();
|
||||
_fetchItemsCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var cancellationToken = _fetchItemsCancellationTokenSource.Token;
|
||||
|
||||
// TEMPORARY: just plop all the items into a single group
|
||||
// see 9806fe5d8 for the last commit that had this with sections
|
||||
_isFetching = true;
|
||||
|
||||
// Collect all the items into new viewmodels
|
||||
Collection<ListItemViewModel> newViewModels = [];
|
||||
|
||||
try
|
||||
{
|
||||
// Check for cancellation before starting expensive operations
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var newItems = _model.Unsafe!.GetItems();
|
||||
|
||||
// Check for cancellation after getting items from extension
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// TODO we can probably further optimize this by also keeping a
|
||||
// HashSet of every ExtensionObject we currently have, and only
|
||||
// building new viewmodels for the ones we haven't already built.
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
// Check for cancellation during item processing
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
ListItemViewModel viewModel = new(item, PageContext);
|
||||
|
||||
// If an item fails to load, silently ignore it.
|
||||
if (viewModel.SafeFastInit())
|
||||
{
|
||||
newViewModels.Add(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cancellation before initializing first twenty items
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var firstTwenty = newViewModels.Take(20);
|
||||
foreach (var item in firstTwenty)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
item?.SafeInitializeProperties();
|
||||
}
|
||||
|
||||
// Cancel any ongoing search
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
// Check for cancellation before updating the list
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
List<ListItemViewModel> removedItems = [];
|
||||
lock (_listLock)
|
||||
{
|
||||
// Now that we have new ViewModels for everything from the
|
||||
// extension, smartly update our list of VMs
|
||||
ListHelpers.InPlaceUpdateList(Items, newViewModels, out removedItems);
|
||||
|
||||
// DO NOT ThrowIfCancellationRequested AFTER THIS! If you do,
|
||||
// you'll clean up list items that we've now transferred into
|
||||
// .Items
|
||||
}
|
||||
|
||||
// If we removed items, we need to clean them up, to remove our event handlers
|
||||
foreach (var removedItem in removedItems)
|
||||
{
|
||||
removedItem.SafeCleanup();
|
||||
}
|
||||
|
||||
// TODO: Iterate over everything in Items, and prune items from the
|
||||
// cache if we don't need them anymore
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Cancellation is expected, don't treat as error
|
||||
|
||||
// However, if we were cancelled, we didn't actually add these items to
|
||||
// our Items list. Before we release them to the GC, make sure we clean
|
||||
// them up
|
||||
foreach (var vm in newViewModels)
|
||||
{
|
||||
vm.SafeCleanup();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: Move this within the for loop, so we can catch issues with individual items
|
||||
// Create a special ListItemViewModel for errors and use an ItemTemplateSelector in the ListPage to display error items differently.
|
||||
ShowException(ex, "Content List");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isFetching = false;
|
||||
}
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_initializeItemsTask = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeItemsTask(_cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
});
|
||||
_initializeItemsTask.Start();
|
||||
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
// Now that our Items contains everything we want, it's time for us to
|
||||
// re-evaluate our Filter on those items.
|
||||
if (!_isDynamic)
|
||||
{
|
||||
// A static list? Great! Just run the filter.
|
||||
ApplyFilterUnderLock();
|
||||
}
|
||||
else
|
||||
{
|
||||
// A dynamic list? Even better! Just stick everything into
|
||||
// FilteredItems. The extension already did any filtering it cared about.
|
||||
ListHelpers.InPlaceUpdateList(FilteredItems, Items.Where(i => !i.IsInErrorState));
|
||||
}
|
||||
|
||||
UpdateEmptyContent();
|
||||
}
|
||||
|
||||
ItemsUpdated?.Invoke(this, EventArgs.Empty);
|
||||
_isLoading.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeItemsTask(CancellationToken ct)
|
||||
{
|
||||
// Were we already canceled?
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
ListItemViewModel[] iterable;
|
||||
lock (_listLock)
|
||||
{
|
||||
iterable = Items.ToArray();
|
||||
}
|
||||
|
||||
foreach (var item in iterable)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// TODO: GH #502
|
||||
// We should probably remove the item from the list if it
|
||||
// entered the error state. I had issues doing that without having
|
||||
// multiple threads muck with `Items` (and possibly FilteredItems!)
|
||||
// at once.
|
||||
item.SafeInitializeProperties();
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply our current filter text to the list of items, and update
|
||||
/// FilteredItems to match the results.
|
||||
/// </summary>
|
||||
private void ApplyFilterUnderLock() => ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(Items, SearchTextBox));
|
||||
|
||||
/// <summary>
|
||||
/// Helper to generate a weighting for a given list item, based on title,
|
||||
/// subtitle, etc. Largely a copy of the version in ListHelpers, but
|
||||
/// operating on ViewModels instead of extension objects.
|
||||
/// </summary>
|
||||
private static int ScoreListItem(string query, CommandItemViewModel listItem)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, listItem.Subtitle);
|
||||
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
|
||||
}
|
||||
|
||||
private struct ScoredListItemViewModel
|
||||
{
|
||||
public int Score;
|
||||
public ListItemViewModel ViewModel;
|
||||
}
|
||||
|
||||
// Similarly stolen from ListHelpers.FilterList
|
||||
public static IEnumerable<ListItemViewModel> FilterList(IEnumerable<ListItemViewModel> items, string query)
|
||||
{
|
||||
var scores = items
|
||||
.Where(i => !i.IsInErrorState)
|
||||
.Select(li => new ScoredListItemViewModel() { ViewModel = li, Score = ScoreListItem(query, li) })
|
||||
.Where(score => score.Score > 0)
|
||||
.OrderByDescending(score => score.Score);
|
||||
return scores
|
||||
.Select(score => score.ViewModel);
|
||||
}
|
||||
|
||||
// InvokeItemCommand is what this will be in Xaml due to source generator
|
||||
// This is what gets invoked when the user presses <enter>
|
||||
[RelayCommand]
|
||||
private void InvokeItem(ListItemViewModel? item)
|
||||
{
|
||||
if (item is not null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.Command.Model, item.Model));
|
||||
}
|
||||
else if (ShowEmptyContent && EmptyContent.PrimaryCommand?.Model.Unsafe is not null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
|
||||
EmptyContent.PrimaryCommand.Command.Model,
|
||||
EmptyContent.PrimaryCommand.Model));
|
||||
}
|
||||
}
|
||||
|
||||
// This is what gets invoked when the user presses <ctrl+enter>
|
||||
[RelayCommand]
|
||||
private void InvokeSecondaryCommand(ListItemViewModel? item)
|
||||
{
|
||||
if (item is not null)
|
||||
{
|
||||
if (item.SecondaryCommand is not null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.SecondaryCommand.Command.Model, item.Model));
|
||||
}
|
||||
}
|
||||
else if (ShowEmptyContent && EmptyContent.SecondaryCommand?.Model.Unsafe is not null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
|
||||
EmptyContent.SecondaryCommand.Command.Model,
|
||||
EmptyContent.SecondaryCommand.Model));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void UpdateSelectedItem(ListItemViewModel? item)
|
||||
{
|
||||
if (_lastSelectedItem is not null)
|
||||
{
|
||||
_lastSelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
|
||||
}
|
||||
|
||||
if (item is not null)
|
||||
{
|
||||
SetSelectedItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearSelectedItem();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSelectedItem(ListItemViewModel item)
|
||||
{
|
||||
if (!item.SafeSlowInit())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// GH #322:
|
||||
// For inexplicable reasons, if you try updating the command bar and
|
||||
// the details on the same UI thread tick as updating the list, we'll
|
||||
// explode
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(item));
|
||||
|
||||
if (ShowDetails && item.HasDetails)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(item.Details));
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<HideDetailsMessage>();
|
||||
}
|
||||
});
|
||||
|
||||
_lastSelectedItem = item;
|
||||
_lastSelectedItem.PropertyChanged += SelectedItemPropertyChanged;
|
||||
}
|
||||
|
||||
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
var item = _lastSelectedItem;
|
||||
if (item is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// already on the UI thread here
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(item.Command):
|
||||
case nameof(item.SecondaryCommand):
|
||||
case nameof(item.AllCommands):
|
||||
case nameof(item.Name):
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(item));
|
||||
break;
|
||||
case nameof(item.Details):
|
||||
if (ShowDetails && item.HasDetails)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(item.Details));
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<HideDetailsMessage>();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearSelectedItem()
|
||||
{
|
||||
// GH #322:
|
||||
// For inexplicable reasons, if you try updating the command bar and
|
||||
// the details on the same UI thread tick as updating the list, we'll
|
||||
// explode
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null));
|
||||
WeakReferenceMessenger.Default.Send<HideDetailsMessage>();
|
||||
WeakReferenceMessenger.Default.Send<UpdateSuggestionMessage>(new(string.Empty));
|
||||
});
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
_isDynamic = model is IDynamicListPage;
|
||||
|
||||
IsGridView = model.GridProperties is not null;
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(GridProperties));
|
||||
|
||||
InitialSearchText = SearchText = model.SearchText;
|
||||
UpdateProperty(nameof(SearchText));
|
||||
UpdateProperty(nameof(InitialSearchText));
|
||||
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||
EmptyContent.SlowInitializeProperties();
|
||||
|
||||
FetchItems();
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
}
|
||||
|
||||
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
{
|
||||
if (gridProperties is IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
return new MediumGridPropertiesViewModel(mediumGridLayout);
|
||||
}
|
||||
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
return new GalleryGridPropertiesViewModel(galleryGridLayout);
|
||||
}
|
||||
else if (gridProperties is ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
return new SmallGridPropertiesViewModel(smallGridLayout);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void LoadMoreIfNeeded()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isLoading.Set())
|
||||
{
|
||||
return;
|
||||
|
||||
// NOTE: May miss newly available items until next scroll if model
|
||||
// state changes between our check and this reset
|
||||
}
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
// Execute all COM calls on background thread to avoid reentrancy issues with UI
|
||||
// with the UI thread when COM starts inner message pump
|
||||
try
|
||||
{
|
||||
if (model.HasMoreItems)
|
||||
{
|
||||
model.LoadMore();
|
||||
|
||||
// _isLoading flag will be set as a result of LoadMore,
|
||||
// which must raise ItemsChanged to end the loading.
|
||||
}
|
||||
else
|
||||
{
|
||||
_isLoading.Clear();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_isLoading.Clear();
|
||||
ShowException(ex, "Content List");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void FetchProperty(string propertyName)
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(GridProperties):
|
||||
IsGridView = model.GridProperties is not null;
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
break;
|
||||
case nameof(SearchText):
|
||||
SearchText = model.SearchText;
|
||||
break;
|
||||
case nameof(EmptyContent):
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||
EmptyContent.SlowInitializeProperties();
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
|
||||
private void UpdateEmptyContent()
|
||||
{
|
||||
UpdateProperty(nameof(ShowEmptyContent));
|
||||
if (!ShowEmptyContent || EmptyContent.Model.Unsafe is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(EmptyContent));
|
||||
|
||||
DoOnUiThread(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(EmptyContent));
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_fetchItemsCancellationTokenSource?.Cancel();
|
||||
_fetchItemsCancellationTokenSource?.Dispose();
|
||||
_fetchItemsCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
protected override void UnsafeCleanup()
|
||||
{
|
||||
base.UnsafeCleanup();
|
||||
|
||||
EmptyContent?.SafeCleanup();
|
||||
EmptyContent = new(new(null), PageContext); // necessary?
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_fetchItemsCancellationTokenSource?.Cancel();
|
||||
|
||||
lock (_listLock)
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
item.SafeCleanup();
|
||||
}
|
||||
|
||||
Items.Clear();
|
||||
foreach (var item in FilteredItems)
|
||||
{
|
||||
item.SafeCleanup();
|
||||
}
|
||||
|
||||
FilteredItems.Clear();
|
||||
}
|
||||
|
||||
var model = _model.Unsafe;
|
||||
if (model is not null)
|
||||
{
|
||||
model.ItemsChanged -= Model_ItemsChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSearchTextBox(string searchTextBox)
|
||||
{
|
||||
//// TODO: Just temp testing, need to think about where we want to filter, as AdvancedCollectionView in View could be done, but then grouping need CollectionViewSource, maybe we do grouping in view
|
||||
//// and manage filtering below, but we should be smarter about this and understand caching and other requirements...
|
||||
//// Investigate if we re-use src\modules\cmdpal\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\ListHelpers.cs InPlaceUpdateList and FilterList?
|
||||
|
||||
// Dynamic pages will handler their own filtering. They will tell us if
|
||||
// something needs to change, by raising ItemsChanged.
|
||||
if (_isDynamic)
|
||||
{
|
||||
// We're getting called on the UI thread.
|
||||
// Hop off to a BG thread to update the extension.
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_model.Unsafe is IDynamicListPage dynamic)
|
||||
{
|
||||
dynamic.SearchText = searchTextBox;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex, "Content List");
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// But for all normal pages, we should run our fuzzy match on them.
|
||||
lock (_listLock)
|
||||
{
|
||||
ApplyFilterUnderLock();
|
||||
}
|
||||
|
||||
ItemsUpdated?.Invoke(this, EventArgs.Empty);
|
||||
UpdateEmptyContent();
|
||||
_isLoading.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,15 +27,15 @@ public sealed partial class ListViewControl : UserControl,
|
||||
{
|
||||
private InputSource _lastInputSource;
|
||||
|
||||
public ListViewModel? ViewModel
|
||||
public IListViewModel? ViewModel
|
||||
{
|
||||
get => (ListViewModel?)GetValue(ViewModelProperty);
|
||||
get => (IListViewModel?)GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for ViewModel. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty ViewModelProperty =
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(ListViewModel), typeof(ListPage), new PropertyMetadata(null, OnViewModelChanged));
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(IListViewModel), typeof(ListViewControl), new PropertyMetadata(null, OnViewModelChanged));
|
||||
|
||||
private ListViewBase ItemView
|
||||
{
|
||||
@@ -78,7 +78,7 @@ public sealed partial class ListViewControl : UserControl,
|
||||
|
||||
// Called after we've finished updating the whole list for either a
|
||||
// GetItems or a change in the filter.
|
||||
private void Page_ItemsUpdated(ListViewModel sender, object args)
|
||||
private void Page_ItemsUpdated(IListViewModel sender, object args)
|
||||
{
|
||||
// If for some reason, we don't have a selected item, fix that.
|
||||
//
|
||||
@@ -105,21 +105,43 @@ public sealed partial class ListViewControl : UserControl,
|
||||
{
|
||||
if (e.ClickedItem is ListItemViewModel item)
|
||||
{
|
||||
if (_lastInputSource == InputSource.Keyboard)
|
||||
if (ViewModel is ListViewModel listViewModel)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
return;
|
||||
}
|
||||
if (_lastInputSource == InputSource.Keyboard)
|
||||
{
|
||||
listViewModel?.InvokeItemCommand.Execute(item);
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (settings.SingleClickActivates)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (settings.SingleClickActivates)
|
||||
{
|
||||
listViewModel?.InvokeItemCommand.Execute(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
listViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (ViewModel is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
if (_lastInputSource == InputSource.Keyboard)
|
||||
{
|
||||
contentListViewModel?.InvokeItemCommand.Execute(item);
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (settings.SingleClickActivates)
|
||||
{
|
||||
contentListViewModel?.InvokeItemCommand.Execute(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
contentListViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,7 +153,14 @@ public sealed partial class ListViewControl : UserControl,
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (!settings.SingleClickActivates)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(vm);
|
||||
if (ViewModel is ListViewModel listViewModel)
|
||||
{
|
||||
listViewModel?.InvokeItemCommand.Execute(vm);
|
||||
}
|
||||
else if (ViewModel is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
contentListViewModel?.InvokeItemCommand.Execute(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +172,14 @@ public sealed partial class ListViewControl : UserControl,
|
||||
var li = ItemView.SelectedItem as ListItemViewModel;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
vm?.UpdateSelectedItemCommand.Execute(li);
|
||||
if (vm is ListViewModel listViewModel)
|
||||
{
|
||||
listViewModel?.UpdateSelectedItemCommand.Execute(li);
|
||||
}
|
||||
else if (vm is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
contentListViewModel?.UpdateSelectedItemCommand.Execute(li);
|
||||
}
|
||||
});
|
||||
|
||||
// There's mysterious behavior here, where the selection seemingly
|
||||
@@ -183,7 +219,14 @@ public sealed partial class ListViewControl : UserControl,
|
||||
ItemView.SelectedItem = item;
|
||||
}
|
||||
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
if (ViewModel is ListViewModel listViewModel)
|
||||
{
|
||||
listViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
}
|
||||
else if (ViewModel is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
contentListViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
}
|
||||
|
||||
var pos = e.GetPosition(element);
|
||||
|
||||
@@ -261,11 +304,25 @@ public sealed partial class ListViewControl : UserControl,
|
||||
{
|
||||
if (ViewModel?.ShowEmptyContent ?? false)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(null);
|
||||
if (ViewModel is ListViewModel listViewModel)
|
||||
{
|
||||
listViewModel?.InvokeItemCommand.Execute(null);
|
||||
}
|
||||
else if (ViewModel is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
contentListViewModel?.InvokeItemCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
else if (ItemView.SelectedItem is ListItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
if (ViewModel is ListViewModel listViewModel)
|
||||
{
|
||||
listViewModel?.InvokeItemCommand.Execute(item);
|
||||
}
|
||||
else if (ViewModel is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
contentListViewModel?.InvokeItemCommand.Execute(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +369,14 @@ public sealed partial class ListViewControl : UserControl,
|
||||
ItemView.SelectedItem = item;
|
||||
}
|
||||
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
if (ViewModel is ListViewModel listViewModel)
|
||||
{
|
||||
listViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
}
|
||||
else if (ViewModel is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
contentListViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
}
|
||||
|
||||
if (!e.TryGetPosition(element, out var pos))
|
||||
{
|
||||
@@ -382,7 +446,14 @@ public sealed partial class ListViewControl : UserControl,
|
||||
{
|
||||
if (ItemView.SelectedItem is ListItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeSecondaryCommandCommand.Execute(item);
|
||||
if (ViewModel is ListViewModel listViewModel)
|
||||
{
|
||||
listViewModel?.InvokeSecondaryCommandCommand.Execute(item);
|
||||
}
|
||||
else if (ViewModel is ContentListViewModel contentListViewModel)
|
||||
{
|
||||
contentListViewModel?.InvokeSecondaryCommandCommand.Execute(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ public partial class ContentTemplateSelector : DataTemplateSelector
|
||||
|
||||
public DataTemplate? TreeTemplate { get; set; }
|
||||
|
||||
public DataTemplate? ListTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item)
|
||||
{
|
||||
return item is ContentViewModel element
|
||||
@@ -27,6 +29,7 @@ public partial class ContentTemplateSelector : DataTemplateSelector
|
||||
ContentFormViewModel => FormTemplate,
|
||||
ContentMarkdownViewModel => MarkdownTemplate,
|
||||
ContentTreeViewModel => TreeTemplate,
|
||||
ContentListViewModel => ListTemplate,
|
||||
_ => null,
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:markdownTextBlockRns="using:CommunityToolkit.WinUI.Controls.MarkdownTextBlockRns"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
@@ -37,12 +36,14 @@
|
||||
x:Key="ContentTemplateSelector"
|
||||
FormTemplate="{StaticResource FormContentTemplate}"
|
||||
MarkdownTemplate="{StaticResource MarkdownContentTemplate}"
|
||||
TreeTemplate="{StaticResource TreeContentTemplate}" />
|
||||
TreeTemplate="{StaticResource TreeContentTemplate}"
|
||||
ListTemplate="{StaticResource ListContentTemplate}" />
|
||||
<cmdpalUI:ContentTemplateSelector
|
||||
x:Key="NestedContentTemplateSelector"
|
||||
FormTemplate="{StaticResource NestedFormContentTemplate}"
|
||||
MarkdownTemplate="{StaticResource NestedMarkdownContentTemplate}"
|
||||
TreeTemplate="{StaticResource TreeContentTemplate}" />
|
||||
TreeTemplate="{StaticResource TreeContentTemplate}"
|
||||
ListTemplate="{StaticResource NestedListContentTemplate}" />
|
||||
|
||||
<DataTemplate x:Key="FormContentTemplate" x:DataType="viewModels:ContentFormViewModel">
|
||||
<Grid Margin="0,4,4,4" Padding="12,8,8,8">
|
||||
@@ -109,6 +110,20 @@
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- List content templates (non-nested) -->
|
||||
<DataTemplate x:Key="ListContentTemplate" x:DataType="viewModels:ContentListViewModel">
|
||||
<Grid Margin="0,4,4,4" Padding="0,0,0,0">
|
||||
<cmdPalControls:ListViewControl ViewModel="{x:Bind}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Nested list content template -->
|
||||
<DataTemplate x:Key="NestedListContentTemplate" x:DataType="viewModels:ContentListViewModel">
|
||||
<Grid>
|
||||
<cmdPalControls:ListViewControl ViewModel="{x:Bind}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
<Grid>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1938_19305)">
|
||||
<path d="M8 0.195312C8.73438 0.195312 9.44271 0.289062 10.125 0.476562C10.8073 0.664062 11.4427 0.934896 12.0312 1.28906C12.6198 1.64323 13.1589 2.0599 13.6484 2.53906C14.138 3.01823 14.5573 3.55729 14.9062 4.15625C15.2552 4.75521 15.5234 5.39323 15.7109 6.07031C15.8984 6.7474 15.9948 7.45573 16 8.19531C16 9.05469 15.8698 9.88281 15.6094 10.6797C15.349 11.4766 14.9766 12.2083 14.4922 12.875C14.0078 13.5417 13.4323 14.125 12.7656 14.625C12.099 15.125 11.3542 15.513 10.5312 15.7891C10.5156 15.7943 10.4922 15.7969 10.4609 15.7969C10.4297 15.7969 10.4062 15.7995 10.3906 15.8047C10.2708 15.8047 10.1745 15.7708 10.1016 15.7031C10.0286 15.6354 9.98958 15.5365 9.98438 15.4062V14.8516L9.99219 13.7656V13.2109C9.99219 12.9453 9.95312 12.6771 9.875 12.4062C9.79688 12.1354 9.65625 11.9089 9.45312 11.7266C10.0625 11.6589 10.5964 11.5339 11.0547 11.3516C11.513 11.1693 11.8932 10.9167 12.1953 10.5938C12.4974 10.2708 12.724 9.88021 12.875 9.42188C13.026 8.96354 13.1016 8.41667 13.1016 7.78125C13.1016 7.375 13.0365 6.98958 12.9062 6.625C12.776 6.26042 12.5677 5.92969 12.2812 5.63281C12.3385 5.48698 12.3802 5.33594 12.4062 5.17969C12.4323 5.02344 12.4453 4.86719 12.4453 4.71094C12.4453 4.50781 12.4245 4.30729 12.3828 4.10938C12.3411 3.91146 12.2812 3.71354 12.2031 3.51562C12.1875 3.50521 12.1719 3.5 12.1562 3.5C12.1406 3.5 12.1224 3.5 12.1016 3.5C11.9193 3.5 11.7344 3.52344 11.5469 3.57031C11.3594 3.61719 11.1771 3.68229 11 3.76562C10.8229 3.84896 10.6484 3.9375 10.4766 4.03125C10.3047 4.125 10.1458 4.22656 10 4.33594C9.34375 4.15365 8.67708 4.0625 8 4.0625C7.32292 4.0625 6.65625 4.15365 6 4.33594C5.84896 4.23698 5.6901 4.13802 5.52344 4.03906C5.35677 3.9401 5.18229 3.85156 5 3.77344C4.81771 3.69531 4.63542 3.63021 4.45312 3.57812C4.27083 3.52604 4.08594 3.5 3.89844 3.5H3.85156C3.83594 3.5 3.81771 3.50521 3.79688 3.51562C3.72396 3.70833 3.66667 3.90365 3.625 4.10156C3.58333 4.29948 3.5599 4.5026 3.55469 4.71094C3.55469 4.86719 3.56771 5.02344 3.59375 5.17969C3.61979 5.33594 3.66146 5.48698 3.71875 5.63281C3.4375 5.92969 3.23177 6.26042 3.10156 6.625C2.97135 6.98958 2.90365 7.375 2.89844 7.78125C2.89844 8.40625 2.97396 8.95052 3.125 9.41406C3.27604 9.8776 3.5026 10.2682 3.80469 10.5859C4.10677 10.9036 4.48438 11.1589 4.9375 11.3516C5.39062 11.5443 5.92448 11.6719 6.53906 11.7344C6.38802 11.8698 6.27344 12.0312 6.19531 12.2188C6.11719 12.4062 6.0625 12.6016 6.03125 12.8047C5.89062 12.8724 5.74219 12.9245 5.58594 12.9609C5.42969 12.9974 5.27344 13.0156 5.11719 13.0156C4.8724 13.0156 4.66667 12.974 4.5 12.8906C4.33333 12.8073 4.1875 12.7031 4.0625 12.5781C3.9375 12.4531 3.82031 12.3151 3.71094 12.1641C3.60156 12.013 3.49219 11.8776 3.38281 11.7578C3.27344 11.638 3.14583 11.5312 3 11.4375C2.85417 11.3438 2.67969 11.3021 2.47656 11.3125H2.38281C2.34115 11.3125 2.30208 11.3203 2.26562 11.3359C2.22917 11.3516 2.19271 11.3672 2.15625 11.3828C2.11979 11.3984 2.10417 11.4245 2.10938 11.4609C2.10938 11.5339 2.14844 11.5964 2.22656 11.6484C2.30469 11.7005 2.36979 11.75 2.42188 11.7969C2.54688 11.8906 2.65104 11.9792 2.73438 12.0625C2.81771 12.1458 2.89062 12.2318 2.95312 12.3203C3.01562 12.4089 3.07292 12.5026 3.125 12.6016C3.17708 12.7005 3.23958 12.8229 3.3125 12.9688C3.51042 13.3802 3.76042 13.6771 4.0625 13.8594C4.36458 14.0417 4.74479 14.1328 5.20312 14.1328C5.33854 14.1328 5.47396 14.125 5.60938 14.1094C5.74479 14.0938 5.88021 14.0703 6.01562 14.0391V15.3984C6.01562 15.5234 5.97917 15.6224 5.90625 15.6953C5.83333 15.7682 5.73177 15.8047 5.60156 15.8047C5.55469 15.8047 5.51042 15.7995 5.46875 15.7891C4.65104 15.5182 3.90625 15.1328 3.23438 14.6328C2.5625 14.1328 1.98698 13.5495 1.50781 12.8828C1.02865 12.2161 0.658854 11.4844 0.398438 10.6875C0.138021 9.89062 0.00520833 9.0599 0 8.19531C0 7.46094 0.09375 6.7526 0.28125 6.07031C0.46875 5.38802 0.739583 4.7526 1.09375 4.16406C1.44792 3.57552 1.86458 3.03646 2.34375 2.54688C2.82292 2.05729 3.36198 1.63802 3.96094 1.28906C4.5599 0.940104 5.19792 0.671875 5.875 0.484375C6.55208 0.296875 7.26042 0.200521 8 0.195312Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1938_19305">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1938_19302)">
|
||||
<path d="M8 0.195312C8.73438 0.195312 9.44271 0.289062 10.125 0.476562C10.8073 0.664062 11.4427 0.934896 12.0312 1.28906C12.6198 1.64323 13.1589 2.0599 13.6484 2.53906C14.138 3.01823 14.5573 3.55729 14.9062 4.15625C15.2552 4.75521 15.5234 5.39323 15.7109 6.07031C15.8984 6.7474 15.9948 7.45573 16 8.19531C16 9.05469 15.8698 9.88281 15.6094 10.6797C15.349 11.4766 14.9766 12.2083 14.4922 12.875C14.0078 13.5417 13.4323 14.125 12.7656 14.625C12.099 15.125 11.3542 15.513 10.5312 15.7891C10.5156 15.7943 10.4922 15.7969 10.4609 15.7969C10.4297 15.7969 10.4062 15.7995 10.3906 15.8047C10.2708 15.8047 10.1745 15.7708 10.1016 15.7031C10.0286 15.6354 9.98958 15.5365 9.98438 15.4062V14.8516L9.99219 13.7656V13.2109C9.99219 12.9453 9.95312 12.6771 9.875 12.4062C9.79688 12.1354 9.65625 11.9089 9.45312 11.7266C10.0625 11.6589 10.5964 11.5339 11.0547 11.3516C11.513 11.1693 11.8932 10.9167 12.1953 10.5938C12.4974 10.2708 12.724 9.88021 12.875 9.42188C13.026 8.96354 13.1016 8.41667 13.1016 7.78125C13.1016 7.375 13.0365 6.98958 12.9062 6.625C12.776 6.26042 12.5677 5.92969 12.2812 5.63281C12.3385 5.48698 12.3802 5.33594 12.4062 5.17969C12.4323 5.02344 12.4453 4.86719 12.4453 4.71094C12.4453 4.50781 12.4245 4.30729 12.3828 4.10938C12.3411 3.91146 12.2812 3.71354 12.2031 3.51562C12.1875 3.50521 12.1719 3.5 12.1562 3.5C12.1406 3.5 12.1224 3.5 12.1016 3.5C11.9193 3.5 11.7344 3.52344 11.5469 3.57031C11.3594 3.61719 11.1771 3.68229 11 3.76562C10.8229 3.84896 10.6484 3.9375 10.4766 4.03125C10.3047 4.125 10.1458 4.22656 10 4.33594C9.34375 4.15365 8.67708 4.0625 8 4.0625C7.32292 4.0625 6.65625 4.15365 6 4.33594C5.84896 4.23698 5.6901 4.13802 5.52344 4.03906C5.35677 3.9401 5.18229 3.85156 5 3.77344C4.81771 3.69531 4.63542 3.63021 4.45312 3.57812C4.27083 3.52604 4.08594 3.5 3.89844 3.5H3.85156C3.83594 3.5 3.81771 3.50521 3.79688 3.51562C3.72396 3.70833 3.66667 3.90365 3.625 4.10156C3.58333 4.29948 3.5599 4.5026 3.55469 4.71094C3.55469 4.86719 3.56771 5.02344 3.59375 5.17969C3.61979 5.33594 3.66146 5.48698 3.71875 5.63281C3.4375 5.92969 3.23177 6.26042 3.10156 6.625C2.97135 6.98958 2.90365 7.375 2.89844 7.78125C2.89844 8.40625 2.97396 8.95052 3.125 9.41406C3.27604 9.8776 3.5026 10.2682 3.80469 10.5859C4.10677 10.9036 4.48438 11.1589 4.9375 11.3516C5.39062 11.5443 5.92448 11.6719 6.53906 11.7344C6.38802 11.8698 6.27344 12.0312 6.19531 12.2188C6.11719 12.4062 6.0625 12.6016 6.03125 12.8047C5.89062 12.8724 5.74219 12.9245 5.58594 12.9609C5.42969 12.9974 5.27344 13.0156 5.11719 13.0156C4.8724 13.0156 4.66667 12.974 4.5 12.8906C4.33333 12.8073 4.1875 12.7031 4.0625 12.5781C3.9375 12.4531 3.82031 12.3151 3.71094 12.1641C3.60156 12.013 3.49219 11.8776 3.38281 11.7578C3.27344 11.638 3.14583 11.5312 3 11.4375C2.85417 11.3438 2.67969 11.3021 2.47656 11.3125H2.38281C2.34115 11.3125 2.30208 11.3203 2.26562 11.3359C2.22917 11.3516 2.19271 11.3672 2.15625 11.3828C2.11979 11.3984 2.10417 11.4245 2.10938 11.4609C2.10938 11.5339 2.14844 11.5964 2.22656 11.6484C2.30469 11.7005 2.36979 11.75 2.42188 11.7969C2.54688 11.8906 2.65104 11.9792 2.73438 12.0625C2.81771 12.1458 2.89062 12.2318 2.95312 12.3203C3.01562 12.4089 3.07292 12.5026 3.125 12.6016C3.17708 12.7005 3.23958 12.8229 3.3125 12.9688C3.51042 13.3802 3.76042 13.6771 4.0625 13.8594C4.36458 14.0417 4.74479 14.1328 5.20312 14.1328C5.33854 14.1328 5.47396 14.125 5.60938 14.1094C5.74479 14.0938 5.88021 14.0703 6.01562 14.0391V15.3984C6.01562 15.5234 5.97917 15.6224 5.90625 15.6953C5.83333 15.7682 5.73177 15.8047 5.60156 15.8047C5.55469 15.8047 5.51042 15.7995 5.46875 15.7891C4.65104 15.5182 3.90625 15.1328 3.23438 14.6328C2.5625 14.1328 1.98698 13.5495 1.50781 12.8828C1.02865 12.2161 0.658854 11.4844 0.398438 10.6875C0.138021 9.89062 0.00520833 9.0599 0 8.19531C0 7.46094 0.09375 6.7526 0.28125 6.07031C0.46875 5.38802 0.739583 4.7526 1.09375 4.16406C1.44792 3.57552 1.86458 3.03646 2.34375 2.54688C2.82292 2.05729 3.36198 1.63802 3.96094 1.28906C4.5599 0.940104 5.19792 0.671875 5.875 0.484375C6.55208 0.296875 7.26042 0.200521 8 0.195312Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1938_19302">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 15C6.61553 15 5.26215 14.5895 4.11101 13.8203C2.95986 13.0511 2.06266 11.9579 1.53284 10.6788C1.00303 9.3997 0.86441 7.99223 1.1345 6.63437C1.4046 5.2765 2.07128 4.02922 3.05025 3.05025C4.02922 2.07128 5.2765 1.4046 6.63437 1.1345C7.99223 0.86441 9.3997 1.00303 10.6788 1.53284C11.9579 2.06266 13.0511 2.95986 13.8203 4.11101C14.5895 5.26215 15 6.61553 15 8C14.9976 9.85578 14.2594 11.6349 12.9471 12.9471C11.6349 14.2594 9.85578 14.9976 8 15ZM8 2C6.81331 2 5.65327 2.35189 4.66658 3.01118C3.67988 3.67047 2.91085 4.60754 2.45672 5.7039C2.0026 6.80026 1.88378 8.00665 2.11529 9.17054C2.3468 10.3344 2.91824 11.4035 3.75736 12.2426C4.59647 13.0818 5.66557 13.6532 6.82946 13.8847C7.99334 14.1162 9.19974 13.9974 10.2961 13.5433C11.3925 13.0892 12.3295 12.3201 12.9888 11.3334C13.6481 10.3467 14 9.18669 14 8C13.9984 6.40919 13.3658 4.88399 12.2409 3.75911C11.116 2.63424 9.59081 2.00159 8 2Z" fill="#3FB950"/>
|
||||
<path d="M10 8C10 8.39556 9.8827 8.78224 9.66294 9.11114C9.44318 9.44004 9.13082 9.69638 8.76537 9.84776C8.39991 9.99913 7.99778 10.0387 7.60982 9.96157C7.22186 9.8844 6.86549 9.69392 6.58579 9.41421C6.30608 9.13451 6.1156 8.77814 6.03843 8.39018C5.96126 8.00222 6.00086 7.60009 6.15224 7.23463C6.30362 6.86918 6.55996 6.55682 6.88886 6.33706C7.21776 6.1173 7.60444 6 8 6C8.53043 6 9.03914 6.21071 9.41421 6.58579C9.78929 6.96086 10 7.46957 10 8Z" fill="#3FB950"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.61561 4.92833C5.34246 5.33998 4.95291 5.66096 4.49661 5.85033C4.34861 5.91033 4.03861 5.98833 4.03861 5.98833V10.9963C4.68393 11.1294 5.25107 11.5108 5.61761 12.0583C5.89061 12.4703 6.03661 12.9533 6.03661 13.4463C6.04461 13.7893 5.97961 14.1303 5.84661 14.4463C5.65589 14.9093 5.32977 15.3039 4.91097 15.5783C4.49216 15.8527 4.00022 15.9943 3.49961 15.9843C3.00555 15.9859 2.52226 15.84 2.11161 15.5653C1.69965 15.2918 1.37857 14.9017 1.1894 14.4448C1.00024 13.9879 0.951567 13.485 1.04961 13.0003C1.14461 12.5143 1.38061 12.0683 1.72961 11.7173C2.07861 11.3743 2.51961 11.1383 2.99861 11.0373V5.94933C2.52004 5.84663 2.08017 5.61093 1.72961 5.26933C1.38115 4.91825 1.14452 4.47179 1.04961 3.98633C0.951366 3.50185 0.999732 2.99908 1.18853 2.54221C1.37734 2.08535 1.69801 1.69511 2.10961 1.42133C2.52081 1.14598 3.00474 0.999415 3.49961 1.00033C3.83424 0.994916 4.16656 1.05664 4.47693 1.18185C4.7873 1.30706 5.06942 1.49322 5.30661 1.72933C5.54359 1.9665 5.73029 2.24904 5.85555 2.56004C5.98081 2.87104 6.04205 3.20411 6.03561 3.53933C6.03761 4.03333 5.89161 4.51733 5.61561 4.92833ZM4.85961 12.7893C4.73142 12.5499 4.54063 12.3498 4.30761 12.2103C4.07564 12.0697 3.80887 11.9969 3.53761 12.0003C3.3159 11.9997 3.09681 12.0483 2.89623 12.1428C2.69565 12.2373 2.51859 12.3752 2.37788 12.5465C2.23716 12.7179 2.13633 12.9184 2.08266 13.1335C2.029 13.3486 2.02386 13.573 2.06761 13.7903C2.12463 14.082 2.26739 14.35 2.4776 14.56C2.68781 14.77 2.95592 14.9126 3.24761 14.9693C3.53561 15.0273 3.83361 14.9993 4.10761 14.8893C4.38361 14.7723 4.61961 14.5773 4.78761 14.3293C4.93761 14.1033 5.02261 13.8393 5.03661 13.5693C5.04879 13.2981 4.98765 13.0287 4.85961 12.7893ZM2.70761 4.74133C2.95461 4.90233 3.24361 4.99133 3.53761 4.99133C3.80861 4.99133 4.07561 4.91633 4.30761 4.78033C4.53974 4.6399 4.72987 4.43968 4.85811 4.2006C4.98636 3.96153 5.04801 3.69239 5.03661 3.42133C5.02298 3.15015 4.93663 2.88764 4.78661 2.66133C4.61883 2.41286 4.38264 2.21835 4.10661 2.10133C3.83413 1.99034 3.53489 1.9625 3.24661 2.02133C2.95503 2.07848 2.68711 2.22127 2.47709 2.43146C2.26708 2.64166 2.12451 2.9097 2.06761 3.20133C2.00961 3.48933 2.03761 3.78733 2.14761 4.06133C2.26461 4.33733 2.45961 4.57333 2.70761 4.74133ZM13.0366 11.0373C13.5166 11.1343 13.9586 11.3723 14.3056 11.7173C14.7716 12.1873 15.0346 12.8243 15.0306 13.4833C15.0326 13.9763 14.8866 14.4603 14.6106 14.8713C14.3365 15.2833 13.9462 15.6047 13.4892 15.7946C13.0323 15.9846 12.5292 16.0346 12.0438 15.9383C11.5584 15.842 11.1125 15.6038 10.7626 15.2538C10.4128 14.9038 10.1747 14.4578 10.0786 13.9723C9.98284 13.4872 10.0331 12.9845 10.223 12.528C10.413 12.0714 10.734 11.6814 11.1456 11.4073C11.4126 11.2243 11.7166 11.0993 12.0346 11.0373V5.48933C12.0343 5.09168 11.8762 4.7104 11.5949 4.42931C11.3136 4.14823 10.9323 3.99033 10.5346 3.99033H8.68661L9.95561 5.26033L9.24561 5.96933L7.11661 3.84033V3.14033L9.24661 1.01033L9.95661 1.72133L8.68761 2.99133H10.5376C11.0327 2.98855 11.5173 3.13376 11.9292 3.40834C12.3412 3.68291 12.6617 4.07432 12.8496 4.53233C12.9746 4.83433 13.0386 5.16033 13.0366 5.48933V11.0373ZM13.5936 14.5463C13.8399 14.3024 13.9936 13.9805 14.0285 13.6356C14.0633 13.2908 13.9771 12.9446 13.7846 12.6563C13.6166 12.4083 13.3805 12.2141 13.1046 12.0973C12.8321 11.9863 12.5329 11.9585 12.2446 12.0173C11.953 12.0744 11.685 12.2171 11.4749 12.4273C11.2649 12.6375 11.1224 12.9056 11.0656 13.1973C11.0068 13.4856 11.0346 13.7848 11.1456 14.0573C11.2388 14.2865 11.3873 14.4889 11.5779 14.6466C11.7685 14.8043 11.9953 14.9122 12.2378 14.9608C12.4804 15.0093 12.7312 14.997 12.9678 14.9248C13.2044 14.8527 13.4194 14.723 13.5936 14.5473V14.5463Z" fill="#3FB950"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.33301 2.83412C1.33301 2.00565 2.00458 1.33412 2.83301 1.33412L6.47431 1.33412C7.04887 1.33412 7.59987 1.56232 8.00621 1.96852L13.6749 7.63585C14.5195 8.48218 14.5201 9.85268 13.6759 10.7001L10.7058 13.6746C9.86054 14.5216 8.48861 14.5227 7.64181 13.6771L1.96857 8.01065C1.56165 7.60425 1.33301 7.05278 1.33301 6.47765L1.33301 2.83412ZM2.33301 2.83412L2.33301 6.47765C2.33301 6.78732 2.45612 7.08432 2.67523 7.30312L8.35687 12.9779C8.81347 13.4247 9.54574 13.4213 9.99814 12.9681L12.9678 9.99394C13.4227 9.53736 13.4223 8.79845 12.9675 8.34265L7.29914 2.67572C7.08041 2.45698 6.78367 2.33412 6.47431 2.33412L2.83301 2.33412C2.55687 2.33412 2.33301 2.55798 2.33301 2.83412ZM3.66779 4.66745C3.66779 4.11518 4.1155 3.66745 4.66778 3.66745C5.22005 3.66745 5.66776 4.11518 5.66776 4.66745C5.66776 5.21972 5.22005 5.66745 4.66778 5.66745C4.1155 5.66745 3.66779 5.21972 3.66779 4.66745Z" fill="#3FB950"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1011 B |
@@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
@@ -13,8 +14,12 @@ internal sealed partial class SampleContentPage : ContentPage
|
||||
{
|
||||
private readonly SampleContentForm sampleForm = new();
|
||||
private readonly MarkdownContent sampleMarkdown = new() { Body = "# Sample page with mixed content \n This page has both markdown, and form content" };
|
||||
private readonly SampleListContent sampleList = new()
|
||||
{
|
||||
GridProperties = new MediumGridLayout(),
|
||||
};
|
||||
|
||||
public override IContent[] GetContent() => [sampleMarkdown, sampleForm];
|
||||
public override IContent[] GetContent() => [sampleMarkdown, sampleForm, sampleList];
|
||||
|
||||
public SampleContentPage()
|
||||
{
|
||||
@@ -314,6 +319,72 @@ internal sealed partial class SampleContentForm : FormContent
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
|
||||
internal sealed partial class SampleListContent : ListContent
|
||||
{
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return [
|
||||
new ListItem(new NoOpCommand()
|
||||
{
|
||||
Name = "List item 1",
|
||||
Icon = new("\uF148"),
|
||||
}),
|
||||
new ListItem(
|
||||
new ToastCommand("Primary command invoked", MessageState.Info) { Name = "Primary command", Icon = new IconInfo("\uF146") }) // dial 1
|
||||
{
|
||||
Title = "You can add context menu items too. Press Ctrl+k",
|
||||
Subtitle = "Try pressing Ctrl+1 with me selected",
|
||||
Icon = new IconInfo("\uE712"), // "More" dots
|
||||
MoreCommands = [
|
||||
new CommandContextItem(
|
||||
new ToastCommand("Secondary command invoked", MessageState.Warning) { Name = "Secondary command", Icon = new IconInfo("\uF147") }) // dial 2
|
||||
{
|
||||
Title = "I'm a second command",
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
|
||||
},
|
||||
new Separator(),
|
||||
new CommandContextItem(
|
||||
new ToastCommand("Third command invoked", MessageState.Error) { Name = "Do 3", Icon = new IconInfo("\uF148") }) // dial 3
|
||||
{
|
||||
Title = "We can go deeper...",
|
||||
Icon = new IconInfo("\uF148"),
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number2),
|
||||
MoreCommands = [
|
||||
new CommandContextItem(
|
||||
new ToastCommand("Nested A invoked") { Name = "Do it", Icon = new IconInfo("A") })
|
||||
{
|
||||
Title = "Nested A",
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(alt: true, vkey: VirtualKey.A),
|
||||
},
|
||||
|
||||
new CommandContextItem(
|
||||
new ToastCommand("Nested B invoked") { Name = "Do it", Icon = new IconInfo("B") })
|
||||
{
|
||||
Title = "Nested B with a really, really long title that should be trimmed",
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.B),
|
||||
MoreCommands = [
|
||||
new CommandContextItem(
|
||||
new ToastCommand("Nested C invoked") { Name = "Do it" })
|
||||
{
|
||||
Title = "You get it",
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.B),
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
new ListItem(new NoOpCommand()
|
||||
{
|
||||
Name = "List item 3",
|
||||
Icon = new("\uF148"),
|
||||
})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
|
||||
internal sealed partial class SampleTreeContentPage : ContentPage
|
||||
{
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleStartPage : ContentPage
|
||||
{
|
||||
private readonly SampleAppListContent _appListContent = new();
|
||||
private readonly MarkdownContent sampleMarkdown = new() { Body = "# Microsoft PowerToys\n\n\n\n[How to use PowerToys][usingPowerToys-docs-link] | [Downloads & Release notes][github-release-link] | [Contributing to PowerToys](#contributing) | [What's Happening](#whats-happening) | [Roadmap](#powertoys-roadmap)\n\n## About\n\nMicrosoft PowerToys is a set of utilities for power users to tune and streamline their Windows experience for greater productivity. For more info on [PowerToys overviews and how to use the utilities][usingPowerToys-docs-link], or any other tools and resources for [Windows development environments](https://learn.microsoft.com/windows/dev-environment/overview), head over to [learn.microsoft.com][usingPowerToys-docs-link]!\n\n| | Current utilities: | |\n|--------------|--------------------|--------------|\n| [Advanced Paste](https://aka.ms/PowerToysOverview_AdvancedPaste) | [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) |\n| [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Command Palette](https://aka.ms/PowerToysOverview_CmdPal) |\n| [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) |\n| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) |\n| [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse Utilities](https://aka.ms/PowerToysOverview_MouseUtilities) |\n| [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [New+](https://aka.ms/PowerToysOverview_NewPlus) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) |\n| [Peek](https://aka.ms/PowerToysOverview_Peek) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |\n| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |\n| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Workspaces](https://aka.ms/PowerToysOverview_Workspaces) |\n| [ZoomIt](https://aka.ms/PowerToysOverview_ZoomIt) |\n\n## Installing and running Microsoft PowerToys\n\n### Requirements\n\n- Windows 11 or Windows 10 version 2004 (code name 20H1 / build number 19041) or newer.\n- x64 or ARM64 processor\n- Our installer will install the following items:\n - [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) bootstrapper. This will install the latest version." };
|
||||
|
||||
public override IContent[] GetContent() => [_appListContent, sampleMarkdown];
|
||||
|
||||
public SampleStartPage()
|
||||
{
|
||||
Name = "Open";
|
||||
Title = "Sample Repository";
|
||||
Icon = GitHubIcon.IconDictionary["logo"];
|
||||
|
||||
Commands = [
|
||||
new CommandContextItem(new OpenUrlCommand("https://github.com/microsoft/powertoys")
|
||||
{
|
||||
Name = "Open repository",
|
||||
Icon = GitHubIcon.IconDictionary["logo"],
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
|
||||
public static class GitHubIcon
|
||||
{
|
||||
public static Dictionary<string, IconInfo> IconDictionary { get; private set; }
|
||||
|
||||
static GitHubIcon()
|
||||
{
|
||||
IconDictionary = new Dictionary<string, IconInfo>
|
||||
{
|
||||
{ "issues", IconHelpers.FromRelativePath("Assets\\github\\issues.svg") },
|
||||
{ "pr", IconHelpers.FromRelativePath("Assets\\github\\pulls.svg") },
|
||||
{ "release", IconHelpers.FromRelativePath("Assets\\github\\releases.svg") },
|
||||
{ "logo", IconHelpers.FromRelativePaths("Assets\\github\\github.light.svg", "Assets\\github\\github.dark.svg") },
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetBase64Icon(string iconPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
var bytes = File.ReadAllBytes(iconPath);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
|
||||
internal sealed partial class SampleAppListContent : ListContent
|
||||
{
|
||||
private string baseUrl = "https://github.com/microsoft/powertoys/";
|
||||
|
||||
public SampleAppListContent()
|
||||
{
|
||||
GridProperties = new MediumGridLayout() { ShowTitle = true };
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return [
|
||||
new ListItem(new OpenUrlCommand($"{baseUrl}/issues")
|
||||
{
|
||||
Name = "Issues\n5,320",
|
||||
Icon = GitHubIcon.IconDictionary["issues"],
|
||||
}),
|
||||
new ListItem(new OpenUrlCommand($"{baseUrl}/pulls")
|
||||
{
|
||||
Name = "Pull Requests\n91",
|
||||
Icon = GitHubIcon.IconDictionary["pr"],
|
||||
}),
|
||||
new ListItem(new OpenUrlCommand($"{baseUrl}/releases")
|
||||
{
|
||||
Name = "Releases\nv0.94",
|
||||
Icon = GitHubIcon.IconDictionary["release"],
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,11 @@ public partial class SamplesListPage : ListPage
|
||||
Title = "Sample content page",
|
||||
Subtitle = "Display mixed forms, markdown, and other types of content",
|
||||
},
|
||||
new ListItem(new SampleStartPage())
|
||||
{
|
||||
Title = "Sample mixed content page",
|
||||
Subtitle = "Mix content types to create a 'start' for your extension",
|
||||
},
|
||||
new ListItem(new SampleTreeContentPage())
|
||||
{
|
||||
Title = "Sample nested content",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class DynamicListContent : ListContent, IDynamicListContent
|
||||
{
|
||||
public new string SearchText
|
||||
{
|
||||
get => base.SearchText;
|
||||
set
|
||||
{
|
||||
SetSearchNoUpdate(value);
|
||||
OnPropertyChanged(nameof(SearchText));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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 Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
// Base implementation of IListContent for extensions to derive from when exposing
|
||||
// list/grid data inside a ContentPage.
|
||||
public partial class ListContent : BaseObservable, IListContent
|
||||
{
|
||||
private string _placeholderText = string.Empty;
|
||||
private string _searchText = string.Empty;
|
||||
private bool _hasMore;
|
||||
private IGridProperties? _gridProperties;
|
||||
private ICommandItem? _emptyContent;
|
||||
|
||||
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;
|
||||
|
||||
public virtual string PlaceholderText
|
||||
{
|
||||
get => _placeholderText;
|
||||
set
|
||||
{
|
||||
_placeholderText = value;
|
||||
OnPropertyChanged(nameof(PlaceholderText));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
protected set
|
||||
{
|
||||
_searchText = value;
|
||||
OnPropertyChanged(nameof(SearchText));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool HasMoreItems
|
||||
{
|
||||
get => _hasMore;
|
||||
set
|
||||
{
|
||||
_hasMore = value;
|
||||
OnPropertyChanged(nameof(HasMoreItems));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IGridProperties? GridProperties
|
||||
{
|
||||
get => _gridProperties;
|
||||
set
|
||||
{
|
||||
_gridProperties = value;
|
||||
OnPropertyChanged(nameof(GridProperties));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual ICommandItem? EmptyContent
|
||||
{
|
||||
get => _emptyContent;
|
||||
set
|
||||
{
|
||||
_emptyContent = value;
|
||||
OnPropertyChanged(nameof(EmptyContent));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IListItem[] GetItems() => [];
|
||||
|
||||
public virtual void LoadMore()
|
||||
{
|
||||
}
|
||||
|
||||
protected void RaiseItemsChanged(int totalItems = -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
ItemsChanged?.Invoke(this, new ItemsChangedEventArgs(totalItems));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetSearchNoUpdate(string newSearchText)
|
||||
{
|
||||
_searchText = newSearchText;
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic variant allows the host to set SearchText.
|
||||
// Dynamic variant declared in separate file per style guidelines.
|
||||
@@ -340,6 +340,23 @@ namespace Microsoft.CommandPalette.Extensions
|
||||
IContent[] GetChildren();
|
||||
}
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IListContent requires IContent, INotifyItemsChanged {
|
||||
String SearchText { get; };
|
||||
IGridProperties GridProperties { get; };
|
||||
Boolean HasMoreItems { get; };
|
||||
ICommandItem EmptyContent { get; };
|
||||
|
||||
IListItem[] GetItems();
|
||||
void LoadMore();
|
||||
}
|
||||
|
||||
[uuid("B2C5A9E7-3F4D-5A3D-A018-AE2F6C5D5678")]
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IDynamicListContent requires IListContent {
|
||||
String SearchText { set; };
|
||||
}
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IContentPage requires IPage, INotifyItemsChanged {
|
||||
IContent[] GetContent();
|
||||
|
||||
Reference in New Issue
Block a user