diff --git a/src/modules/cmdpal/Exts/EverythingExtension/Pages/EverythingExtensionPage.cs b/src/modules/cmdpal/Exts/EverythingExtension/Pages/EverythingExtensionPage.cs index 8bc7e5e7a6ad..216c9abbfcb6 100644 --- a/src/modules/cmdpal/Exts/EverythingExtension/Pages/EverythingExtensionPage.cs +++ b/src/modules/cmdpal/Exts/EverythingExtension/Pages/EverythingExtensionPage.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -30,10 +31,14 @@ public EverythingExtensionPage() Everything_SetMax(20); } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) { - Everything_SetSearchW(query); + Everything_SetSearchW(SearchText); + RaiseItemsChanged(0); + } + public override IListItem[] GetItems() + { if (!Everything_QueryW(true)) { // Throwing an exception would make sense, however, diff --git a/src/modules/cmdpal/Exts/MastodonExtension/MastodonExtensionPage.cs b/src/modules/cmdpal/Exts/MastodonExtension/MastodonExtensionPage.cs index e0029d399f95..ad5c1b5106f9 100644 --- a/src/modules/cmdpal/Exts/MastodonExtension/MastodonExtensionPage.cs +++ b/src/modules/cmdpal/Exts/MastodonExtension/MastodonExtensionPage.cs @@ -24,24 +24,34 @@ internal sealed partial class MastodonExtensionPage : ListPage internal static readonly HttpClient Client = new(); internal static readonly JsonSerializerOptions Options = new() { PropertyNameCaseInsensitive = true }; + private readonly List _posts = new(); + public MastodonExtensionPage() { Icon = new("https://mastodon.social/packs/media/icons/android-chrome-36x36-4c61fdb42936428af85afdbf8c6a45a8.png"); Name = "Mastodon"; ShowDetails = true; + HasMore = true; } public override IListItem[] GetItems() { - var postsAsync = FetchExplorePage(); - postsAsync.ConfigureAwait(false); - var posts = postsAsync.Result; - return posts + if (_posts.Count == 0) + { + var postsAsync = FetchExplorePage(); + postsAsync.ConfigureAwait(false); + var posts = postsAsync.Result; + this._posts.AddRange(posts); + } + + return _posts .Select(p => new ListItem(new MastodonPostPage(p)) { Title = p.Account.DisplayName, // p.ContentAsPlainText(), Subtitle = $"@{p.Account.Username}", Icon = new(p.Account.Avatar), + + // * Tags = [ new Tag() { @@ -53,7 +63,7 @@ public override IListItem[] GetItems() Icon = new("\ue8ee"), // RepeatAll Text = p.Boosts.ToString(CultureInfo.CurrentCulture), }, - ], + ], // */ Details = new Details() { // It was a cool idea to have a single image as the HeroImage, but the scaling is terrible @@ -67,14 +77,31 @@ public override IListItem[] GetItems() .ToArray(); } + public override void LoadMore() + { + var postsAsync = FetchExplorePage(20, this._posts.Count); + postsAsync.ContinueWith((res) => + { + var posts = postsAsync.Result; + this._posts.AddRange(posts); + this.RaiseItemsChanged(this._posts.Count); + }).ConfigureAwait(false); + } + public async Task> FetchExplorePage() + { + return await FetchExplorePage(20, 0); + } + + public async Task> FetchExplorePage(int limit, int offset) { var statuses = new List(); try { // Make a GET request to the Mastodon trends API endpoint - HttpResponseMessage response = await Client.GetAsync("https://mastodon.social/api/v1/trends/statuses"); + HttpResponseMessage response = await Client + .GetAsync($"https://mastodon.social/api/v1/trends/statuses?limit={limit}&offset={offset}"); response.EnsureSuccessStatusCode(); // Read and deserialize the response JSON into a list of MastodonStatus objects diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Pages/RegistryListPage.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Pages/RegistryListPage.cs index d89bab6d2542..8b2f23285cc4 100644 --- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Pages/RegistryListPage.cs +++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Pages/RegistryListPage.cs @@ -3,21 +3,12 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; using Microsoft.CmdPal.Ext.Registry.Classes; using Microsoft.CmdPal.Ext.Registry.Helpers; using Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; -using Microsoft.UI.Windowing; namespace Microsoft.CmdPal.Ext.Registry; @@ -27,7 +18,7 @@ internal sealed partial class RegistryListPage : DynamicListPage public RegistryListPage() { - Icon = new(string.Empty); + Icon = new("\uE74C"); // OEM Name = "Windows Registry"; _defaultIconPath = "Images/reg.light.png"; } @@ -54,12 +45,9 @@ public List Query(string query) // when only one sub-key was found and a user search for values ("\\") // show the filtered list of values of one sub-key - if (searchForValueName && list.Count == 1) - { - return ResultHelper.GetValuesFromKey(list.First().Key, _defaultIconPath, queryValueName); - } - - return ResultHelper.GetResultList(list, _defaultIconPath); + return searchForValueName && list.Count == 1 + ? ResultHelper.GetValuesFromKey(list.First().Key, _defaultIconPath, queryValueName) + : ResultHelper.GetResultList(list, _defaultIconPath); } else if (baseKeyList.Count() > 1) { @@ -70,8 +58,13 @@ public List Query(string query) return new List(); } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) + { + RaiseItemsChanged(0); + } + + public override IListItem[] GetItems() { - return Query(query).ToArray(); + return Query(SearchText).ToArray(); } } diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/RegistryCommandsProvider.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/RegistryCommandsProvider.cs index 39dda11753ad..9c8170262247 100644 --- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/RegistryCommandsProvider.cs +++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/RegistryCommandsProvider.cs @@ -2,7 +2,6 @@ // 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 Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; @@ -20,8 +19,8 @@ public override IListItem[] TopLevelCommands() return [ new ListItem(new RegistryListPage()) { - Title = "Search the Windows Registry", - Subtitle = "Navigates inside the Windows registry", + Title = "Registry", + Subtitle = "Navigate the Windows registry", } ]; } diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/Pages/ServicesListPage.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/Pages/ServicesListPage.cs index 0c11c16fdd1d..98c6eb56d8c4 100644 --- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/Pages/ServicesListPage.cs +++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/Pages/ServicesListPage.cs @@ -27,9 +27,14 @@ public ServicesListPage() Name = "Windows Services"; } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) { - ListItem[] items = ServiceHelper.Search(query).ToArray(); + RaiseItemsChanged(0); + } + + public override IListItem[] GetItems() + { + ListItem[] items = ServiceHelper.Search(SearchText).ToArray(); return items; } diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/WindowsServicesCommandsProvider.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/WindowsServicesCommandsProvider.cs index 925de1faf39b..e14a57548b81 100644 --- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/WindowsServicesCommandsProvider.cs +++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsServices/WindowsServicesCommandsProvider.cs @@ -2,7 +2,6 @@ // 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 Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; @@ -20,8 +19,8 @@ public override IListItem[] TopLevelCommands() return [ new ListItem(new ServicesListPage()) { - Title = "Search Windows Services", - Subtitle = "Quickly manage all Windows Services", + Title = "Windows Services", + Subtitle = "Manage Windows Services", } ]; } diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs index 379bf6b3a356..76e8ddd47745 100644 --- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs +++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs @@ -3,21 +3,11 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; using Microsoft.CmdPal.Ext.WindowsSettings.Classes; -using Microsoft.CmdPal.Ext.WindowsSettings.Helpers; using Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; -using Microsoft.UI.Windowing; namespace Microsoft.CmdPal.Ext.WindowsSettings; @@ -28,7 +18,7 @@ internal sealed partial class WindowsSettingsListPage : DynamicListPage public WindowsSettingsListPage(Classes.WindowsSettings windowsSettings) { - Icon = new(string.Empty); + Icon = new("\uE713"); // Settings Name = "Windows Settings"; _defaultIconPath = "Images/WindowsSettings.light.png"; _windowsSettings = windowsSettings; @@ -92,18 +82,18 @@ bool Predicate(WindowsSetting found) } // Search by key char '>' for app name and settings path - if (query.Contains('>')) - { - return ResultHelper.FilterBySettingsPath(found, query); - } - - return false; + return query.Contains('>') ? ResultHelper.FilterBySettingsPath(found, query) : false; } } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) + { + RaiseItemsChanged(0); + } + + public override IListItem[] GetItems() { - ListItem[] items = Query(query).ToArray(); + var items = Query(SearchText).ToArray(); return items; } diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs index 6af4d8b9bb2e..cae6c4018f69 100644 --- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs +++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs @@ -2,8 +2,6 @@ // 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 Microsoft.CmdPal.Ext.WindowsSettings.Classes; using Microsoft.CmdPal.Ext.WindowsSettings.Helpers; using Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; @@ -20,13 +18,13 @@ public partial class WindowsSettingsCommandsProvider : CommandProvider public WindowsSettingsCommandsProvider() { - DisplayName = $"Windows Services"; + DisplayName = $"Windows Settings"; _windowsSettings = JsonSettingsListHelper.ReadAllPossibleSettings(); _searchSettingsListItem = new ListItem(new WindowsSettingsListPage(_windowsSettings)) { - Title = "Search Windows Settings", - Subtitle = "Quickly navigate to specific Windows settings", + Title = "Windows Settings", + Subtitle = "Navigate to specific Windows settings", }; UnsupportedSettingsHelper.FilterByBuild(_windowsSettings); diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleDynamicListPage.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleDynamicListPage.cs index 4f9aa99bb899..e0b63517aaad 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleDynamicListPage.cs +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleDynamicListPage.cs @@ -23,25 +23,27 @@ internal sealed partial class SampleDynamicListPage : DynamicListPage public SampleDynamicListPage() { Icon = new(string.Empty); - Name = "SSH Keychain"; + Name = "Dynamic List"; } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) { - return [ - 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", - } - ], - } - ]; + RaiseItemsChanged(newSearch.Length); + } + + public override IListItem[] GetItems() + { + var items = SearchText.ToCharArray().Select(ch => new ListItem(new NoOpCommand()) { Title = ch.ToString() }).ToArray(); + if (items.Length == 0) + { + items = [new ListItem(new NoOpCommand()) { Title = "Start typing in the search box" }]; + } + + if (items.Length > 0) + { + items[0].Subtitle = "Notice how the number of items changes for this page when you type in the filter box"; + } + + return items; } } diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs index d6326c8427e1..e231663477e7 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleListPage.cs @@ -2,19 +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; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; using Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; -using Microsoft.UI.Windowing; namespace SamplePagesExtension; diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleMarkdownPage.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleMarkdownPage.cs index 6457d8a069c2..4edc39788e80 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleMarkdownPage.cs +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/Pages/SampleMarkdownPage.cs @@ -2,19 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; -using Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; -using Microsoft.UI.Windowing; namespace SamplePagesExtension; @@ -25,17 +13,152 @@ internal sealed partial class SampleMarkdownPage : MarkdownPage Markdown is a lightweight markup language with plain text formatting syntax. It's often used to format readme files, for writing messages in online forums, and to create rich text using a simple, plain text editor. ---- +## Basic Markdown Formatting + +### Headings + + # This is an

tag + ## This is an

tag + ### This is an

tag + #### This is an

tag + ##### This is an

tag + ###### This is an
tag + +### Emphasis + + *This text will be italic* + _This will also be italic_ + + **This text will be bold** + __This will also be bold__ + + _You **can** combine them_ + +Result: + +*This text will be italic* + +_This will also be italic_ + +**This text will be bold** + +__This will also be bold__ + +_You **can** combine them_ + +### Lists + +**Inordered:** + + * Milk + * Bread + * Wholegrain + * Butter + +Result: + +* Milk +* Bread + * Wholegrain +* Butter + +**Ordered:** + + 1. Tidy the kitchen + 2. Prepare ingredients + 3. Cook delicious things + +Result: + +1. Tidy the kitchen +2. Prepare ingredients +3. Cook delicious things + +### Images + + ![Alt Text](url) + +Result: + +![paintin'](https://i.imgur.com/93XJSNh.png) + +### Links + + [link](http://example.com) + +Result: + +[link](http://example.com) + +### Blockquotes + + As Albert Einstein said: + + > If we knew what it was we were doing, + > it would not be called research, would it? -## Headings +Result: -You can create headings using the `#` symbol, with the number of `#` determining the heading level. +As Albert Einstein said: + +> If we knew what it was we were doing, +> it would not be called research, would it? + +### Horizontal Rules ```markdown -# H1 Heading -## H2 Heading -### H3 Heading -#### H4 Heading + --- +``` + +Result: + +--- + +### Code Snippets + + Indenting by 4 spaces will turn an entire paragraph into a code-block. + +Result: + + .my-link { + text-decoration: underline; + } + +### Reference Lists & Titles + + **The quick brown [fox][1], jumped over the lazy [dog][2].** + + [1]: https://en.wikipedia.org/wiki/Fox ""Wikipedia: Fox"" + [2]: https://en.wikipedia.org/wiki/Dog ""Wikipedia: Dog"" + +Result: + +**The quick brown [fox][1], jumped over the lazy [dog][2].** + +[1]: https://en.wikipedia.org/wiki/Fox ""Wikipedia: Fox"" +[2]: https://en.wikipedia.org/wiki/Dog ""Wikipedia: Dog"" + +### Escaping + + \*literally\* + +Result: + +\*literally\* + +## Advanced Markdown + +Note: Some syntax which is not standard to native Markdown. They're extensions of the language. + +### Strike-throughs + + ~~deleted words~~ + +Result: + +~~deleted words~~ + + "; public SampleMarkdownPage() diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesCommandsProvider.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesCommandsProvider.cs index 192ff456f2cc..98ea3e33a92f 100644 --- a/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesCommandsProvider.cs +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/SamplePagesCommandsProvider.cs @@ -1,8 +1,7 @@ -// Copyright (c) Microsoft Corporation +// 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 Microsoft.CmdPal.Extensions; using Microsoft.CmdPal.Extensions.Helpers; @@ -16,36 +15,11 @@ public SamplePagesCommandsProvider() } private readonly IListItem[] _commands = [ - new ListItem(new SampleMarkdownPage()) + new ListItem(new SamplesListPage()) { - Title = "Markdown Page Sample Command", - Subtitle = "SamplePages Extension", + Title = "Sample Pages", + Subtitle = "View example commands", }, - new ListItem(new SampleListPage()) - { - Title = "List Page Sample Command", - Subtitle = "SamplePages Extension", - }, - new ListItem(new SampleFormPage()) - { - Title = "Form Page Sample Command", - Subtitle = "SamplePages Extension", - }, - new ListItem(new SampleListPageWithDetails()) - { - Title = "List Page With Details Sample Command", - Subtitle = "SamplePages Extension", - }, - new ListItem(new SampleDynamicListPage()) - { - Title = "Dynamic List Page Command", - Subtitle = "SamplePages Extension", - }, - new ListItem(new SampleSettingsPage()) - { - Title = "Sample settings page", - Subtitle = "A demo of the settings helpers", - } ]; public override IListItem[] TopLevelCommands() diff --git a/src/modules/cmdpal/Exts/SamplePagesExtension/SamplesListPage.cs b/src/modules/cmdpal/Exts/SamplePagesExtension/SamplesListPage.cs new file mode 100644 index 000000000000..ca4efa27f778 --- /dev/null +++ b/src/modules/cmdpal/Exts/SamplePagesExtension/SamplesListPage.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CmdPal.Extensions; +using Microsoft.CmdPal.Extensions.Helpers; + +namespace SamplePagesExtension; + +public partial class SamplesListPage : ListPage +{ + private readonly IListItem[] _commands = [ + new ListItem(new SampleMarkdownPage()) + { + Title = "Markdown Page Sample Command", + Subtitle = "Display a page of rendered markdown", + }, + new ListItem(new SampleListPage()) + { + Title = "List Page Sample Command", + Subtitle = "Display a list of items", + }, + new ListItem(new SampleFormPage()) + { + Title = "Form Page Sample Command", + Subtitle = "Define inputs to retrieve input from the user", + }, + new ListItem(new SampleListPageWithDetails()) + { + Title = "List Page With Details Sample Command", + Subtitle = "A list of items, each with additional details to display", + }, + new ListItem(new SampleDynamicListPage()) + { + Title = "Dynamic List Page Command", + Subtitle = "Changes the list of items in response to the typed query", + }, + new ListItem(new SampleSettingsPage()) + { + Title = "Sample settings page", + Subtitle = "A demo of the settings helpers", + } + ]; + + public SamplesListPage() + { + Name = "Samples"; + Icon = new("\ue946"); // Info + } + + public override IListItem[] GetItems() + { + return _commands; + } +} diff --git a/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelVideosPage.cs b/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelVideosPage.cs index c426c5b89658..edbfcffc6054 100644 --- a/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelVideosPage.cs +++ b/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelVideosPage.cs @@ -37,9 +37,14 @@ public YouTubeChannelVideosPage(string channelId = null, string channelName = nu _channelName = channelName; } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) { - return DoGetItems(query).GetAwaiter().GetResult(); // Fetch and await the task synchronously + RaiseItemsChanged(0); // 0 is bodgy + } + + public override IListItem[] GetItems() + { + return DoGetItems(SearchText).GetAwaiter().GetResult(); // Fetch and await the task synchronously } private async Task DoGetItems(string query) diff --git a/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelsPage.cs b/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelsPage.cs index 5946b94e86eb..676e01a63842 100644 --- a/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelsPage.cs +++ b/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeChannelsPage.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; @@ -25,15 +24,20 @@ public YouTubeChannelsPage() this.ShowDetails = true; } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) { - return DoGetItems(query).GetAwaiter().GetResult(); // Fetch and await the task synchronously + RaiseItemsChanged(0); // 0 is bodgy + } + + public override IListItem[] GetItems() + { + return DoGetItems(SearchText).GetAwaiter().GetResult(); // Fetch and await the task synchronously } private async Task DoGetItems(string query) { // Fetch YouTube channels based on the query - List items = await GetYouTubeChannels(query); + var items = await GetYouTubeChannels(query); // Create a section and populate it with the channel results var section = items.Select(channel => new ListItem(new OpenChannelLinkAction(channel.ChannelUrl)) @@ -65,7 +69,7 @@ private static async Task> GetYouTubeChannels(string query) var channels = new List(); - using (HttpClient client = new HttpClient()) + using (var client = new HttpClient()) { try { @@ -108,7 +112,7 @@ private static async Task> GetYouTubeChannels(string query) // Fetch subscriber count for each channel using a separate API call private static async Task GetChannelSubscriberCount(string apiKey, string channelId) { - using HttpClient client = new HttpClient(); + using var client = new HttpClient(); { try { diff --git a/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeVideosPage.cs b/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeVideosPage.cs index 66fa8d438e67..40f7a80091be 100644 --- a/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeVideosPage.cs +++ b/src/modules/cmdpal/Exts/YouTubeExtension/Pages/YouTubeVideosPage.cs @@ -26,9 +26,14 @@ public YouTubeVideosPage() this.ShowDetails = true; } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) { - return DoGetItems(query).GetAwaiter().GetResult(); // Fetch and await the task synchronously + RaiseItemsChanged(0); // 0 is bodgy + } + + public override IListItem[] GetItems() + { + return DoGetItems(SearchText).GetAwaiter().GetResult(); // Fetch and await the task synchronously } private async Task DoGetItems(string query) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainPage/MainListPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainPage/MainListPage.cs index e75e52f9dece..5bda80552d93 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainPage/MainListPage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainPage/MainListPage.cs @@ -24,4 +24,9 @@ public MainListPage(ShellViewModel shellViewModel) } public override IListItem[] GetItems() => _items; + + public override void UpdateSearchText(string oldSearch, string newSearch) + { + /* handle changes to the filter text here */ + } } diff --git a/src/modules/cmdpal/WindowsCommandPalette/FilteredListSection.cs b/src/modules/cmdpal/WindowsCommandPalette/FilteredListSection.cs index 5d0728112928..29d966ba5006 100644 --- a/src/modules/cmdpal/WindowsCommandPalette/FilteredListSection.cs +++ b/src/modules/cmdpal/WindowsCommandPalette/FilteredListSection.cs @@ -41,9 +41,10 @@ public sealed partial class FilteredListSection // * 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 ItemsToEnumerate => - lastSearchResults != null ? + (lastSearchResults != null ? lastSearchResults : - TopLevelItems.Concat(AllApps); + TopLevelItems.Concat(AllApps)) + .Where(i => i != null); private string _lastQuery = string.Empty; @@ -81,7 +82,9 @@ internal string Query // // instead run the query once when the action query changes, and store the // results. - public IListItem[] Items => ItemsToEnumerate.Where(i => i != null).ToArray(); + public IListItem[] Items => ItemsToEnumerate.ToArray(); + + public int Count => ItemsToEnumerate.Count(); public FilteredListSection(MainViewModel viewModel, ObservableCollection topLevelItems) { diff --git a/src/modules/cmdpal/WindowsCommandPalette/MainListPage.cs b/src/modules/cmdpal/WindowsCommandPalette/MainListPage.cs index 27cc48e1c62d..5b85fdc0e406 100644 --- a/src/modules/cmdpal/WindowsCommandPalette/MainListPage.cs +++ b/src/modules/cmdpal/WindowsCommandPalette/MainListPage.cs @@ -50,10 +50,17 @@ public MainListPage(MainViewModel viewModel) Loading = false; } - public override IListItem[] GetItems(string query) + public override void UpdateSearchText(string oldSearch, string newSearch) { - _filteredSection.Query = query; + UpdateQuery(); + } + + private void UpdateQuery() + { + // Let our filtering wrapper know the newly typed search text: + _filteredSection.Query = SearchText; + // Update all the top-level commands which are fallback providers: var fallbacks = topLevelItems .Select(i => i?.FallbackHandler) .Where(fb => fb != null) @@ -61,10 +68,16 @@ public override IListItem[] GetItems(string query) foreach (var fb in fallbacks) { - fb.UpdateQuery(query); + fb.UpdateQuery(SearchText); } - if (string.IsNullOrEmpty(query)) + var count = string.IsNullOrEmpty(SearchText) ? topLevelItems.Count : _filteredSection.Count; + RaiseItemsChanged(count); + } + + public override IListItem[] GetItems() + { + if (string.IsNullOrEmpty(SearchText)) { return topLevelItems.ToArray(); } @@ -105,6 +118,6 @@ private void TopLevelCommands_CollectionChanged(object? sender, NotifyCollection // 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"); + this.RaiseItemsChanged(topLevelItems.Count); } } diff --git a/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs b/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs index 658de4d2b09e..790f5ed2f625 100644 --- a/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs +++ b/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs @@ -11,6 +11,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; using WindowsCommandPalette; @@ -67,6 +68,57 @@ private bool MoreCommandsAvailable public ListPage() { this.InitializeComponent(); + + this.ItemsList.Loaded += ItemsList_Loaded; + } + + private void ItemsList_Loaded(object sender, RoutedEventArgs e) + { + // Find the ScrollViewer in the ListView + var listViewScrollViewer = FindScrollViewer(this.ItemsList); + + if (listViewScrollViewer != null) + { + listViewScrollViewer.ViewChanged += ListViewScrollViewer_ViewChanged; + } + } + + private void ListViewScrollViewer_ViewChanged(object? sender, ScrollViewerViewChangedEventArgs e) + { + var scrollView = sender as ScrollViewer; + if (scrollView == null) + { + return; + } + + // When we get to the bottom, request more from the extension, if they + // have more to give us. + // We're checking when we get to 80% of the scroll height, to give the + // extension a bit of a heads-up before the user actually gets there. + if (scrollView.VerticalOffset >= (scrollView.ScrollableHeight * .8)) + { + ViewModel?.LoadMoreIfNeeded(); + } + } + + private ScrollViewer? FindScrollViewer(DependencyObject parent) + { + if (parent is ScrollViewer) + { + return (ScrollViewer)parent; + } + + for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + var result = FindScrollViewer(child); + if (result != null) + { + return result; + } + } + + return null; } protected override void OnNavigatedTo(NavigationEventArgs e) @@ -82,12 +134,12 @@ protected override void OnNavigatedTo(NavigationEventArgs e) { ViewModel.InitialRender().ContinueWith((t) => { - DispatcherQueue.TryEnqueue(async () => { await UpdateFilter(FilterBox.Text); }); + DispatcherQueue.TryEnqueue(() => { UpdateFilter(FilterBox.Text); }); }); } else { - DispatcherQueue.TryEnqueue(async () => { await UpdateFilter(FilterBox.Text); }); + DispatcherQueue.TryEnqueue(() => { UpdateFilter(FilterBox.Text); }); } this.ItemsList.SelectedIndex = 0; @@ -276,10 +328,10 @@ private void FilterBox_TextChanged(object sender, TextChangedEventArgs e) } // on the UI thread - _ = UpdateFilter(FilterBox.Text); + UpdateFilter(FilterBox.Text); } - private async Task UpdateFilter(string text) + private void UpdateFilter(string text) { if (ViewModel == null) { @@ -292,7 +344,7 @@ private async Task UpdateFilter(string text) // * 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); + var items = ViewModel.GetFilteredItems(text); Debug.WriteLine($" UpdateFilter after GetFilteredItems({text}) --> {items.Count()} ; {ViewModel.FilteredItems.Count}"); diff --git a/src/modules/cmdpal/WindowsCommandPalette/Views/ListPageViewModel.xaml.cs b/src/modules/cmdpal/WindowsCommandPalette/Views/ListPageViewModel.xaml.cs index 3ed49b38117a..3d2380088c02 100644 --- a/src/modules/cmdpal/WindowsCommandPalette/Views/ListPageViewModel.xaml.cs +++ b/src/modules/cmdpal/WindowsCommandPalette/Views/ListPageViewModel.xaml.cs @@ -30,21 +30,39 @@ public sealed class ListPageViewModel : PageViewModel public bool ShowDetails => _forceShowDetails || Page.ShowDetails; + public bool HasMore { get; private set; } + + private bool _loadingMore; + public ListPageViewModel(IListPage page) : base(page) { page.PropChanged += Page_PropChanged; + page.ItemsChanged += Page_ItemsChanged; + HasMore = page.HasMore; + } + + private void Page_ItemsChanged(object sender, ItemsChangedEventArgs args) + { + Debug.WriteLine("Items changed"); + + _loadingMore = false; + + _dispatcherQueue.TryEnqueue(async () => + { + await this.UpdateListItems(); + }); } private void Page_PropChanged(object sender, PropChangedEventArgs args) { - if (args.PropertyName == "Items") + switch (args.PropertyName) { - Debug.WriteLine("Items changed"); - _dispatcherQueue.TryEnqueue(async () => + case nameof(HasMore): { - await this.UpdateListItems(); - }); + HasMore = Page.HasMore; + break; + } } } @@ -60,9 +78,7 @@ internal async Task UpdateListItems() { try { - return IsDynamicPage != null ? - IsDynamicPage.GetItems(_query) : - this.Page.GetItems(); + return this.Page.GetItems(); } catch (Exception ex) { @@ -92,7 +108,7 @@ internal async Task UpdateListItems() Debug.WriteLine($"Done with UpdateListItems, found {FilteredItems.Count} / {_items.Count}"); } - internal async Task> GetFilteredItems(string query) + internal IEnumerable GetFilteredItems(string query) { // This method does NOT change any lists. It doesn't modify _items or FilteredItems... if (query == _query) @@ -101,10 +117,11 @@ internal async Task> GetFilteredItems(string quer } _query = query; - if (IsDynamic) + if (IsDynamicPage != null) { - // ... except here we might modify those lists. But ignore that for now, GH #77 will fix this. - await UpdateListItems(); + // Tell the dynamic page the new search text. If they need to update, they will. + IsDynamicPage.SearchText = _query; + return FilteredItems; } else @@ -125,4 +142,23 @@ internal async Task> GetFilteredItems(string quer return newFilter; } } + + public async void LoadMoreIfNeeded() + { + if (!_loadingMore && HasMore) + { + // This is kinda a hack, to prevent us from over-requesting + // more at the bottom. + // We'll set this flag after we've requested more. We will clear it + // on the new ItemsChanged + _loadingMore = true; + + // TODO GH #73: When we have a real prototype, this should be an async call + // A thought: maybe the ExtensionObject.Unsafe could be an async + // call, so that you _know_ you need to wrap it up when you call it? + var t = new Task(() => Page.LoadMore()); + t.Start(); + await t; + } + } } diff --git a/src/modules/cmdpal/WindowsCommandPalette/Views/MarkdownPage.xaml b/src/modules/cmdpal/WindowsCommandPalette/Views/MarkdownPage.xaml index 089b34673494..b86f1026d475 100644 --- a/src/modules/cmdpal/WindowsCommandPalette/Views/MarkdownPage.xaml +++ b/src/modules/cmdpal/WindowsCommandPalette/Views/MarkdownPage.xaml @@ -97,7 +97,7 @@ x:Name="mdTextBox" Background="Transparent" IsTextSelectionEnabled="True" - Text="{x:Bind ViewModel.MarkdownContent, Mode=OneWay}" /> + Text="" /> diff --git a/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1 b/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1 index 10770b91f70f..7c2cdcf42c82 100644 --- a/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1 +++ b/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1 @@ -81,6 +81,17 @@ namespace Microsoft.CmdPal.Extensions String PropertyName { get; }; }; + [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] + interface INotifyItemsChanged { + event Windows.Foundation.TypedEventHandler ItemsChanged; + }; + + [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] + runtimeclass ItemsChangedEventArgs { + ItemsChangedEventArgs(Int32 totalItems); + Int32 TotalItems { get; }; + }; + $sdkContents } "@ diff --git a/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md b/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md index d9307dc48719..ade1fa04daa3 100644 --- a/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md +++ b/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md @@ -531,11 +531,15 @@ Lists can be either "static" or "dynamic": * A **dynamic** list leaves the extension in charge of filtering the list of items. * These are implementations of the `IDynamicListPage` interface. - * In this case, `GetItems` will be called every time the query - changes, and the extension is responsible for filtering the list of items. + * In this case, the host app will call the setter for `SearchText` on the + `IDynamicListPage` when the user changes the query. The extension may then + raise an `ItemsChanged` event afterwards, to let the host know it needs to + fetch items again. Additionally, the host app won't do any filtering of the + results - it's the extension's responsibility to filter them. * Ex: The GitHub extension may want to allow the user to type `is:issue is:open`, then return a list of open issues, without string matching on - the text. + the text. + ```csharp interface IFallbackHandler { @@ -571,18 +575,22 @@ interface IGridProperties { Windows.Foundation.Size TileSize { get; }; } -interface IListPage requires IPage { +interface IListPage requires IPage, INotifyItemsChanged { + // DevPal will be responsible for filtering the list of items, unless the + // class implements IDynamicListPage String SearchText { get; }; String PlaceholderText { get; }; Boolean ShowDetails{ get; }; IFilters Filters { get; }; IGridProperties GridProperties { get; }; + Boolean HasMore { get; }; - IListItem[] GetItems(); // DevPal will be responsible for filtering the list of items + IListItem[] GetItems(); + void LoadMore(); } interface IDynamicListPage requires IListPage { - IListItem[] GetItems(String query); // DevPal will do no filtering of these items + String SearchText { set; }; } ``` @@ -766,7 +774,39 @@ changed event. > But we want both static and dynamic lists to be able to update the results in > real-time. Example: A process list that updates in real-time. We want to be > able to add and remove items from the list as they start and stop. - +> +> Additionally, `HasMoreItems` / `LoadMoreItems` is almost exactly +> [`ISupportIncrementalLoading`], but without 1: the async aspect, 2: forcing us +> to pull in XAML as a dependency + +Here's a breakdown of how a dynamic list responds to the CmdPal. In this +example, we'll use a hypothetical GitHub issue search extension, which allows +the user to type a query and get a list of issues back. + +1. CmdPal loads the `ListPage` from the extension. +2. It is a `IDynamicListPage`, so the command palette knows not to do any + host-side filtering. +3. CmdPal reads the `SearchText` from the ListPage + - it returns `is:issue is:open` as initial text +4. CmdPal reads the `HasMoreItems` from the ListPage + - it returns `true` +5. CmdPal calls `GetItems()` + - the extension returns the first 25 items that match the query. +6. User scrolls the page to the bottom + - CmdPal calls `GetMore` on the ListPage, to let it know it should start + fetching more results +7. The extension raises a `ItemsChanged(40)`, to indicate that it now has 40 items + - CmdPal calls `GetItems`, which the command returns the whole list of items + - CmdPal does an in-place update of the existing items - ignoring the + unchanged ones, and appending the new ones to the end of the list + - The extension probably also raises a `PropChanged("HasMoreItems")` here, + (but we'll skip that for simplicity) +8. The user types `foo`. + - CmdPal calls `page.SearchText("is:issue is:open foo")`, to let the + extension know the query has changed +9. The extension does the background query to match +10. The extension raises an `ItemsChanged(5)`, to indicate there are 5 results +11. CmdPal calls `GetItems` to fetch the items. ##### Filtering the list @@ -1106,6 +1146,12 @@ interface INotifyPropChanged { runtimeclass PropChangedEventArgs { String PropName { get; }; } +interface INotifyItemsChanged { + event Windows.Foundation.TypedEventHandler ItemsChanged; +} +runtimeclass ItemsChangedEventArgs { + Int32 TotalItems { get; }; +} ``` It's basically exactly the event from XAML. I've named it `PropChanged` to avoid @@ -1688,3 +1734,4 @@ Or, to generate straight to the place I'm consuming it from: MBM. Thanks to Mano for helping me figure this one out. [Dev Home Extension]: https://learn.microsoft.com/en-us/windows/dev-home/extensions +[`ISupportIncrementalLoading`]: https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.data.isupportincrementalloading \ No newline at end of file diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/BaseObservable.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/BaseObservable.cs index 2e63da5cfc77..459fe9a9bdcb 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/BaseObservable.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/BaseObservable.cs @@ -9,6 +9,7 @@ namespace Microsoft.CmdPal.Extensions.Helpers; // TODO! We probably want to have OnPropertyChanged raise the event // asynchonously, so as to not block the extension app while it's being // processed in the host app. +// (also consider this for ItemsChanged in ListPage) public class BaseObservable : INotifyPropChanged { public event TypedEventHandler? PropChanged; diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/DynamicListPage.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/DynamicListPage.cs index 8106c84216c1..5a6404e0657e 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/DynamicListPage.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/DynamicListPage.cs @@ -4,7 +4,18 @@ namespace Microsoft.CmdPal.Extensions.Helpers; -public class DynamicListPage : ListPage, IDynamicListPage +public abstract class DynamicListPage : ListPage, IDynamicListPage { - public virtual IListItem[] GetItems(string query) => throw new NotImplementedException(); + public override string SearchText + { + get => base.SearchText; + set + { + var oldSearch = base.SearchText; + base.SearchText = value; + UpdateSearchText(oldSearch, value); + } + } + + public abstract void UpdateSearchText(string oldSearch, string newSearch); } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListPage.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListPage.cs index c54c82c4e457..7440de747dda 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListPage.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListPage.cs @@ -1,7 +1,9 @@ -// Copyright (c) Microsoft Corporation +// 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.CmdPal.Extensions.Helpers; public class ListPage : Page, IListPage @@ -9,9 +11,12 @@ public class ListPage : Page, IListPage private string _placeholderText = string.Empty; private string _searchText = string.Empty; private bool _showDetails; + private bool _hasMore; private IFilters? _filters; private IGridProperties? _gridProperties; + public event TypedEventHandler? ItemsChanged; + public string PlaceholderText { get => _placeholderText; @@ -22,7 +27,7 @@ public string PlaceholderText } } - public string SearchText + public virtual string SearchText { get => _searchText; set @@ -42,6 +47,16 @@ public bool ShowDetails } } + public bool HasMore + { + get => _hasMore; + set + { + _hasMore = value; + OnPropertyChanged(nameof(HasMore)); + } + } + public IFilters? Filters { get => _filters; @@ -62,5 +77,17 @@ public IGridProperties? GridProperties } } - public virtual IListItem[] GetItems() => throw new NotImplementedException(); + public virtual IListItem[] GetItems() => []; + + public virtual void LoadMore() + { + } + + protected void RaiseItemsChanged(int totalItems) + { + if (ItemsChanged != null) + { + ItemsChanged.Invoke(this, new ItemsChangedEventArgs(totalItems)); + } + } } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/ItemsChangedEventArgs.cpp b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/ItemsChangedEventArgs.cpp new file mode 100644 index 000000000000..b2d691fe9db7 --- /dev/null +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/ItemsChangedEventArgs.cpp @@ -0,0 +1,3 @@ +#include "pch.h" +#include "ItemsChangedEventArgs.h" +#include "ItemsChangedEventArgs.g.cpp" diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/ItemsChangedEventArgs.h b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/ItemsChangedEventArgs.h new file mode 100644 index 000000000000..1de580e9dc09 --- /dev/null +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/ItemsChangedEventArgs.h @@ -0,0 +1,20 @@ +#pragma once +#include "ItemsChangedEventArgs.g.h" + +namespace winrt::Microsoft::CmdPal::Extensions::implementation +{ + struct ItemsChangedEventArgs : ItemsChangedEventArgsT + { + ItemsChangedEventArgs() = default; + ItemsChangedEventArgs(int32_t total) : TotalItems{ total } {}; + + til::property TotalItems; + }; +} + +namespace winrt::Microsoft::CmdPal::Extensions::factory_implementation +{ + struct ItemsChangedEventArgs : ItemsChangedEventArgsT + { + }; +} diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl index b2ca0314fb56..96c031264ff5 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl @@ -31,6 +31,17 @@ namespace Microsoft.CmdPal.Extensions String PropertyName { get; }; }; + [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] + interface INotifyItemsChanged { + event Windows.Foundation.TypedEventHandler ItemsChanged; + }; + + [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] + runtimeclass ItemsChangedEventArgs { + ItemsChangedEventArgs(Int32 totalItems); + Int32 TotalItems { get; }; + }; + [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] interface ICommand requires INotifyPropChanged{ String Name{ get; }; @@ -211,19 +222,23 @@ namespace Microsoft.CmdPal.Extensions } [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] - interface IListPage requires IPage { + interface IListPage requires IPage, INotifyItemsChanged { + // DevPal will be responsible for filtering the list of items, unless the + // class implements IDynamicListPage String SearchText { get; }; String PlaceholderText { get; }; Boolean ShowDetails{ get; }; IFilters Filters { get; }; IGridProperties GridProperties { get; }; + Boolean HasMore { get; }; - IListItem[] GetItems(); // DevPal will be responsible for filtering the list of items + IListItem[] GetItems(); + void LoadMore(); } [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] interface IDynamicListPage requires IListPage { - IListItem[] GetItems(String query); // DevPal will do no filtering of these items + String SearchText { set; }; } [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)] diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.vcxproj b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.vcxproj index 9c415a7261ee..ddd50e850e1c 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.vcxproj +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.vcxproj @@ -145,6 +145,7 @@ + @@ -153,6 +154,7 @@ +