Removed sections from the spec, prototype (#129)

As discussed. 

I'm getting rid of  ``ISection[]`` and replacing that with just a `String Section { get; }`[^1] property on `ListItem`'s themselves. 
 
I didn't want to bother wasting engineering resurrecting the `SectionListView` thing in the prototype without `ISection`s, so I'll just leave that for us to implement The Right Way in the MVVM exe. 
 
This should keep the prototype running, and keep us moving forward

[^1]: we discussed making this an object. I didn't here, but we probably can in the future.
This commit is contained in:
Mike Griese
2024-11-03 10:38:18 -06:00
committed by GitHub
parent 3ef20c5b23
commit 878692650a
38 changed files with 319 additions and 696 deletions

View File

@@ -45,27 +45,23 @@ internal sealed partial class HackerNewsPage : ListPage
return posts;
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
var t = DoGetItems();
t.ConfigureAwait(false);
return t.Result;
}
private async Task<ISection[]> DoGetItems()
private async Task<IListItem[]> DoGetItems()
{
List<NewsPost> items = await GetHackerNewsTopPosts();
this.Loading = false;
var s = new ListSection()
{
Title = "Posts",
Items = items.Select((post) => new ListItem(new LinkCommand(post))
var s = items.Select((post) => new ListItem(new LinkCommand(post))
{
Title = post.Title,
Subtitle = post.Link,
MoreCommands = [new CommandContextItem(new CommentCommand(post))],
}).ToArray(),
};
return [s];
}).ToArray();
return s;
}
}

View File

@@ -17,15 +17,10 @@ internal sealed partial class MastodonExtensionPage : ListPage
Name = "Mastodon";
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
return [
new ListSection()
{
Items = [
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" }
],
}
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" }
];
}
}

View File

@@ -11,7 +11,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Programs;
public sealed partial class AllAppsPage : ListPage
{
private ISection allAppsSection = new ListSection();
private IListItem[] allAppsSection = [];
public AllAppsPage()
{
@@ -23,22 +23,18 @@ public sealed partial class AllAppsPage : ListPage
this.PlaceholderText = "Search installed apps...";
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
if (this.allAppsSection == null || allAppsSection.Items.Length == 0)
if (this.allAppsSection == null || allAppsSection.Length == 0)
{
var apps = GetPrograms();
this.Loading = false;
this.allAppsSection = new ListSection()
{
Title = "Apps",
Items = apps
this.allAppsSection = apps
.Select((app) => new AppListItem(app))
.ToArray(),
};
.ToArray();
}
return [allAppsSection];
return allAppsSection;
}
internal static List<AppItem> GetPrograms()

View File

@@ -70,10 +70,8 @@ internal sealed partial class RegistryListPage : DynamicListPage
return new List<ListItem>();
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
ListItem[] items = Query(query).ToArray();
return new ISection[] { new ListSection() { Title = "Registry Keys", Items = items } };
return Query(query).ToArray();
}
}

View File

@@ -27,10 +27,10 @@ internal sealed partial class ServicesListPage : DynamicListPage
Name = "Windows Services";
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
ListItem[] items = ServiceHelper.Search(query).ToArray();
return new ISection[] { new ListSection() { Title = "Windows Services", Items = items } };
return items;
}
}

View File

@@ -101,10 +101,10 @@ internal sealed partial class WindowsSettingsListPage : DynamicListPage
}
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
ListItem[] items = Query(query).ToArray();
return new ISection[] { new ListSection() { Title = "Windows Settings", Items = items } };
return items;
}
}

View File

@@ -70,15 +70,9 @@ internal sealed partial class ProfilesListPage : ListPage
return result;
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
return [
new ListSection()
{
Title = "Profiles",
Items = Query().ToArray(),
}
];
return Query().ToArray();
}
private BitmapImage GetLogo(TerminalPackage terminal)

View File

@@ -19,19 +19,16 @@ internal sealed partial class ProcessListPage : ListPage
this.Name = "Process Monitor";
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
return DoGetItems();
}
private ISection[] DoGetItems()
private IListItem[] DoGetItems()
{
var items = GetRunningProcesses();
this.Loading = false;
var s = new ListSection()
{
Title = "Processes",
Items = items
var s = items
.OrderByDescending(p => p.Memory)
.Select((process) => new ListItem(new SwitchToProcess(process))
{
@@ -40,9 +37,8 @@ internal sealed partial class ProcessListPage : ListPage
MoreCommands = [
new CommandContextItem(new TerminateProcess(process))
],
}).ToArray(),
};
return [s];
}).ToArray();
return s;
}
private static IEnumerable<ProcessItem> GetRunningProcesses()

View File

@@ -59,26 +59,22 @@ internal sealed partial class SSHHostsListPage : ListPage
return hosts;
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
var t = DoGetItems();
t.ConfigureAwait(false);
return t.Result;
}
private async Task<ISection[]> DoGetItems()
private async Task<IListItem[]> DoGetItems()
{
List<SSHKeychainItem> items = await GetSSHHosts();
var s = new ListSection()
{
Title = "SSH Hosts",
Items = items.Select((host) => new ListItem(new LaunchSSHHostCommand(host))
var s = items.Select((host) => new ListItem(new LaunchSSHHostCommand(host))
{
Title = host.HostName,
Subtitle = host.EscapedHost,
MoreCommands = [new CommandContextItem(new OpenConfigFileCommand(_defaultConfigFile))],
}).ToArray(),
};
return [s];
}).ToArray();
return s;
}
}

View File

@@ -26,28 +26,22 @@ internal sealed partial class SampleDynamicListPage : DynamicListPage
Name = "SSH Keychain";
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
return [
new ListSection()
new ListItem(new NoOpCommand()) { Title = string.IsNullOrEmpty(query) ? "dynamic item" : query, Subtitle = "Notice how the title changes for this list item when you type in the filter box" },
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" },
new ListItem(new NoOpCommand()) { Title = "This one has a subtitle too", Subtitle = "Example Subtitle" },
new ListItem(new NoOpCommand())
{
Title = "Sample List Page",
Items = [
new ListItem(new NoOpCommand()) { Title = string.IsNullOrEmpty(query) ? "dynamic item" : query, Subtitle = "Notice how the title changes for this list item when you type in the filter box" },
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" },
new ListItem(new NoOpCommand()) { Title = "This one has a subtitle too", Subtitle = "Example Subtitle" },
new ListItem(new NoOpCommand())
{
Title = "This one has a tag too",
Subtitle = "the one with a tag",
Tags = [new Tag()
{
Text = "Sample Tag",
}
],
}
Title = "This one has a tag too",
Subtitle = "the one with a tag",
Tags = [new Tag()
{
Text = "Sample Tag",
}
],
}
];
];
}
}

View File

@@ -26,27 +26,21 @@ internal sealed partial class SampleListPage : ListPage
Name = "Sample List Page";
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
return [
new ListSection()
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" },
new ListItem(new SampleListPageWithDetails()) { Title = "This one has a subtitle too", Subtitle = "Example Subtitle" },
new ListItem(new SampleMarkdownPage())
{
Title = "Sample List Page",
Items = [
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" },
new ListItem(new SampleListPageWithDetails()) { Title = "This one has a subtitle too", Subtitle = "Example Subtitle" },
new ListItem(new SampleMarkdownPage())
{
Title = "This one has a tag too",
Subtitle = "the one with a tag",
Tags = [new Tag()
{
Text = "Sample Tag",
}
],
}
Title = "This one has a tag too",
Subtitle = "the one with a tag",
Tags = [new Tag()
{
Text = "Sample Tag",
}
],
}
];
];
}
}

View File

@@ -27,49 +27,43 @@ internal sealed partial class SampleListPageWithDetails : ListPage
this.ShowDetails = true;
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
return [
new ListSection()
new ListItem(new NoOpCommand())
{
Title = "Sample List Page",
Items = [
new ListItem(new NoOpCommand())
{
Title = "TODO: Implement your extension here",
Details = new Details()
{
Title = "List Item 1",
Body = "### Example of markdown details",
},
},
new ListItem(new NoOpCommand())
{
Title = "This one has a subtitle too",
Subtitle = "Example Subtitle",
Details = new Details()
{
Title = "List Item 2",
Body = "### Example of markdown details",
},
},
new ListItem(new NoOpCommand())
{
Title = "This one has a tag too",
Subtitle = "the one with a tag",
Tags = [new Tag()
{
Text = "Sample Tag",
}
],
Details = new Details()
{
Title = "List Item 3",
Body = "### Example of markdown details",
},
}
],
Title = "TODO: Implement your extension here",
Details = new Details()
{
Title = "List Item 1",
Body = "### Example of markdown details",
},
},
new ListItem(new NoOpCommand())
{
Title = "This one has a subtitle too",
Subtitle = "Example Subtitle",
Details = new Details()
{
Title = "List Item 2",
Body = "### Example of markdown details",
},
},
new ListItem(new NoOpCommand())
{
Title = "This one has a tag too",
Subtitle = "the one with a tag",
Tags = [new Tag()
{
Text = "Sample Tag",
}
],
Details = new Details()
{
Title = "List Item 3",
Body = "### Example of markdown details",
},
}
];
];
}
}

View File

@@ -16,15 +16,10 @@ internal sealed partial class TemplateExtensionPage : ListPage
Name = "TemplateDisplayName";
}
public override ISection[] GetItems()
public override IListItem[] GetItems()
{
return [
new ListSection()
{
Items = [
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" }
],
}
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" }
];
}
}

View File

@@ -37,45 +37,41 @@ internal sealed partial class YouTubeChannelVideosPage : DynamicListPage
_channelName = channelName;
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
return DoGetItems(query).GetAwaiter().GetResult(); // Fetch and await the task synchronously
}
private async Task<ISection[]> DoGetItems(string query)
private async Task<IListItem[]> DoGetItems(string query)
{
// Fetch YouTube videos scoped to the channel
List<YouTubeVideo> items = await GetYouTubeChannelVideos(query, _channelId, _channelName);
// Create a section and populate it with the video results
var section = new ListSection()
var section = items.Select(video => new ListItem(new OpenVideoLinkAction(video.Link))
{
Title = $"Videos from {_channelName ?? _channelId}",
Items = items.Select(video => new ListItem(new OpenVideoLinkAction(video.Link))
Title = video.Title,
Subtitle = $"{video.Channel}",
Details = new Details()
{
Title = video.Title,
Subtitle = $"{video.Channel}",
Details = new Details()
{
Title = video.Title,
HeroImage = new(video.ThumbnailUrl),
Body = $"{video.Channel}",
},
Tags = [
HeroImage = new(video.ThumbnailUrl),
Body = $"{video.Channel}",
},
Tags = [
new Tag()
{
Text = video.PublishedAt.ToString("MMMM dd, yyyy", CultureInfo.InvariantCulture), // Show the date of the video post
}
],
MoreCommands = [
MoreCommands = [
new CommandContextItem(new OpenChannelLinkAction(video.ChannelUrl)),
new CommandContextItem(new YouTubeVideoInfoMarkdownPage(video)),
new CommandContextItem(new YouTubeAPIPage()),
],
}).ToArray(),
};
}).ToArray();
return new[] { section }; // Properly return an array of sections
return section;
}
// Method to fetch videos from a specific channel

View File

@@ -25,39 +25,35 @@ internal sealed partial class YouTubeChannelsPage : DynamicListPage
this.ShowDetails = true;
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
return DoGetItems(query).GetAwaiter().GetResult(); // Fetch and await the task synchronously
}
private async Task<ISection[]> DoGetItems(string query)
private async Task<IListItem[]> DoGetItems(string query)
{
// Fetch YouTube channels based on the query
List<YouTubeChannel> items = await GetYouTubeChannels(query);
// Create a section and populate it with the channel results
var section = new ListSection()
var section = items.Select(channel => new ListItem(new OpenChannelLinkAction(channel.ChannelUrl))
{
Title = "Search Results",
Items = items.Select(channel => new ListItem(new OpenChannelLinkAction(channel.ChannelUrl))
Title = channel.Name,
Subtitle = $"{channel.SubscriberCount} subscribers",
Details = new Details()
{
Title = channel.Name,
Subtitle = $"{channel.SubscriberCount} subscribers",
Details = new Details()
{
Title = channel.Name,
HeroImage = new(channel.ProfilePicUrl),
Body = $"Subscribers: {channel.SubscriberCount}\nChannel Description: {channel.Description}",
},
MoreCommands = [
HeroImage = new(channel.ProfilePicUrl),
Body = $"Subscribers: {channel.SubscriberCount}\nChannel Description: {channel.Description}",
},
MoreCommands = [
new CommandContextItem(new YouTubeChannelInfoMarkdownPage(channel)),
new CommandContextItem(new YouTubeChannelVideosPage(channel.ChannelId, channel.Name)),
new CommandContextItem(new YouTubeAPIPage()),
],
}).ToArray(),
};
}).ToArray();
return new[] { section }; // Properly return an array of sections
return section;
}
// Method to fetch channels from YouTube API

View File

@@ -26,44 +26,40 @@ internal sealed partial class YouTubeVideosPage : DynamicListPage
this.ShowDetails = true;
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
return DoGetItems(query).GetAwaiter().GetResult(); // Fetch and await the task synchronously
}
private async Task<ISection[]> DoGetItems(string query)
private async Task<IListItem[]> DoGetItems(string query)
{
// Fetch YouTube videos based on the query
List<YouTubeVideo> items = await GetYouTubeVideos(query);
// Create a section and populate it with the video results
var section = new ListSection()
var section = items.Select(video => new ListItem(new OpenVideoLinkAction(video.Link))
{
Title = "Search Results",
Items = items.Select(video => new ListItem(new OpenVideoLinkAction(video.Link))
Title = video.Title,
Subtitle = $"{video.Channel}",
Details = new Details()
{
Title = video.Title,
Subtitle = $"{video.Channel}",
Details = new Details()
{
Title = video.Title,
HeroImage = new(video.ThumbnailUrl),
Body = $"{video.Channel}",
},
Tags = [new Tag()
HeroImage = new(video.ThumbnailUrl),
Body = $"{video.Channel}",
},
Tags = [new Tag()
{
Text = video.PublishedAt.ToString("MMMM dd, yyyy", CultureInfo.InvariantCulture), // Show the date of the video post
}
],
MoreCommands = [
MoreCommands = [
new CommandContextItem(new OpenChannelLinkAction(video.ChannelUrl)),
new CommandContextItem(new YouTubeVideoInfoMarkdownPage(video)),
new CommandContextItem(new YouTubeAPIPage()),
],
}).ToArray(),
};
}).ToArray();
return new[] { section }; // Properly return an array of sections
return section; // Properly return an array of sections
}
// Method to fetch videos from YouTube API

View File

@@ -14,28 +14,14 @@ namespace Microsoft.CmdPal.UI.Pages;
/// </summary>
public partial class MainListPage : DynamicListPage
{
private readonly ISection[] _sections;
private readonly IListItem[] _items;
// TODO: Thinking we may want a separate MainViewModel from the ShellViewModel and/or a CommandService/Provider
// which holds the TopLevelCommands and anything that needs to access those functions...
public MainListPage(ShellViewModel shellViewModel)
{
_sections = [new MainListSection()
{
Items = shellViewModel.TopLevelCommands.Select(w => w.Unsafe).Where(li => li != null).ToArray(),
}
];
_items = shellViewModel.TopLevelCommands.Select(w => w.Unsafe).Where(li => li != null).ToArray();
}
public override ISection[] GetItems() => _sections;
}
//// TODO: Temporary until we sort out proper PageViewModel and SectionViewModel containers/setup
#pragma warning disable SA1402 // File may only contain a single type
public partial class MainListSection : ISection
#pragma warning restore SA1402 // File may only contain a single type
{
public required IListItem[] Items { get; set; }
public string Title => "Commands"; // TODO: Localization
public override IListItem[] GetItems() => _items;
}

View File

@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using static System.Collections.Specialized.BitVector32;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -20,17 +21,16 @@ public partial class ListViewModel : ObservableObject
public ListViewModel(IListPage model)
{
foreach (var section in model.GetItems())
// TEMPORARY: just plop all the items into a single group
// see 9806fe5d8 for the last commit that had this with sections
ObservableGroup<string, ListItemViewModel> group = new(string.Empty);
foreach (var item in model.GetItems())
{
ObservableGroup<string, ListItemViewModel> group = new(section.Title);
foreach (var item in section.Items)
{
group.Add(new(item));
}
Items.AddGroup(group);
group.Add(new(item));
}
Items.AddGroup(group);
}
// InvokeItemCommand is what this will be in Xaml due to source generator

View File

@@ -2,7 +2,7 @@
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PathToRoot>..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.6.240829007</WasdkNuget>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.6.240923002</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WebView2Nuget>$(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2739.15</WebView2Nuget>
</PropertyGroup>

View File

@@ -13,20 +13,23 @@ namespace WindowsCommandPalette;
// The FilteredListSection is for when we've got any filter at all. It starts by
// enumerating all actions and apps, and returns the subset that matches.
public sealed partial class FilteredListSection : ISection, INotifyCollectionChanged
//
// Although the concept of ISection's is vestigial, this class is still helpful
// for encapsulating the filtering of the main page, which has weird logic for
// adding apps or not.
public sealed partial class FilteredListSection
{
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public string Title => string.Empty;
private readonly MainViewModel _mainViewModel;
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
// Top-level list items, from builtin commands and extensions
public ObservableCollection<MainListItem> TopLevelItems { get; set; }
// (This is owned by MainListPage)
public ObservableCollection<MainListItem> TopLevelItems { get; init; }
// Apps, from the apps built in command.
private IEnumerable<IListItem> AllApps => _mainViewModel.Apps.GetItems().First().Items;
private IEnumerable<IListItem> AllApps => _mainViewModel.Apps.GetItems();
// Results from the last searched text
private IEnumerable<IListItem>? lastSearchResults;
@@ -80,21 +83,10 @@ public sealed partial class FilteredListSection : ISection, INotifyCollectionCha
// results.
public IListItem[] Items => ItemsToEnumerate.Where(i => i != null).ToArray();
public FilteredListSection(MainViewModel viewModel)
public FilteredListSection(MainViewModel viewModel, ObservableCollection<MainListItem> topLevelItems)
{
this._mainViewModel = viewModel;
// TODO: We should probably just get rid of MainListItem entirely, so I'm leaveing these uncaught
TopLevelItems = new(_mainViewModel.TopLevelCommands.Where(wrapper => wrapper.Unsafe != null).Select(wrapper => new MainListItem(wrapper.Unsafe!)));
TopLevelItems.CollectionChanged += Bubble_CollectionChanged;
}
private void Bubble_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
_dispatcherQueue.TryEnqueue(() =>
{
CollectionChanged?.Invoke(this, e);
});
this.TopLevelItems = topLevelItems;
}
internal void Reset()

View File

@@ -2,7 +2,9 @@
// 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 System.Collections.Specialized;
using System.Diagnostics;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using WindowsCommandPalette.Models;
@@ -13,65 +15,75 @@ namespace WindowsCommandPalette;
public sealed partial class MainListPage : DynamicListPage
{
private readonly MainViewModel _mainViewModel;
private readonly MainListSection _mainSection;
private readonly RecentsListSection _recentsListSection;
private readonly FilteredListSection _filteredSection;
private readonly ISection[] _sections;
private readonly ObservableCollection<MainListItem> topLevelItems = new();
public MainListPage(MainViewModel viewModel)
{
this._mainViewModel = viewModel;
_mainSection = new(_mainViewModel);
_recentsListSection = new(_mainViewModel);
_filteredSection = new(_mainViewModel);
// wacky: "All apps" is added to _mainViewModel.TopLevelCommands before
// we're constructed, so we never get a
// TopLevelCommands_CollectionChanged callback when we're first launched
// that would let us add it
foreach (var i in _mainViewModel.TopLevelCommands)
{
this.topLevelItems.Add(new MainListItem(i.Unsafe));
}
// We're using a FilteredListSection to help abstract some of dealing with
// filtering the list of commands & apps. It's just a little more convenient.
// It's not an actual section, just vestigial from that era.
//
// Let the FilteredListSection use our TopLevelItems. That way we don't
// need to maintain two lists.
_filteredSection = new(_mainViewModel, this.topLevelItems);
// Listen for changes to the TopLevelCommands. This happens as we async
// load them on startup. We'll use CollectionChanged as an opportunity
// to raise the 'Items' changed event.
_mainViewModel.TopLevelCommands.CollectionChanged += TopLevelCommands_CollectionChanged;
_sections = [
_recentsListSection,
_mainSection
];
PlaceholderText = "Search...";
ShowDetails = true;
Loading = false;
}
public override ISection[] GetItems()
{
return _sections;
}
public override ISection[] GetItems(string query)
public override IListItem[] GetItems(string query)
{
_filteredSection.Query = query;
_mainSection.UpdateQuery(query);
var fallbacks = topLevelItems
.Select(i => i?.FallbackHandler)
.Where(fb => fb != null)
.Select(fb => fb!);
foreach (var fb in fallbacks)
{
fb.UpdateQuery(query);
}
if (string.IsNullOrEmpty(query))
{
return _sections;
return topLevelItems.ToArray();
}
else
{
return [_filteredSection];
return _filteredSection.Items;
}
}
private void TopLevelCommands_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("TopLevelCommands_CollectionChanged");
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
{
foreach (var item in e.NewItems)
{
if (item is ExtensionObject<IListItem> listItem)
{
// Eh, it's fine to be unsafe here, we're probably tossing MainListItem
if (!_mainViewModel.Recent.Contains(listItem))
{
_mainSection.TopLevelItems.Add(new MainListItem(listItem.Unsafe));
}
_filteredSection.TopLevelItems.Add(new MainListItem(listItem.Unsafe));
topLevelItems.Add(new MainListItem(listItem.Unsafe));
}
}
}
@@ -79,25 +91,20 @@ public sealed partial class MainListPage : DynamicListPage
{
foreach (var item in e.OldItems)
{
if (item is ExtensionObject<IListItem> listItem)
if (item is ExtensionObject<IListItem> _)
{
foreach (var mainListItem in _mainSection.TopLevelItems)
{
if (mainListItem.Item == listItem)
{
_mainSection.TopLevelItems.Remove(mainListItem);
break;
}
}
// If we were maintaining the POC project we'd remove the items here.
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
_mainSection.Reset();
_filteredSection.Reset();
topLevelItems.Clear();
}
_recentsListSection.Reset();
// Sneaky?
// Raise a Items changed event, so the list page knows that our items
// have changed, and it should re-fetch them.
this.OnPropertyChanged("Items");
}
}

View File

@@ -11,7 +11,7 @@ using WindowsCommandPalette.Views;
namespace WindowsCommandPalette;
// The MainListSection is for all non-recent actions. No apps.
public sealed partial class MainListSection : ISection, INotifyCollectionChanged
public sealed partial class MainListSection : /*ISection, */INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler? CollectionChanged;
@@ -29,7 +29,7 @@ public sealed partial class MainListSection : ISection, INotifyCollectionChanged
// * OR one of:
// * Just the top-level actions (if there's no query)
// * OR the top-level actions AND the apps (if there's a query)
private IEnumerable<IListItem> TopLevelItemsToEnumerate => TopLevelItems.Where(i => i != null && (!_mainViewModel.IsRecentCommand(i)));
private IEnumerable<IListItem> TopLevelItemsToEnumerate => TopLevelItems.Where(i => i != null /*&& (!_mainViewModel.IsRecentCommand(i))*/);
// Watch out future me!
//

View File

@@ -1,72 +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.Collections.ObjectModel;
using System.Collections.Specialized;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using Microsoft.UI.Dispatching;
using WindowsCommandPalette.Views;
namespace WindowsCommandPalette;
public sealed partial class RecentsListSection : ListSection, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler? CollectionChanged;
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private readonly MainViewModel _mainViewModel;
private readonly ObservableCollection<MainListItem> _items = [];
private bool loadedApps;
public RecentsListSection(MainViewModel viewModel)
{
Title = "Recent";
_mainViewModel = viewModel;
var recent = _mainViewModel.RecentActions;
Reset();
_items.CollectionChanged += Bubble_CollectionChanged;
_mainViewModel.AppsReady += MainViewModel_AppsReady;
}
private void MainViewModel_AppsReady(object sender, object? args)
{
loadedApps = true;
_mainViewModel.AppsReady -= MainViewModel_AppsReady;
AddApps();
}
private void Bubble_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
_dispatcherQueue.TryEnqueue(() =>
{
CollectionChanged?.Invoke(this, e);
});
}
public override IListItem[] Items => _items.ToArray();
internal void Reset()
{
_items.Clear();
if (loadedApps)
{
AddApps();
}
}
internal void AddApps()
{
var apps = _mainViewModel.Recent;
foreach (var app in apps)
{
_items.Add(new MainListItem(app.Unsafe)); // we know these are all local
}
}
}

View File

@@ -2,6 +2,8 @@
// 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;
using System.Runtime.InteropServices;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.Extensions;
using Windows.Win32;
@@ -47,7 +49,24 @@ public sealed class CommandProviderWrapper
return;
}
var t = new Task<IListItem[]>(() => CommandProvider.TopLevelCommands());
var t = new Task<IListItem[]>(() =>
{
try
{
return CommandProvider.TopLevelCommands();
}
catch (COMException e)
{
if (extensionWrapper != null)
{
Debug.WriteLine($"Error loading commands from {extensionWrapper.ExtensionDisplayName}", "error");
}
Debug.WriteLine(e.ToString(), "error");
}
return [];
});
t.Start();
var commands = await t.ConfigureAwait(false);

View File

@@ -19,7 +19,7 @@
<ResourceDictionary>
<converters:StringVisibilityConverter x:Key="StringNotEmptyToVisibilityConverter" EmptyValue="Collapsed" NotEmptyValue="Visible" />
<converters:BoolToVisibilityConverter x:Key="ReverseBoolToVisibilityConverter" TrueValue="Collapsed" FalseValue="Visible" />
<CollectionViewSource x:Name="ItemsCVS" IsSourceGrouped="True" />
<StackLayout
x:Name="HorizontalStackLayout"
Orientation="Horizontal"
@@ -242,29 +242,9 @@
Margin="4,0,0,0"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource ListItemTemplate}"
ItemsSource="{x:Bind ItemsCVS.View, Mode=OneWay}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
SelectionChanged="ItemsList_SelectionChanged"
Style="{StaticResource NoAnimationsPlease}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel AreStickyGroupHeadersEnabled="False" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle HeaderContainerStyle="{StaticResource CustomHeaderContainerStyle}" HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="local:SectionInfoList">
<TextBlock
Padding="20,12,0,8"
AutomationProperties.AccessibilityView="Raw"
FontSize="14"
FontWeight="SemiBold"
Text="{x:Bind Title}"
Visibility="{x:Bind Title, Converter={StaticResource StringNotEmptyToVisibilityConverter}, Mode=OneWay}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.CmdPal.Extensions.Helpers;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
@@ -89,7 +90,6 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
DispatcherQueue.TryEnqueue(async () => { await UpdateFilter(FilterBox.Text); });
}
this.ItemsCVS.Source = ViewModel?.FilteredItems;
this.ItemsList.SelectedIndex = 0;
}
@@ -286,50 +286,20 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP
return;
}
// ViewModel.Query = text;
Debug.WriteLine($"UpdateFilter({text})");
// This first part will first filter all the commands that were passed
// into us initially. We handle the filtering of these ones. Commands
// from async querying happens later.
var newMatches = await ViewModel.GetFilteredItems(text);
// Go ask the ViewModel for the items to display. This might:
// * do an async request to the extension (fixme after GH #77)
// * just return already filtered items.
// * return a subset of items matching the filter text
var items = await ViewModel.GetFilteredItems(text);
// this.ItemsCVS.Source = ViewModel.FilteredItems;
// Returns back on the UI thread
ListHelpers.InPlaceUpdateList(ViewModel.FilteredItems, newMatches);
Debug.WriteLine($" UpdateFilter after GetFilteredItems({text}) --> {items.Count()} ; {ViewModel.FilteredItems.Count}");
/*
// for (var i = 0; i < ViewModel.FilteredItems.Count && i < newMatches.Count; i++)
// {
// for (var j = i; j < ViewModel.FilteredItems.Count; j++)
// {
// if (ViewModel.FilteredItems[j] == newMatches[i])
// {
// for (var k = i; k < j; k++)
// {
// ViewModel.FilteredItems.RemoveAt(i);
// }
// break;
// }
// }
// if (ViewModel.FilteredItems[i] != newMatches[i])
// {
// ViewModel.FilteredItems.Insert(i, newMatches[i]);
// }
// }
// // Remove any extra trailing items from the destination
// while (ViewModel.FilteredItems.Count > newMatches.Count)
// {
// ViewModel.FilteredItems.RemoveAt(ViewModel.FilteredItems.Count - 1);//RemoveAtEnd
// }
// // Add any extra trailing items from the source
// while (ViewModel.FilteredItems.Count < newMatches.Count)
// {
// ViewModel.FilteredItems.Add(newMatches[ViewModel.FilteredItems.Count]);
// }
*/
// Here, actually populate ViewModel.FilteredItems
// WARNING: if you do this off the UI thread, it sure won't work right.
ListHelpers.InPlaceUpdateList(ViewModel.FilteredItems, new(items.ToList()));
Debug.WriteLine($" UpdateFilter after InPlaceUpdateList --> {ViewModel.FilteredItems.Count}");
// set the selected index to the first item in the list
if (ItemsList.Items.Count > 0)

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
@@ -12,9 +13,9 @@ namespace WindowsCommandPalette.Views;
public sealed class ListPageViewModel : PageViewModel
{
private readonly ObservableCollection<SectionInfoList> _items = [];
private readonly ObservableCollection<ListItemViewModel> _items = [];
public ObservableCollection<SectionInfoList> FilteredItems { get; set; } = [];
public ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
internal IListPage Page => (IListPage)this.PageAction;
@@ -32,6 +33,19 @@ public sealed class ListPageViewModel : PageViewModel
public ListPageViewModel(IListPage page)
: base(page)
{
page.PropChanged += Page_PropChanged;
}
private void Page_PropChanged(object sender, PropChangedEventArgs args)
{
if (args.PropertyName == "Items")
{
Debug.WriteLine("Items changed");
_dispatcherQueue.TryEnqueue(async () =>
{
await this.UpdateListItems();
});
}
}
internal Task InitialRender()
@@ -42,7 +56,7 @@ public sealed class ListPageViewModel : PageViewModel
internal async Task UpdateListItems()
{
// on main thread
var t = new Task<ISection[]>(() =>
var t = new Task<IListItem[]>(() =>
{
try
{
@@ -54,16 +68,11 @@ public sealed class ListPageViewModel : PageViewModel
{
System.Diagnostics.Debug.WriteLine(ex);
_forceShowDetails = true;
return [new ListSection()
{
Title = "Error",
Items = [new ErrorListItem(ex)],
}
];
return [new ErrorListItem(ex)];
}
});
t.Start();
var sections = await t;
var items = await t;
// still on main thread
@@ -71,38 +80,21 @@ public sealed class ListPageViewModel : PageViewModel
// we already have, then rebuilding it. We shouldn't do that. We should
// still use the results from GetItems and put them into the code in
// UpdateFilter to intelligently add/remove as needed.
// Items.Clear();
// FilteredItems.Clear();
Collection<SectionInfoList> newItems = new();
// TODODO! are we still? ^^
Collection<ListItemViewModel> newItems = new(items.Select(i => new ListItemViewModel(i)).ToList());
Debug.WriteLine($" Found {newItems.Count} items");
var size = sections.Length;
for (var sectionIndex = 0; sectionIndex < size; sectionIndex++)
{
var section = sections[sectionIndex];
var sectionItems = new SectionInfoList(
section,
section.Items
.Where(i => i != null && !string.IsNullOrEmpty(i.Title))
.Select(i => new ListItemViewModel(i)));
// var items = section.Items;
// for (var i = 0; i < items.Length; i++) {
// ListItemViewModel vm = new(items[i]);
// Items.Add(vm);
// FilteredItems.Add(vm);
// }
newItems.Add(sectionItems);
// Items.Add(sectionItems);
// FilteredItems.Add(sectionItems);
}
ListHelpers.InPlaceUpdateList(_items, newItems);
// THIS populates FilteredItems. If you do this off the UI thread, guess what -
// the list view won't update. So WATCH OUT
ListHelpers.InPlaceUpdateList(FilteredItems, newItems);
ListHelpers.InPlaceUpdateList(_items, newItems);
Debug.WriteLine($"Done with UpdateListItems, found {FilteredItems.Count} / {_items.Count}");
}
internal async Task<Collection<SectionInfoList>> GetFilteredItems(string query)
internal async Task<IEnumerable<ListItemViewModel>> GetFilteredItems(string query)
{
// This method does NOT change any lists. It doesn't modify _items or FilteredItems...
if (query == _query)
{
return FilteredItems;
@@ -111,6 +103,7 @@ public sealed class ListPageViewModel : PageViewModel
_query = query;
if (IsDynamic)
{
// ... except here we might modify those lists. But ignore that for now, GH #77 will fix this.
await UpdateListItems();
return FilteredItems;
}
@@ -122,24 +115,14 @@ public sealed class ListPageViewModel : PageViewModel
return _items;
}
//// TODO! Probably bad that this turns list view models into listitems back to NEW view models
// return ListHelpers.FilterList(Items.Select(vm => vm.ListItem), Query).Select(li => new ListItemViewModel(li)).ToList();
try
{
var allFilteredItems = ListHelpers.FilterList(
_items
.SelectMany(section => section)
.Select(vm => vm.ListItem.Unsafe),
_query).Select(li => new ListItemViewModel(li));
// TODO! Probably bad that this turns list view models into listitems back to NEW view models
// TODO! make this safer
// TODODO! ^ still relevant?
var newFilter = ListHelpers
.FilterList(_items.Select(vm => vm.ListItem.Unsafe), query)
.Select(li => new ListItemViewModel(li));
var newSection = new SectionInfoList(null, allFilteredItems);
return [newSection];
}
catch (COMException ex)
{
_forceShowDetails = true;
return [new SectionInfoList(null, [new ListItemViewModel(new ErrorListItem(ex))])];
}
return newFilter;
}
}
}

View File

@@ -104,7 +104,6 @@ public sealed partial class MainPage : Page
private void InvokeActionHandler(object sender, ActionViewModel args)
{
var action = args.Command;
ViewModel.PushRecentAction(action);
TryAllowForeground(action);
if (action is IInvokableCommand invokable)
{

View File

@@ -95,28 +95,7 @@ public sealed class MainViewModel : IDisposable
return title + subtitle;
}
private string[] _recentCommandHashes = [];
public IEnumerable<IListItem> RecentActions => TopLevelCommands
.Select(i => i.Unsafe)
.Where((i) =>
{
if (i != null)
{
try
{
return _recentCommandHashes.Contains(CreateHash(i.Title, i.Subtitle));
}
catch (COMException)
{
return false;
}
}
return false;
}).Select(i => i!);
public IEnumerable<IListItem> AppItems => LoadedApps ? Apps.GetItems().First().Items : [];
public IEnumerable<IListItem> AppItems => LoadedApps ? Apps.GetItems() : [];
public IEnumerable<ExtensionObject<IListItem>> Everything => TopLevelCommands
.Concat(AppItems.Select(i => new ExtensionObject<IListItem>(i)))
@@ -126,69 +105,6 @@ public sealed class MainViewModel : IDisposable
return v;
});
public IEnumerable<ExtensionObject<IListItem>> Recent => _recentCommandHashes
.Select(hash =>
Everything
.Where(i =>
{
try
{
var o = i.Unsafe;
return CreateHash(o.Title, o.Subtitle) == hash;
}
catch (COMException)
{
return false;
}
})
.FirstOrDefault())
.Where(i => i != null)
.Select(i => i!);
public bool IsRecentCommand(MainListItem item)
{
try
{
foreach (var wraprer in Recent)
{
if (wraprer.Unsafe == item)
{
return true;
}
}
}
catch (COMException)
{
return false;
}
return false;
}
internal void PushRecentAction(ICommand action)
{
foreach (var wrapped in Everything)
{
try
{
var listItem = wrapped?.Unsafe;
if (listItem != null && listItem.Command == action)
{
// Found it, awesome.
var hash = CreateHash(listItem.Title, listItem.Subtitle);
// Remove the old one and push the new one to the front
var recent = new List<string>([hash]).Concat(_recentCommandHashes.Where(h => h != hash)).Take(5).ToArray();
_recentCommandHashes = recent.ToArray();
return;
}
}
catch (COMException)
{ /* log something */
}
}
}
public void Dispose()
{
_quitCommandProvider.Dispose();

View File

@@ -1,72 +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.Collections.ObjectModel;
using System.Collections.Specialized;
using Microsoft.CmdPal.Extensions;
using Microsoft.UI.Dispatching;
namespace WindowsCommandPalette.Views;
public class SectionInfoList : ObservableCollection<ListItemViewModel>
{
public string Title { get; }
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public SectionInfoList(ISection? section, IEnumerable<ListItemViewModel> items)
: base(items)
{
Title = section?.Title ?? string.Empty;
if (section != null && section is INotifyCollectionChanged observable)
{
observable.CollectionChanged -= Items_CollectionChanged;
observable.CollectionChanged += Items_CollectionChanged;
}
if (this._dispatcherQueue == null)
{
throw new InvalidOperationException("DispatcherQueue is null");
}
}
private void Items_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// DispatcherQueue.TryEnqueue(() => {
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
{
foreach (var i in e.NewItems)
{
if (i is IListItem li)
{
if (!string.IsNullOrEmpty(li.Title))
{
ListItemViewModel vm = new(li);
this.Add(vm);
}
// if (isDynamic)
// {
// // Dynamic lists are in charge of their own
// // filtering. They know if this thing was already
// // filtered or not.
// FilteredItems.Add(vm);
// }
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
this.Clear();
// Items.Clear();
// if (isDynamic)
// {
// FilteredItems.Clear();
// }
}
// });
}
}

View File

@@ -1,7 +1,7 @@
---
author: Mike Griese
created on: 2024-07-19
last updated: 2024-09-06
last updated: 2024-10-22
issue id: n/a
---
@@ -563,11 +563,7 @@ interface IListItem requires INotifyPropChanged {
ITag[] Tags{ get; };
IDetails Details{ get; };
IFallbackHandler FallbackHandler{ get; };
}
interface ISection {
String Title { get; };
IListItem[] Items { get; };
String Section { get; };
}
interface IGridProperties {
@@ -581,24 +577,17 @@ interface IListPage requires IPage {
IFilters Filters { get; };
IGridProperties GridProperties { get; };
ISection[] GetItems(); // DevPal will be responsible for filtering the list of items
IListItem[] GetItems(); // DevPal will be responsible for filtering the list of items
}
interface IDynamicListPage requires IListPage {
ISection[] GetItems(String query); // DevPal will do no filtering of these items
IListItem[] GetItems(String query); // DevPal will do no filtering of these items
}
```
![A mockup of individual elements of a list page and the list items](./list-elements-mock.png)
Lists are comprised of a collection of `Section`s, each with filled with
`ListItems`s as items. Sections may have a title, though they are not required
to. Sections are displayed to the user in the order they are returned by the
extension. Many extensions will only have a single section, but if developers
want to have lots of grouped results, they're free to have as many sections as
they like.
* For example: An "Agenda" extension may want to have one section for each day,
with each section's items containing the events for the day.
Lists are comprised of a collection of `IListItems`.
![Another mockup of the elements of a list item](./list-elements-mock-002.png)
@@ -607,7 +596,7 @@ when the user selects the item. If the IListItem has a non-null `Icon`, that
icon will be displayed in the list. If the `Icon` is null, DevPal will display
the `Icon` of the list item's `Command` instead.
ListItems may also have a list of `MoreCommands`.
ListItems may also have a list of `MoreCommands`.
These are additional commands that the user can take on the item. These will be
displayed to the user in the "More commands" flyout when the user has that item
selected. As the user moves focus through the list to select different items, we
@@ -657,6 +646,16 @@ internal sealed class TogglePlayMediaAction : InvokableCommand
// And a similar InvokableCommand for the PrevNextTrackAction
```
List items may also have an optional `Section` provided as a string. When
displaying items to the user, the Command Palette will group items with the same
`Section` string together in the order that the `Section`s are first seen in
the results. Many extensions will not use sections at all. If developers want to
have lots of grouped results, they're free to have as many sections as they
like.
* For example: An "Agenda" extension may want to have one section for each day,
with each section's items containing the events for the day.
* Or a Pokedex extension may want to group results by region.
Lists may either be a list of items like a traditional ListView, or they can be
a grid of items. Each of these items can be grouped into sections, which will be
displayed to the user in the order they are returned by the extension. Many
@@ -730,15 +729,12 @@ class NewsListItem(NewsPost post) : Microsoft.Windows.Run.Extensions.ListItem {
}
class HackerNewsPage: Microsoft.Windows.Run.Extensions.ListPage {
public bool Loading => true;
IListSection[] GetItems() {
IListItem[] GetItems() {
List<NewsItem> items = /* do some RSS feed stuff */;
this.Loading = false;
return new Microsoft.Windows.Run.Extensions.ListSection() {
Title = "Posts",
Items = items
.Select((post) => new NewsListItem(post))
.ToList()
};
return items
.Select((post) => new NewsListItem(post))
.ToArray();
}
}
```
@@ -1166,7 +1162,6 @@ We'll provide default implementations for the following interfaces:
* `IInvokableCommand`
* `IListItem`
* `IListSection`
* `ICommandContextItem`
* `ICommandResult`
* `IGoToPageArgs`
@@ -1218,21 +1213,16 @@ class NewsListItem(NewsPost post) : Microsoft.Windows.Run.Extensions.ListItem {
Icon = "\uE8F2" // ChatBubbles
})
];
public ITag[] Tags => [ new Tag(){ Text=post.Poster, new Tag(){ Text=post.Points } ];
public ITag[] Tags => [ new Tag(){ Text=post.Poster, new Tag(){ Text=post.Points } } ];
}
class HackerNewsPage: Microsoft.Windows.Run.Extensions.ListPage {
public bool Loading => true;
IListSection[] GetItems(String query) {
IListItem[] GetItems(String query) {
List<NewsItem> items = /* do some RSS feed stuff */;
this.Loading = false;
return [
new ListSection() {
Title = "Posts",
Items = items
.Select((post) => new NewsListItem(post))
.ToArray()
}
];
return items
.Select((post) => new NewsListItem(post))
.ToArray();
}
}
```
@@ -1363,6 +1353,7 @@ classDiagram
ITag[] Tags
IDetails Details
IFallbackHandler FallbackHandler
String Section
}
IContextItem "*" *-- IListItem
IDetails "?" *-- IListItem
@@ -1370,12 +1361,6 @@ classDiagram
ITag "*" *-- IListItem
IFallbackHandler "?" *-- IListItem
class ISection {
String Title
IListItem[] Items
}
IListItem "*" *-- ISection
class IGridProperties {
Windows.Foundation.Size TileSize
}
@@ -1388,15 +1373,15 @@ classDiagram
IFilters Filters
IGridProperties GridProperties
ISection[] GetItems()
IListItem[] GetItems()
}
IGridProperties "?" *-- IListPage
ISection "*" *-- IListPage
IListItem "*" *-- IListPage
IFilters "*" *-- IListPage
IDynamicListPage --|> IListPage
class IDynamicListPage {
ISection[] GetItems(String query)
IListItem[] GetItems(String query)
}
class IDetails {

View File

@@ -6,5 +6,5 @@ namespace Microsoft.CmdPal.Extensions.Helpers;
public class DynamicListPage : ListPage, IDynamicListPage
{
public virtual ISection[] GetItems(string query) => throw new NotImplementedException();
public virtual IListItem[] GetItems(string query) => throw new NotImplementedException();
}

View File

@@ -16,6 +16,7 @@ public class ListItem : BaseObservable, IListItem
private ICommand? _command;
private IContextItem[] _moreCommands = [];
private IFallbackHandler? _fallbackHandler;
private string _section = string.Empty;
public IconDataType? Icon
{
@@ -103,6 +104,16 @@ public class ListItem : BaseObservable, IListItem
init => _fallbackHandler = value;
}
public string Section
{
get => _section;
set
{
_section = value;
OnPropertyChanged(nameof(Section));
}
}
public ListItem(ICommand command)
{
Command = command;

View File

@@ -58,5 +58,5 @@ public class ListPage : Page, IListPage
}
}
public virtual ISection[] GetItems() => throw new NotImplementedException();
public virtual IListItem[] GetItems() => throw new NotImplementedException();
}

View File

@@ -1,11 +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.
namespace Microsoft.CmdPal.Extensions.Helpers;
public class ListSection : ISection
{
public string Title { get; set; } = "";
public virtual IListItem[] Items { get; set; } = [];
}

View File

@@ -159,12 +159,7 @@ namespace Microsoft.CmdPal.Extensions
ITag[] Tags{ get; };
IDetails Details{ get; };
IFallbackHandler FallbackHandler{ get; };
}
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
interface ISection {
String Title { get; };
IListItem[] Items { get; };
String Section { get; };
}
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
@@ -180,12 +175,12 @@ namespace Microsoft.CmdPal.Extensions
IFilters Filters { get; };
IGridProperties GridProperties { get; };
ISection[] GetItems(); // DevPal will be responsible for filtering the list of items
IListItem[] GetItems(); // DevPal will be responsible for filtering the list of items
}
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
interface IDynamicListPage requires IListPage {
ISection[] GetItems(String query); // DevPal will do no filtering of these items
IListItem[] GetItems(String query); // DevPal will do no filtering of these items
}
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]

View File

@@ -2,12 +2,11 @@
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PathToRoot>..\..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.6.240829007</WasdkNuget>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.6.240923002</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428</WindowsSdkBuildToolsNuget>
<WebView2Nuget>$(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2739.15</WebView2Nuget>
</PropertyGroup>
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.props')" />
@@ -189,4 +188,4 @@
<ItemGroup>
<ResourceCompile Include="version.rc" />
</ItemGroup>
</Project>
</Project>

View File

@@ -3,5 +3,5 @@
<package id="Microsoft.Web.WebView2" version="1.0.2739.15" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.240829007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.240923002" targetFramework="native" />
</packages>