mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-11 14:56:48 +01:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
],
|
||||
}
|
||||
];
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
],
|
||||
}
|
||||
];
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
];
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!
|
||||
//
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
// }
|
||||
}
|
||||
|
||||
// });
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
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`.
|
||||
|
||||

|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -58,5 +58,5 @@ public class ListPage : Page, IListPage
|
||||
}
|
||||
}
|
||||
|
||||
public virtual ISection[] GetItems() => throw new NotImplementedException();
|
||||
public virtual IListItem[] GetItems() => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -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; } = [];
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user