From 6fdc3e6f371e1748d561290af240c90efd22115f Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sat, 6 Jun 2026 13:30:06 +0200 Subject: [PATCH 01/47] Add Community Toolkit packages --- Vanara-Jnana/Jnana.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Vanara-Jnana/Jnana.csproj b/Vanara-Jnana/Jnana.csproj index 520e6023..97141414 100644 --- a/Vanara-Jnana/Jnana.csproj +++ b/Vanara-Jnana/Jnana.csproj @@ -27,6 +27,8 @@ + + From 623a1ff2cd2606d9136b8da26e1a61b9b7d3fb40 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sat, 6 Jun 2026 14:03:20 +0200 Subject: [PATCH 02/47] Add dummy `SettingsCard`s --- Vanara-Jnana/Views/SettingsPage.xaml | 24 ++++++++++++++++-------- Vanara-Jnana/Views/VoidPage.xaml | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Vanara-Jnana/Views/SettingsPage.xaml b/Vanara-Jnana/Views/SettingsPage.xaml index 9cec3438..82068985 100644 --- a/Vanara-Jnana/Views/SettingsPage.xaml +++ b/Vanara-Jnana/Views/SettingsPage.xaml @@ -4,16 +4,24 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:ClassicSamplesBrowser.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" DataContext="{Binding RelativeSource={RelativeSource Self}}"> - - - - - - - - + + + + + + + Light + Dark + System + + + + + diff --git a/Vanara-Jnana/Views/VoidPage.xaml b/Vanara-Jnana/Views/VoidPage.xaml index d4504847..3de1083f 100644 --- a/Vanara-Jnana/Views/VoidPage.xaml +++ b/Vanara-Jnana/Views/VoidPage.xaml @@ -4,6 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Jnana.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" xmlns:viewmodels="using:Jnana.ViewModels" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" @@ -33,6 +34,24 @@ --> + + + + + + + From cf32578cffbb7ad9b87757ec1a2f0fbecb6a451e Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sun, 7 Jun 2026 14:49:08 +0200 Subject: [PATCH 16/47] Add more buttons to the dummy Ribbon. --- Vanara-Jnana/Views/UtilitiesPage.xaml | 45 ++++++++++----------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/Vanara-Jnana/Views/UtilitiesPage.xaml b/Vanara-Jnana/Views/UtilitiesPage.xaml index 3dea7964..302556ac 100644 --- a/Vanara-Jnana/Views/UtilitiesPage.xaml +++ b/Vanara-Jnana/Views/UtilitiesPage.xaml @@ -11,6 +11,22 @@ + + + + + + - - - - From 14a9d8a708189d5705ff31c8dad0621fb3b02689 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sun, 7 Jun 2026 15:55:06 +0200 Subject: [PATCH 17/47] Add `UtilitiesAreaViewModel` --- .../ViewModels/UtilitiesAreaViewModel.cs | 11 ++++++++ Vanara-Jnana/Views/ShellPage.xaml | 2 +- Vanara-Jnana/Views/UtilitiesPage.xaml | 11 ++++++++ Vanara-Jnana/Views/UtilitiesPage.xaml.cs | 28 ++++++------------- 4 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 Vanara-Jnana/ViewModels/UtilitiesAreaViewModel.cs diff --git a/Vanara-Jnana/ViewModels/UtilitiesAreaViewModel.cs b/Vanara-Jnana/ViewModels/UtilitiesAreaViewModel.cs new file mode 100644 index 00000000..5854cab0 --- /dev/null +++ b/Vanara-Jnana/ViewModels/UtilitiesAreaViewModel.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jnana.ViewModels; + +internal class UtilitiesAreaViewModel +{ +} diff --git a/Vanara-Jnana/Views/ShellPage.xaml b/Vanara-Jnana/Views/ShellPage.xaml index 5fa050f8..c6f0f169 100644 --- a/Vanara-Jnana/Views/ShellPage.xaml +++ b/Vanara-Jnana/Views/ShellPage.xaml @@ -95,7 +95,7 @@ - + diff --git a/Vanara-Jnana/Views/UtilitiesPage.xaml b/Vanara-Jnana/Views/UtilitiesPage.xaml index 302556ac..af8144c5 100644 --- a/Vanara-Jnana/Views/UtilitiesPage.xaml +++ b/Vanara-Jnana/Views/UtilitiesPage.xaml @@ -36,5 +36,16 @@ + + + + + + + + + + + diff --git a/Vanara-Jnana/Views/UtilitiesPage.xaml.cs b/Vanara-Jnana/Views/UtilitiesPage.xaml.cs index 8fff0d5b..2d07dc1b 100644 --- a/Vanara-Jnana/Views/UtilitiesPage.xaml.cs +++ b/Vanara-Jnana/Views/UtilitiesPage.xaml.cs @@ -1,30 +1,20 @@ -using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Navigation; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; - -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. namespace Jnana.Views; -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// public sealed partial class UtilitiesPage : Page { public UtilitiesPage() { InitializeComponent(); + //Loaded += OnLoaded; } + + //private void OnLoaded(object sender, RoutedEventArgs e) + //{ + // if (!ToolManager.HasOpenTools) + // { + // ToolManager.Open(); + // } + //} } From a6219e991b9c162919180ae1be789a5d805723e2 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sun, 7 Jun 2026 16:42:59 +0200 Subject: [PATCH 18/47] Load NuGets page as default --- Vanara-Jnana/Views/ShellPage.xaml | 2 +- Vanara-Jnana/Views/ShellPage.xaml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Vanara-Jnana/Views/ShellPage.xaml b/Vanara-Jnana/Views/ShellPage.xaml index c6f0f169..ba243867 100644 --- a/Vanara-Jnana/Views/ShellPage.xaml +++ b/Vanara-Jnana/Views/ShellPage.xaml @@ -95,7 +95,7 @@ - + diff --git a/Vanara-Jnana/Views/ShellPage.xaml.cs b/Vanara-Jnana/Views/ShellPage.xaml.cs index 0423858f..3e37d9f8 100644 --- a/Vanara-Jnana/Views/ShellPage.xaml.cs +++ b/Vanara-Jnana/Views/ShellPage.xaml.cs @@ -16,7 +16,7 @@ public sealed partial class ShellPage : Page private NavigationService _navigationService { get; } // TODO: Consider making this a user setting that can be persisted across sessions, or determining it based on the last visited area // TODO: @dahall this is where you currently set the default navigation target... - private readonly INavigationService.Area defaultNavigationTarget = INavigationService.Area.Utilities; + private readonly INavigationService.Area defaultNavigationTarget = INavigationService.Area.NuGets; public ShellPage() { From 057f6a6ed0f2e7513a096a5e074a1843ac98072f Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sun, 7 Jun 2026 17:38:40 +0200 Subject: [PATCH 19/47] =?UTF-8?q?Correct=20spelling=20of=20`J=C3=B1=C4=81n?= =?UTF-8?q?a`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Vanara-Jnana/Views/ShellPage.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vanara-Jnana/Views/ShellPage.xaml b/Vanara-Jnana/Views/ShellPage.xaml index ba243867..9464a087 100644 --- a/Vanara-Jnana/Views/ShellPage.xaml +++ b/Vanara-Jnana/Views/ShellPage.xaml @@ -38,7 +38,7 @@ - + From bbc6bc100ab16f5c3cb5f354790a1ead02c5989e Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sun, 7 Jun 2026 20:59:53 +0200 Subject: [PATCH 20/47] Prepare `NuGetsPage` for GUI-Overhaul --- .../ViewModels/NuGetsAreaViewModel.cs | 1 + Vanara-Jnana/Views/NuGetsPage.xaml | 43 ++++++++++++++++--- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 2 + Vanara-Jnana/Views/ShellPage.xaml | 1 + 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index d29410bb..7c006c9a 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -15,6 +15,7 @@ public partial class NuGetsAreaViewModel : ObservableObject public NuGetsAreaViewModel() { RefreshCommand = new RelayCommand(LoadPackages); + Packages = new ObservableCollection() { "Vanara.PInvoke.User32", "Vanara.PInvoke.Kernel32", "Vanara.PInvoke.Gdi32" }; } private void LoadPackages() diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index 81fdec09..42c07bed 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -8,13 +8,44 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" DataContext="{Binding RelativeSource={RelativeSource Self}}"> - + - - + + - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 395767b7..9404d99a 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -1,3 +1,4 @@ +using Jnana.ViewModels; using Microsoft.UI.Xaml.Controls; // To learn more about WinUI, the WinUI project structure, @@ -10,6 +11,7 @@ namespace Jnana.Views; /// public sealed partial class NuGetsPage : Page { + public NuGetsAreaViewModel ViewModel { get; } = new NuGetsAreaViewModel(); public NuGetsPage() { InitializeComponent(); diff --git a/Vanara-Jnana/Views/ShellPage.xaml b/Vanara-Jnana/Views/ShellPage.xaml index 9464a087..1f21d459 100644 --- a/Vanara-Jnana/Views/ShellPage.xaml +++ b/Vanara-Jnana/Views/ShellPage.xaml @@ -48,6 +48,7 @@ + From c90143e02f47aa656feff62b01b3e949b4069564 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Sun, 7 Jun 2026 23:06:58 +0200 Subject: [PATCH 21/47] Remove orphaned `AssemblyLoaderService.cs` --- .../Vanara/Services/AssemblyLoaderService.cs | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 Vanara-Jnana/Vanara/Services/AssemblyLoaderService.cs diff --git a/Vanara-Jnana/Vanara/Services/AssemblyLoaderService.cs b/Vanara-Jnana/Vanara/Services/AssemblyLoaderService.cs deleted file mode 100644 index dc8333c2..00000000 --- a/Vanara-Jnana/Vanara/Services/AssemblyLoaderService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using ClassicSamplesBrowser.Vanara.Reflection; - -namespace ClassicSamplesBrowser.Vanara.Services; - -internal class AssemblyLoaderService -{ - public async Task LoadVanaraAssemblyTreeAsync() - { - // Get Vanara packages from NuGet -// var packages = await NuGetUtils.GetVanaraPackagesAsync(); -// -// // Select the latest version (simple for now) -// var latest = packages.OrderByDescending(p => p.Version).FirstOrDefault(); -// if (latest == null) -// return null; -// -// // Download package and extract DLLs -// var dllPaths = await NuGetUtils.DownloadAndExtractAssembliesAsync(latest); -// -// // Create reflection tree -// var root = AssemblyElements.CreateFromAssemblies(dllPaths); -// -// return root; - return null; - } -} From 61c8c23fce7308cbf59aeef42eb6eb0b017641a0 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Mon, 8 Jun 2026 00:34:23 +0200 Subject: [PATCH 22/47] Add prototypes to `NuGetsAreaViewModel` --- .../ViewModels/NuGetsAreaViewModel.cs | 54 ++++++++++++++++++- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 8 +-- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index 7c006c9a..3e6c9ffb 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -1,6 +1,10 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using ClassicSamplesBrowser.Vanara.Reflection; +using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using NuGet.Common; +using NuGet.Protocol.Core.Types; using System.Collections.ObjectModel; +using System.Runtime.CompilerServices; using System.Windows.Input; namespace Jnana.ViewModels; @@ -18,6 +22,54 @@ public NuGetsAreaViewModel() Packages = new ObservableCollection() { "Vanara.PInvoke.User32", "Vanara.PInvoke.Kernel32", "Vanara.PInvoke.Gdi32" }; } + // TODO: 06-08-26: Added this stub... Minor fix, and it should work. The main issue is that the NuGet API is a bit complex, and I need to figure out how to use it properly. + //public static async IAsyncEnumerable LoadLatestPackagesAsync( + // string prefix, ILogger? logger, [EnumeratorCancellation] CancellationToken cancellationToken) + //{ + // PackageSearchResource searchResource = + // await repository.GetResourceAsync(cancellationToken); + // + // SearchFilter filter = new(includePrerelease: false); + // + // IEnumerable results = + // await searchResource.SearchAsync(prefix, filter, skip: 0, take: 200, logger, cancellationToken); + // + // foreach (var pkg in results) + // { + // // pkg.Versions ist eine IAsyncEnumerable + // var latest = await pkg.GetVersionsAsync(); + // + // var stable = latest + // .Where(v => !v.Version.IsPrerelease) + // .OrderBy(v => v.Version) + // .LastOrDefault(); + // + // if (stable != null) + // yield return pkg; + // } + //} + + + internal async Task LoadVanaraAssemblyTreeAsync() + { + // Get Vanara packages from NuGet + // var packages = await NuGetUtils.GetVanaraPackagesAsync(); + // + // // Select the latest version (simple for now) + // var latest = packages.OrderByDescending(p => p.Version).FirstOrDefault(); + // if (latest == null) + // return null; + // + // // Download package and extract DLLs + // var dllPaths = await NuGetUtils.DownloadAndExtractAssembliesAsync(latest); + // + // // Create reflection tree + // var root = AssemblyElements.CreateFromAssemblies(dllPaths); + // + // return root; + return null; + } + private void LoadPackages() { // TODO: API call diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 9404d99a..c7e42bc4 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -1,14 +1,8 @@ using Jnana.ViewModels; using Microsoft.UI.Xaml.Controls; -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. - namespace Jnana.Views; -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// public sealed partial class NuGetsPage : Page { public NuGetsAreaViewModel ViewModel { get; } = new NuGetsAreaViewModel(); @@ -16,4 +10,4 @@ public NuGetsPage() { InitializeComponent(); } -} \ No newline at end of file +} From a21cdc1034dccedf0c335e74f58b1ce996df3349 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Mon, 8 Jun 2026 00:38:16 +0200 Subject: [PATCH 23/47] Add asset --- .../Assets/Vanara/dahall-NuGet Avatar.png | Bin 0 -> 76834 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Vanara-Jnana/Assets/Vanara/dahall-NuGet Avatar.png diff --git a/Vanara-Jnana/Assets/Vanara/dahall-NuGet Avatar.png b/Vanara-Jnana/Assets/Vanara/dahall-NuGet Avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..f04a0b9256f9f223a4bbcc2b3d16593939566ea9 GIT binary patch literal 76834 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 Date: Mon, 8 Jun 2026 01:56:32 +0200 Subject: [PATCH 24/47] Add `NuGetDiscoveryService.cs` --- .../Services/NuGetDiscoveryService.cs | 30 +++++++++++++++++++ Vanara-Jnana/Views/NuGetsPage.xaml | 29 +++++++----------- 2 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 Vanara-Jnana/Services/NuGetDiscoveryService.cs diff --git a/Vanara-Jnana/Services/NuGetDiscoveryService.cs b/Vanara-Jnana/Services/NuGetDiscoveryService.cs new file mode 100644 index 00000000..abbfb2aa --- /dev/null +++ b/Vanara-Jnana/Services/NuGetDiscoveryService.cs @@ -0,0 +1,30 @@ +using NuGet.Versioning; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jnana.Services; + +public record PackageId(string Id); + +public record PackageVersionInfo( + string Id, + NuGetVersion Version, + Uri? IconUrl, + string? Description, + string? Summary +); + +public record PackageContent( + string Id, + NuGetVersion Version, + IReadOnlyList Assemblies, + string? Readme, + byte[]? IconBytes +); + +internal class NuGetDiscoveryService +{ +} diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index 42c07bed..65378029 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -19,16 +19,10 @@ - + - - - - - - - - - - - - + --> + + + + + + + From 8eb35af3b03428489b68a9299f148cad9ee17b1b Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Mon, 8 Jun 2026 02:31:17 +0200 Subject: [PATCH 25/47] Implement `NuGetDiscoveryService` --- .../Services/NuGetDiscoveryService.cs | 118 ++++++++++++++++-- 1 file changed, 111 insertions(+), 7 deletions(-) diff --git a/Vanara-Jnana/Services/NuGetDiscoveryService.cs b/Vanara-Jnana/Services/NuGetDiscoveryService.cs index abbfb2aa..66528b81 100644 --- a/Vanara-Jnana/Services/NuGetDiscoveryService.cs +++ b/Vanara-Jnana/Services/NuGetDiscoveryService.cs @@ -1,9 +1,11 @@ -using NuGet.Versioning; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; +using System.Runtime.CompilerServices; namespace Jnana.Services; @@ -25,6 +27,108 @@ public record PackageContent( byte[]? IconBytes ); -internal class NuGetDiscoveryService +public sealed class NuGetDiscoveryService { + private readonly SourceRepository _repo; + private readonly ILogger? _logger; + + public NuGetDiscoveryService(string sourceUrl, ILogger? logger = null) + { + var provider = new SourceRepositoryProvider( + new PackageSourceProvider(NullSettings.Instance), + Repository.Provider.GetCoreV3()); + + _repo = provider.CreateRepository(new PackageSource(sourceUrl)); + _logger = logger; + } + + // Search for package IDs matching the prefix + public async IAsyncEnumerable SearchPackagesAsync(string prefix, [EnumeratorCancellation] CancellationToken token) + { + var search = await _repo.GetResourceAsync(token); + var filter = new SearchFilter(includePrerelease: false); + + var results = await search.SearchAsync(prefix, filter, 0, 200, _logger, token); + + foreach (var r in results) + yield return new PackageId(r.Identity.Id); + } + + public static async IAsyncEnumerable LoadLatestStablePackagesAsync(string prefix, ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + var search = await repository.GetResourceAsync(cancellationToken); + + var filter = new SearchFilter(includePrerelease: true); // Let's filter manually + var results = await search.SearchAsync(prefix, filter, skip: 0, take: 200, logger, cancellationToken); + + foreach (var pkg in results) + { + var versions = await pkg.GetVersionsAsync(); + + var latestStable = versions + .Where(v => !v.Version.IsPrerelease) + .OrderBy(v => v.Version) + .LastOrDefault(); + + if (latestStable is null) + continue; + + yield return pkg; + } + } + + public async Task DownloadPackageAsync(string id, NuGetVersion version, CancellationToken token) + { + var download = await _repo.GetResourceAsync(token); + + var result = await download.GetDownloadResourceResultAsync( + new PackageIdentity(id, version), + new PackageDownloadContext(new SourceCacheContext()), + SettingsUtility.GetGlobalPackagesFolder(NullSettings.Instance), + _logger, + token); + + using var reader = result.PackageReader; + + var dlls = reader.GetLibItems() + .SelectMany(i => i.Items) + .Where(f => f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + // TODO: var readme = await reader.GetReadmeAsync(token); + // TODO: var icon = await reader.GetIconAsync(token); + // return new PackageContent(id, version, dlls, readme, icon); + + return new PackageContent(id, version, dlls, null, null); + } + + public static async Task> GetAssemblyFilesAsync( + string id, + NuGetVersion version, + ILogger logger, + CancellationToken cancellationToken) + { + var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + var find = await repository.GetResourceAsync(cancellationToken); + + using var cache = new SourceCacheContext(); + using var stream = new MemoryStream(); + + bool ok = await find.CopyNupkgToStreamAsync( + id, version, stream, cache, logger, cancellationToken); + + if (!ok) + return Array.Empty(); + + stream.Position = 0; + using var reader = new PackageArchiveReader(stream); + + var dlls = reader.GetFiles() + .Where(f => f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + return dlls; + } + } From cdc9c165201687d169211d6fa622b2a55e87f4a0 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Tue, 9 Jun 2026 17:03:00 +0200 Subject: [PATCH 26/47] Add `NuGetCatalogService` and load Vanara Packages --- Vanara-Jnana/Services/INuGetCatalogService.cs | 15 ++ Vanara-Jnana/Services/NavigationService.cs | 3 + Vanara-Jnana/Services/NuGetCatalogService.cs | 112 +++++++++++ .../Services/NuGetDiscoveryService.cs | 1 + .../ViewModels/NuGetsAreaViewModel.cs | 181 ++---------------- Vanara-Jnana/Views/NuGetsPage.xaml | 26 +-- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 23 ++- Vanara-Jnana/Views/ShellPage.xaml.cs | 2 - 8 files changed, 182 insertions(+), 181 deletions(-) create mode 100644 Vanara-Jnana/Services/INuGetCatalogService.cs create mode 100644 Vanara-Jnana/Services/NuGetCatalogService.cs diff --git a/Vanara-Jnana/Services/INuGetCatalogService.cs b/Vanara-Jnana/Services/INuGetCatalogService.cs new file mode 100644 index 00000000..fd747d6d --- /dev/null +++ b/Vanara-Jnana/Services/INuGetCatalogService.cs @@ -0,0 +1,15 @@ +using NuGet.Versioning; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jnana.Services; + +public interface INuGetCatalogService +{ + IAsyncEnumerable SearchPackagesAsync(string prefix, CancellationToken token); + Task GetLatestStableVersionAsync(string packageId, CancellationToken token); + Task DownloadPackageAsync(string id, NuGetVersion version, CancellationToken token); +} diff --git a/Vanara-Jnana/Services/NavigationService.cs b/Vanara-Jnana/Services/NavigationService.cs index 30b6eae3..91e5955b 100644 --- a/Vanara-Jnana/Services/NavigationService.cs +++ b/Vanara-Jnana/Services/NavigationService.cs @@ -41,10 +41,12 @@ public Area CurrentArea public Page CurrentPage => CurrentArea switch { + Area.Disassembler => new DisassemblerPage(), Area.GitHub => new GitHubPage(), Area.NuGets => new NuGetsPage(), Area.Samples => new SamplesPage(), Area.Settings => new SettingsPage(), + Area.Utilities => new UtilitiesPage(), _ => new VoidPage() }; @@ -54,6 +56,7 @@ public NavigationService(Frame frame) _areaPageMap = new() { + { Area.Disassembler, typeof(DisassemblerPage) }, { Area.GitHub, typeof(GitHubPage) }, { Area.NuGets, typeof(NuGetsPage) }, { Area.Samples, typeof(SamplesPage) }, diff --git a/Vanara-Jnana/Services/NuGetCatalogService.cs b/Vanara-Jnana/Services/NuGetCatalogService.cs new file mode 100644 index 00000000..03d367f2 --- /dev/null +++ b/Vanara-Jnana/Services/NuGetCatalogService.cs @@ -0,0 +1,112 @@ +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Jnana.Services; + +public sealed class NuGetCatalogService : INuGetCatalogService +{ + private readonly SourceRepository _repo; + private readonly ILogger? _logger; + + public NuGetCatalogService(string sourceUrl, ILogger? logger = null) + { + _repo = Repository.Factory.GetCoreV3(sourceUrl); + _logger = logger; + } + + public async IAsyncEnumerable SearchPackagesAsync( + string prefix, + [EnumeratorCancellation] CancellationToken token) + { + var search = await _repo.GetResourceAsync(token); + var filter = new SearchFilter(includePrerelease: true); + + var results = await search.SearchAsync(prefix, filter, 0, 200, _logger, token); + + foreach (var r in results) + yield return new PackageId(r.Identity.Id); + } + + public async Task GetLatestStableVersionAsync( + string packageId, + CancellationToken token) + { + var search = await _repo.GetResourceAsync(token); + var filter = new SearchFilter(includePrerelease: true); + + var results = await search.SearchAsync(packageId, filter, 0, 1, _logger, token); + + var pkg = results.FirstOrDefault(); + if (pkg is null) + return null; + + var versions = await pkg.GetVersionsAsync(); + + var latestStable = versions + .Where(v => !v.Version.IsPrerelease) + .OrderBy(v => v.Version) + .LastOrDefault(); + + if (latestStable is null) + return null; + + return new PackageVersionInfo( + pkg.Identity.Id, + latestStable.Version, + pkg.IconUrl, + pkg.Description, + pkg.Summary + ); + } + + public async Task DownloadPackageAsync( + string id, + NuGetVersion version, + CancellationToken token) + { + var find = await _repo.GetResourceAsync(token); + + using var cache = new SourceCacheContext(); + using var stream = new MemoryStream(); + + bool ok = await find.CopyNupkgToStreamAsync( + id, version, stream, cache, _logger, token); + + if (!ok) + throw new InvalidOperationException($"Could not download {id} {version}"); + + stream.Position = 0; + using var reader = new PackageArchiveReader(stream); + + var dlls = reader.GetFiles() + .Where(f => f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + // TODO: Use ExtractFile to load the files + // var readme = await reader.GetReadmeAsync(token); + // var icon = await reader.GetIconAsync(token); + + string readme = null; + byte[] icon = null; + + + return new PackageContent( + id, + version, + dlls, + readme, + icon + ); + } +} diff --git a/Vanara-Jnana/Services/NuGetDiscoveryService.cs b/Vanara-Jnana/Services/NuGetDiscoveryService.cs index 66528b81..e4023cec 100644 --- a/Vanara-Jnana/Services/NuGetDiscoveryService.cs +++ b/Vanara-Jnana/Services/NuGetDiscoveryService.cs @@ -27,6 +27,7 @@ public record PackageContent( byte[]? IconBytes ); +[Obsolete("This class has been replaced by NuGetCatalogService")] public sealed class NuGetDiscoveryService { private readonly SourceRepository _repo; diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index 3e6c9ffb..4f485cef 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -1,6 +1,7 @@ using ClassicSamplesBrowser.Vanara.Reflection; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Jnana.Services; using NuGet.Common; using NuGet.Protocol.Core.Types; using System.Collections.ObjectModel; @@ -11,178 +12,24 @@ namespace Jnana.ViewModels; public partial class NuGetsAreaViewModel : ObservableObject { - // TODO: Use `NuGetPackageInfo` here: - public ObservableCollection Packages { get; } = new ObservableCollection(); + private readonly INuGetCatalogService _catalog; - public ICommand RefreshCommand { get; } + public ObservableCollection Packages { get; } = new(); - public NuGetsAreaViewModel() + public NuGetsAreaViewModel(ILogger? logger = null) { - RefreshCommand = new RelayCommand(LoadPackages); - Packages = new ObservableCollection() { "Vanara.PInvoke.User32", "Vanara.PInvoke.Kernel32", "Vanara.PInvoke.Gdi32" }; + _catalog = new NuGetCatalogService("https://api.nuget.org/v3/index.json", logger); } - // TODO: 06-08-26: Added this stub... Minor fix, and it should work. The main issue is that the NuGet API is a bit complex, and I need to figure out how to use it properly. - //public static async IAsyncEnumerable LoadLatestPackagesAsync( - // string prefix, ILogger? logger, [EnumeratorCancellation] CancellationToken cancellationToken) - //{ - // PackageSearchResource searchResource = - // await repository.GetResourceAsync(cancellationToken); - // - // SearchFilter filter = new(includePrerelease: false); - // - // IEnumerable results = - // await searchResource.SearchAsync(prefix, filter, skip: 0, take: 200, logger, cancellationToken); - // - // foreach (var pkg in results) - // { - // // pkg.Versions ist eine IAsyncEnumerable - // var latest = await pkg.GetVersionsAsync(); - // - // var stable = latest - // .Where(v => !v.Version.IsPrerelease) - // .OrderBy(v => v.Version) - // .LastOrDefault(); - // - // if (stable != null) - // yield return pkg; - // } - //} - - - internal async Task LoadVanaraAssemblyTreeAsync() - { - // Get Vanara packages from NuGet - // var packages = await NuGetUtils.GetVanaraPackagesAsync(); - // - // // Select the latest version (simple for now) - // var latest = packages.OrderByDescending(p => p.Version).FirstOrDefault(); - // if (latest == null) - // return null; - // - // // Download package and extract DLLs - // var dllPaths = await NuGetUtils.DownloadAndExtractAssembliesAsync(latest); - // - // // Create reflection tree - // var root = AssemblyElements.CreateFromAssemblies(dllPaths); - // - // return root; - return null; - } - - private void LoadPackages() + public async Task LoadPackagesAsync() { - // TODO: API call + Packages.Clear(); + + await foreach (var pkg in _catalog.SearchPackagesAsync("Vanara.", CancellationToken.None)) + { + var latest = await _catalog.GetLatestStableVersionAsync(pkg.Id, CancellationToken.None); + if (latest != null) + Packages.Add(latest); + } } - - // private readonly AssemblyLoaderService _loader; - // - // public NuGetViewModel(AssemblyLoaderService loader) - // { - // _loader = loader; - // - // Packages = new ObservableCollection(); - // Versions = new ObservableCollection(); - // } - // - // // ----------------------------- - // // Collections - // // ----------------------------- - // public ObservableCollection Packages { get; } - // public ObservableCollection Versions { get; } - // - // // ----------------------------- - // // Selected Items - // // ----------------------------- - // [ObservableProperty] - // private NuGetPackageInfo? selectedPackage; - // - // partial void OnSelectedPackageChanged(NuGetPackageInfo? value) - // { - // if (value != null) - // LoadVersionsCommand.Execute(value); - // } - // - // [ObservableProperty] - // private string? selectedVersion; - // - // partial void OnSelectedVersionChanged(string? value) - // { - // if (value != null) - // LoadAssemblyTreeCommand.Execute(value); - // } - // - // // ----------------------------- - // // Reflection Root - // // ----------------------------- - // [ObservableProperty] - // private IElementInfo? rootElement; - // - // // ----------------------------- - // // Loading Flags - // // ----------------------------- - // [ObservableProperty] private bool isLoadingPackages; - // [ObservableProperty] private bool isLoadingVersions; - // [ObservableProperty] private bool isLoadingAssemblies; - // - // // ----------------------------- - // // Commands - // // ----------------------------- - // [RelayCommand] - // private async Task LoadPackagesAsync() - // { - // try - // { - // IsLoadingPackages = true; - // Packages.Clear(); - // - // var pkgs = await _nuget.GetVanaraPackagesAsync(); - // - // foreach (var pkg in pkgs) - // Packages.Add(pkg); - // } - // finally - // { - // IsLoadingPackages = false; - // } - // } - // - // [RelayCommand] - // private async Task LoadVersionsAsync(NuGetPackageInfo package) - // { - // try - // { - // IsLoadingVersions = true; - // Versions.Clear(); - // - // var versions = await _nuget.GetPackageVersionsAsync(package.Id); - // - // foreach (var v in versions) - // Versions.Add(v); - // } - // finally - // { - // IsLoadingVersions = false; - // } - // } - // - // [RelayCommand] - // private async Task LoadAssemblyTreeAsync(string version) - // { - // try - // { - // IsLoadingAssemblies = true; - // - // var assemblies = await _nuget.DownloadAndExtractAssembliesAsync( - // SelectedPackage!.Id, - // version - // ); - // - // RootElement = await _loader.LoadFromAssembliesAsync(assemblies); - // } - // finally - // { - // IsLoadingAssemblies = false; - // } - // } } diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index 65378029..eb5a9d48 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -4,14 +4,15 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Jnana.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:models="using:Jnana.Services" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" DataContext="{Binding RelativeSource={RelativeSource Self}}"> - - + + @@ -21,19 +22,22 @@ - - - - - + + + - --> - - + + + diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index c7e42bc4..191b80e5 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -1,13 +1,34 @@ +using Jnana.Services; using Jnana.ViewModels; using Microsoft.UI.Xaml.Controls; +using NuGet.Common; +using System.Diagnostics; namespace Jnana.Views; public sealed partial class NuGetsPage : Page { - public NuGetsAreaViewModel ViewModel { get; } = new NuGetsAreaViewModel(); + private readonly ILogger? logger; + private INuGetCatalogService nuGetCatalogService; + public NuGetsAreaViewModel ViewModel { get; } + private CancellationToken cancelToken = CancellationToken.None; + + public NuGetsPage() { InitializeComponent(); + + logger = new NullLogger(); + ViewModel = new NuGetsAreaViewModel(logger); + + ViewModel.LoadPackagesAsync().ContinueWith(t => + { + if (t.IsFaulted) + { + Debug.Print($"Failed to load NuGet packages: {t.Exception}"); + //logger?.LogError(t.Exception, "Failed to load NuGet packages"); + } + }, cancelToken); + } } diff --git a/Vanara-Jnana/Views/ShellPage.xaml.cs b/Vanara-Jnana/Views/ShellPage.xaml.cs index 3e37d9f8..09692d48 100644 --- a/Vanara-Jnana/Views/ShellPage.xaml.cs +++ b/Vanara-Jnana/Views/ShellPage.xaml.cs @@ -10,7 +10,6 @@ namespace Jnana.Views; public sealed partial class ShellPage : Page { static readonly CancellationToken CancellationToken = CancellationToken.None; - private NuGetsAreaViewModel NuGetVM { get; } private GitHubAreaViewModel GitHubVM { get; } private SamplesAreaViewModel SamplesVM { get; } private NavigationService _navigationService { get; } @@ -21,7 +20,6 @@ public sealed partial class ShellPage : Page public ShellPage() { InitializeComponent(); - NuGetVM = new NuGetsAreaViewModel(); GitHubVM = new GitHubAreaViewModel(); SamplesVM = new SamplesAreaViewModel(); From 001ae5a1a46bc9e2ccc6677c58f671f51605d706 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Tue, 9 Jun 2026 18:14:36 +0200 Subject: [PATCH 27/47] Add `NuGetCatalogCache` --- Vanara-Jnana/Services/NuGetCatalogCache.cs | 49 ++++++++++++++++++++++ Vanara-Jnana/Views/NuGetsPage.xaml | 19 ++++----- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 5 +++ 3 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 Vanara-Jnana/Services/NuGetCatalogCache.cs diff --git a/Vanara-Jnana/Services/NuGetCatalogCache.cs b/Vanara-Jnana/Services/NuGetCatalogCache.cs new file mode 100644 index 00000000..416d506d --- /dev/null +++ b/Vanara-Jnana/Services/NuGetCatalogCache.cs @@ -0,0 +1,49 @@ +using NuGet.Versioning; +using System.Runtime.CompilerServices; + +namespace Jnana.Services; + +public sealed class NuGetCatalogCache : INuGetCatalogService +{ + private readonly INuGetCatalogService _inner; + private readonly TimeSpan _ttl = TimeSpan.FromHours(24); + + private readonly Dictionary _cache + = new(); + + public NuGetCatalogCache(INuGetCatalogService inner) + { + _inner = inner; + } + + public async IAsyncEnumerable SearchPackagesAsync( + string prefix, + [EnumeratorCancellation] CancellationToken token) + { + await foreach (var pkg in _inner.SearchPackagesAsync(prefix, token)) + yield return pkg; + } + + public async Task GetLatestStableVersionAsync( + string packageId, + CancellationToken token) + { + if (_cache.TryGetValue(packageId, out var entry)) + { + if (DateTime.UtcNow - entry.Timestamp < _ttl) + return entry.Info; + } + + var info = await _inner.GetLatestStableVersionAsync(packageId, token); + if (info != null) + _cache[packageId] = (DateTime.UtcNow, info); + + return info; + } + + public Task DownloadPackageAsync( + string id, + NuGetVersion version, + CancellationToken token) + => _inner.DownloadPackageAsync(id, version, token); +} diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index eb5a9d48..e461d742 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -11,8 +11,8 @@ - + @@ -26,15 +26,14 @@ - - - - - - - - + + + + + + + + diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 191b80e5..92091dfd 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -1,3 +1,4 @@ +using Jnana.Helpers; using Jnana.Services; using Jnana.ViewModels; using Microsoft.UI.Xaml.Controls; @@ -10,6 +11,7 @@ public sealed partial class NuGetsPage : Page { private readonly ILogger? logger; private INuGetCatalogService nuGetCatalogService; + private INuGetCatalogService nuGetCatalogCache; public NuGetsAreaViewModel ViewModel { get; } private CancellationToken cancelToken = CancellationToken.None; @@ -21,6 +23,9 @@ public NuGetsPage() logger = new NullLogger(); ViewModel = new NuGetsAreaViewModel(logger); + nuGetCatalogService = new NuGetCatalogService("https://api.nuget.org/v3/index.json", logger); + nuGetCatalogCache = new NuGetCatalogCache(nuGetCatalogService); + ViewModel.LoadPackagesAsync().ContinueWith(t => { if (t.IsFaulted) From 2c828a9883d131f4cec9668442feb494186b5c05 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Tue, 9 Jun 2026 19:40:41 +0200 Subject: [PATCH 28/47] Add `NuGetCatalogDiskCache`. Rename `NuGetCatalogCache` to `NuGetCatalogMemoryCache` --- Vanara-Jnana/Services/INuGetCatalogService.cs | 18 ++++ .../Services/NuGetCatalogDiskCache.cs | 102 ++++++++++++++++++ ...logCache.cs => NuGetCatalogMemoryCache.cs} | 4 +- Vanara-Jnana/Services/NuGetCatalogService.cs | 2 +- .../Services/NuGetDiscoveryService.cs | 18 ---- .../ViewModels/NuGetsAreaViewModel.cs | 17 ++- Vanara-Jnana/Views/NuGetsPage.xaml | 3 +- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 7 +- 8 files changed, 138 insertions(+), 33 deletions(-) create mode 100644 Vanara-Jnana/Services/NuGetCatalogDiskCache.cs rename Vanara-Jnana/Services/{NuGetCatalogCache.cs => NuGetCatalogMemoryCache.cs} (91%) diff --git a/Vanara-Jnana/Services/INuGetCatalogService.cs b/Vanara-Jnana/Services/INuGetCatalogService.cs index fd747d6d..cecbe71c 100644 --- a/Vanara-Jnana/Services/INuGetCatalogService.cs +++ b/Vanara-Jnana/Services/INuGetCatalogService.cs @@ -7,6 +7,24 @@ namespace Jnana.Services; +public record PackageId(string Id); + +public record PackageVersionInfo( + string Id, + string Version, + Uri? IconUrl, + string? Description, + string? Summary +); + +public record PackageContent( + string Id, + NuGetVersion Version, + IReadOnlyList Assemblies, + string? Readme, + byte[]? IconBytes +); + public interface INuGetCatalogService { IAsyncEnumerable SearchPackagesAsync(string prefix, CancellationToken token); diff --git a/Vanara-Jnana/Services/NuGetCatalogDiskCache.cs b/Vanara-Jnana/Services/NuGetCatalogDiskCache.cs new file mode 100644 index 00000000..3a792d9a --- /dev/null +++ b/Vanara-Jnana/Services/NuGetCatalogDiskCache.cs @@ -0,0 +1,102 @@ +using NuGet.Versioning; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Jnana.Services; + +public sealed class NuGetCatalogDiskCache : INuGetCatalogService +{ + private readonly INuGetCatalogService _inner; + private readonly string _cacheFile; + private readonly TimeSpan _ttl = TimeSpan.FromHours(24); + + private Dictionary? _cache; + private DateTime _timestamp; + + public NuGetCatalogDiskCache(INuGetCatalogService inner, string cacheFile) + { + _inner = inner; + _cacheFile = cacheFile; + + LoadFromDisk(); + } + + private void LoadFromDisk() + { + if (!File.Exists(_cacheFile)) + return; + + try + { + var json = File.ReadAllText(_cacheFile); + var data = JsonSerializer.Deserialize(json); + + if (data != null && DateTime.UtcNow - data.Timestamp < _ttl) + { + _timestamp = data.Timestamp; + _cache = data.Packages.ToDictionary(p => p.Id, p => p); + } + } + catch + { + // we can ignore any errors here - if the cache is corrupted or unreadable, we'll just start fresh + } + } + + private void SaveToDisk() + { + var data = new CacheData + { + Timestamp = DateTime.UtcNow, + Packages = _cache?.Values.ToList() ?? new() + }; + + var json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); + Directory.CreateDirectory(Path.GetDirectoryName(_cacheFile)!); + File.WriteAllText(_cacheFile, json); + } + + public async IAsyncEnumerable SearchPackagesAsync( + string prefix, + [EnumeratorCancellation] CancellationToken token) + { + await foreach (var pkg in _inner.SearchPackagesAsync(prefix, token)) + yield return pkg; + } + + public async Task GetLatestStableVersionAsync( + string packageId, + CancellationToken token) + { + _cache ??= new(); + + if (_cache.TryGetValue(packageId, out var info)) + return info; + + var latest = await _inner.GetLatestStableVersionAsync(packageId, token); + if (latest != null) + { + _cache[packageId] = latest; + SaveToDisk(); + } + + return latest; + } + + public Task DownloadPackageAsync( + string id, + NuGetVersion version, + CancellationToken token) + => _inner.DownloadPackageAsync(id, version, token); + + private sealed class CacheData + { + public DateTime Timestamp { get; set; } + public List Packages { get; set; } = new(); + } +} diff --git a/Vanara-Jnana/Services/NuGetCatalogCache.cs b/Vanara-Jnana/Services/NuGetCatalogMemoryCache.cs similarity index 91% rename from Vanara-Jnana/Services/NuGetCatalogCache.cs rename to Vanara-Jnana/Services/NuGetCatalogMemoryCache.cs index 416d506d..80302916 100644 --- a/Vanara-Jnana/Services/NuGetCatalogCache.cs +++ b/Vanara-Jnana/Services/NuGetCatalogMemoryCache.cs @@ -3,7 +3,7 @@ namespace Jnana.Services; -public sealed class NuGetCatalogCache : INuGetCatalogService +public sealed class NuGetCatalogMemoryCache : INuGetCatalogService { private readonly INuGetCatalogService _inner; private readonly TimeSpan _ttl = TimeSpan.FromHours(24); @@ -11,7 +11,7 @@ public sealed class NuGetCatalogCache : INuGetCatalogService private readonly Dictionary _cache = new(); - public NuGetCatalogCache(INuGetCatalogService inner) + public NuGetCatalogMemoryCache(INuGetCatalogService inner) { _inner = inner; } diff --git a/Vanara-Jnana/Services/NuGetCatalogService.cs b/Vanara-Jnana/Services/NuGetCatalogService.cs index 03d367f2..ddcb04fb 100644 --- a/Vanara-Jnana/Services/NuGetCatalogService.cs +++ b/Vanara-Jnana/Services/NuGetCatalogService.cs @@ -63,7 +63,7 @@ public async IAsyncEnumerable SearchPackagesAsync( return new PackageVersionInfo( pkg.Identity.Id, - latestStable.Version, + latestStable.Version.ToString(), pkg.IconUrl, pkg.Description, pkg.Summary diff --git a/Vanara-Jnana/Services/NuGetDiscoveryService.cs b/Vanara-Jnana/Services/NuGetDiscoveryService.cs index e4023cec..2970fb1a 100644 --- a/Vanara-Jnana/Services/NuGetDiscoveryService.cs +++ b/Vanara-Jnana/Services/NuGetDiscoveryService.cs @@ -9,24 +9,6 @@ namespace Jnana.Services; -public record PackageId(string Id); - -public record PackageVersionInfo( - string Id, - NuGetVersion Version, - Uri? IconUrl, - string? Description, - string? Summary -); - -public record PackageContent( - string Id, - NuGetVersion Version, - IReadOnlyList Assemblies, - string? Readme, - byte[]? IconBytes -); - [Obsolete("This class has been replaced by NuGetCatalogService")] public sealed class NuGetDiscoveryService { diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index 4f485cef..06ef0d89 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -12,22 +12,29 @@ namespace Jnana.ViewModels; public partial class NuGetsAreaViewModel : ObservableObject { - private readonly INuGetCatalogService _catalog; - + private INuGetCatalogService nuGetCatalogService; + private INuGetCatalogService nuGetCatalogMemoryCache; + private INuGetCatalogService NuGetCatalogDiskCache; public ObservableCollection Packages { get; } = new(); public NuGetsAreaViewModel(ILogger? logger = null) { - _catalog = new NuGetCatalogService("https://api.nuget.org/v3/index.json", logger); + nuGetCatalogService = new NuGetCatalogService("https://api.nuget.org/v3/index.json", logger); + nuGetCatalogMemoryCache = new NuGetCatalogMemoryCache(nuGetCatalogService); + + var cachePath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Jnana", "nuget-cache.json"); + + NuGetCatalogDiskCache = new NuGetCatalogDiskCache(nuGetCatalogMemoryCache, cachePath); } public async Task LoadPackagesAsync() { Packages.Clear(); - await foreach (var pkg in _catalog.SearchPackagesAsync("Vanara.", CancellationToken.None)) + await foreach (var pkg in NuGetCatalogDiskCache.SearchPackagesAsync("Vanara.", CancellationToken.None)) { - var latest = await _catalog.GetLatestStableVersionAsync(pkg.Id, CancellationToken.None); + var latest = await NuGetCatalogDiskCache.GetLatestStableVersionAsync(pkg.Id, CancellationToken.None); if (latest != null) Packages.Add(latest); } diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index e461d742..d74e3260 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -18,12 +18,13 @@ + - + diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 92091dfd..03639c3d 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -10,12 +10,10 @@ namespace Jnana.Views; public sealed partial class NuGetsPage : Page { private readonly ILogger? logger; - private INuGetCatalogService nuGetCatalogService; - private INuGetCatalogService nuGetCatalogCache; + public NuGetsAreaViewModel ViewModel { get; } private CancellationToken cancelToken = CancellationToken.None; - public NuGetsPage() { InitializeComponent(); @@ -23,9 +21,6 @@ public NuGetsPage() logger = new NullLogger(); ViewModel = new NuGetsAreaViewModel(logger); - nuGetCatalogService = new NuGetCatalogService("https://api.nuget.org/v3/index.json", logger); - nuGetCatalogCache = new NuGetCatalogCache(nuGetCatalogService); - ViewModel.LoadPackagesAsync().ContinueWith(t => { if (t.IsFaulted) From 7b8fcb7641c69b0e6698e81ae63a4ac6e693a802 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Tue, 9 Jun 2026 20:47:52 +0200 Subject: [PATCH 29/47] Replace `ItemsRepeater` by `TreeView` for showing packages --- Vanara-Jnana/Services/INuGetCatalogService.cs | 4 +- Vanara-Jnana/Services/NuGetCatalogService.cs | 11 +- .../Services/NuGetDiscoveryService.cs | 117 ------------------ Vanara-Jnana/Views/NuGetsPage.xaml | 26 ++-- 4 files changed, 23 insertions(+), 135 deletions(-) delete mode 100644 Vanara-Jnana/Services/NuGetDiscoveryService.cs diff --git a/Vanara-Jnana/Services/INuGetCatalogService.cs b/Vanara-Jnana/Services/INuGetCatalogService.cs index cecbe71c..f602cfb3 100644 --- a/Vanara-Jnana/Services/INuGetCatalogService.cs +++ b/Vanara-Jnana/Services/INuGetCatalogService.cs @@ -22,7 +22,9 @@ public record PackageContent( NuGetVersion Version, IReadOnlyList Assemblies, string? Readme, - byte[]? IconBytes + Stream? ReadmeStream, + byte[]? IconBytes, + Stream? IconBytesStream ); public interface INuGetCatalogService diff --git a/Vanara-Jnana/Services/NuGetCatalogService.cs b/Vanara-Jnana/Services/NuGetCatalogService.cs index ddcb04fb..98acdf41 100644 --- a/Vanara-Jnana/Services/NuGetCatalogService.cs +++ b/Vanara-Jnana/Services/NuGetCatalogService.cs @@ -97,16 +97,17 @@ public async Task DownloadPackageAsync( // var readme = await reader.GetReadmeAsync(token); // var icon = await reader.GetIconAsync(token); - string readme = null; - byte[] icon = null; - + var readmeStream = await reader.GetStreamAsync("readme.md", token); + var iconStream = await reader.GetStreamAsync("icon.png", token); return new PackageContent( id, version, dlls, - readme, - icon + null, + readmeStream, + null, + iconStream ); } } diff --git a/Vanara-Jnana/Services/NuGetDiscoveryService.cs b/Vanara-Jnana/Services/NuGetDiscoveryService.cs deleted file mode 100644 index 2970fb1a..00000000 --- a/Vanara-Jnana/Services/NuGetDiscoveryService.cs +++ /dev/null @@ -1,117 +0,0 @@ -using NuGet.Common; -using NuGet.Configuration; -using NuGet.Packaging; -using NuGet.Packaging.Core; -using NuGet.Protocol; -using NuGet.Protocol.Core.Types; -using NuGet.Versioning; -using System.Runtime.CompilerServices; - -namespace Jnana.Services; - -[Obsolete("This class has been replaced by NuGetCatalogService")] -public sealed class NuGetDiscoveryService -{ - private readonly SourceRepository _repo; - private readonly ILogger? _logger; - - public NuGetDiscoveryService(string sourceUrl, ILogger? logger = null) - { - var provider = new SourceRepositoryProvider( - new PackageSourceProvider(NullSettings.Instance), - Repository.Provider.GetCoreV3()); - - _repo = provider.CreateRepository(new PackageSource(sourceUrl)); - _logger = logger; - } - - // Search for package IDs matching the prefix - public async IAsyncEnumerable SearchPackagesAsync(string prefix, [EnumeratorCancellation] CancellationToken token) - { - var search = await _repo.GetResourceAsync(token); - var filter = new SearchFilter(includePrerelease: false); - - var results = await search.SearchAsync(prefix, filter, 0, 200, _logger, token); - - foreach (var r in results) - yield return new PackageId(r.Identity.Id); - } - - public static async IAsyncEnumerable LoadLatestStablePackagesAsync(string prefix, ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken) - { - var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); - var search = await repository.GetResourceAsync(cancellationToken); - - var filter = new SearchFilter(includePrerelease: true); // Let's filter manually - var results = await search.SearchAsync(prefix, filter, skip: 0, take: 200, logger, cancellationToken); - - foreach (var pkg in results) - { - var versions = await pkg.GetVersionsAsync(); - - var latestStable = versions - .Where(v => !v.Version.IsPrerelease) - .OrderBy(v => v.Version) - .LastOrDefault(); - - if (latestStable is null) - continue; - - yield return pkg; - } - } - - public async Task DownloadPackageAsync(string id, NuGetVersion version, CancellationToken token) - { - var download = await _repo.GetResourceAsync(token); - - var result = await download.GetDownloadResourceResultAsync( - new PackageIdentity(id, version), - new PackageDownloadContext(new SourceCacheContext()), - SettingsUtility.GetGlobalPackagesFolder(NullSettings.Instance), - _logger, - token); - - using var reader = result.PackageReader; - - var dlls = reader.GetLibItems() - .SelectMany(i => i.Items) - .Where(f => f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - .ToList(); - - // TODO: var readme = await reader.GetReadmeAsync(token); - // TODO: var icon = await reader.GetIconAsync(token); - // return new PackageContent(id, version, dlls, readme, icon); - - return new PackageContent(id, version, dlls, null, null); - } - - public static async Task> GetAssemblyFilesAsync( - string id, - NuGetVersion version, - ILogger logger, - CancellationToken cancellationToken) - { - var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); - var find = await repository.GetResourceAsync(cancellationToken); - - using var cache = new SourceCacheContext(); - using var stream = new MemoryStream(); - - bool ok = await find.CopyNupkgToStreamAsync( - id, version, stream, cache, logger, cancellationToken); - - if (!ok) - return Array.Empty(); - - stream.Position = 0; - using var reader = new PackageArchiveReader(stream); - - var dlls = reader.GetFiles() - .Where(f => f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - .ToList(); - - return dlls; - } - -} diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index d74e3260..b2893c03 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -25,18 +25,20 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + From 43b6e3366e92afab6d809874d9fc55ec948f5010 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Tue, 9 Jun 2026 21:47:07 +0200 Subject: [PATCH 30/47] Cleanup --- Vanara-Jnana/Models/ApiAssemblyModel.cs | 5 ----- Vanara-Jnana/Models/ApiEnumModel.cs | 5 ----- Vanara-Jnana/Models/ApiMethodModel.cs | 5 ----- Vanara-Jnana/Models/ApiNamespaceModel.cs | 5 ----- Vanara-Jnana/Models/ApiPropertyModel.cs | 5 ----- Vanara-Jnana/Models/ApiSampleModel.cs | 5 ----- Vanara-Jnana/Models/ApiTypeModel.cs | 5 ----- 7 files changed, 35 deletions(-) delete mode 100644 Vanara-Jnana/Models/ApiAssemblyModel.cs delete mode 100644 Vanara-Jnana/Models/ApiEnumModel.cs delete mode 100644 Vanara-Jnana/Models/ApiMethodModel.cs delete mode 100644 Vanara-Jnana/Models/ApiNamespaceModel.cs delete mode 100644 Vanara-Jnana/Models/ApiPropertyModel.cs delete mode 100644 Vanara-Jnana/Models/ApiSampleModel.cs delete mode 100644 Vanara-Jnana/Models/ApiTypeModel.cs diff --git a/Vanara-Jnana/Models/ApiAssemblyModel.cs b/Vanara-Jnana/Models/ApiAssemblyModel.cs deleted file mode 100644 index 12e786ba..00000000 --- a/Vanara-Jnana/Models/ApiAssemblyModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ClassicSamplesBrowser.Models; - -internal class ApiAssemblyModel -{ -} \ No newline at end of file diff --git a/Vanara-Jnana/Models/ApiEnumModel.cs b/Vanara-Jnana/Models/ApiEnumModel.cs deleted file mode 100644 index b3fd6f9b..00000000 --- a/Vanara-Jnana/Models/ApiEnumModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ClassicSamplesBrowser.Models; - -internal class ApiEnumModel -{ -} \ No newline at end of file diff --git a/Vanara-Jnana/Models/ApiMethodModel.cs b/Vanara-Jnana/Models/ApiMethodModel.cs deleted file mode 100644 index e7f45188..00000000 --- a/Vanara-Jnana/Models/ApiMethodModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ClassicSamplesBrowser.Models; - -internal class ApiMethodModel -{ -} \ No newline at end of file diff --git a/Vanara-Jnana/Models/ApiNamespaceModel.cs b/Vanara-Jnana/Models/ApiNamespaceModel.cs deleted file mode 100644 index 6c0e2e09..00000000 --- a/Vanara-Jnana/Models/ApiNamespaceModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ClassicSamplesBrowser.Models; - -internal class ApiNamespaceModel -{ -} \ No newline at end of file diff --git a/Vanara-Jnana/Models/ApiPropertyModel.cs b/Vanara-Jnana/Models/ApiPropertyModel.cs deleted file mode 100644 index bc91c5d7..00000000 --- a/Vanara-Jnana/Models/ApiPropertyModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ClassicSamplesBrowser.Models; - -internal class ApiPropertyModel -{ -} \ No newline at end of file diff --git a/Vanara-Jnana/Models/ApiSampleModel.cs b/Vanara-Jnana/Models/ApiSampleModel.cs deleted file mode 100644 index 650c7826..00000000 --- a/Vanara-Jnana/Models/ApiSampleModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ClassicSamplesBrowser.Models; - -internal class ApiSampleModel -{ -} \ No newline at end of file diff --git a/Vanara-Jnana/Models/ApiTypeModel.cs b/Vanara-Jnana/Models/ApiTypeModel.cs deleted file mode 100644 index 5bdd927b..00000000 --- a/Vanara-Jnana/Models/ApiTypeModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ClassicSamplesBrowser.Models; - -internal class ApiTypeModel -{ -} \ No newline at end of file From 492bd9de2e32ee2533ae7b8cc4b740b31668f154 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Wed, 10 Jun 2026 12:13:11 +0200 Subject: [PATCH 31/47] Add `ImageExtensions.cs` Helper --- Vanara-Jnana/Helpers/ImageExtensions.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Vanara-Jnana/Helpers/ImageExtensions.cs diff --git a/Vanara-Jnana/Helpers/ImageExtensions.cs b/Vanara-Jnana/Helpers/ImageExtensions.cs new file mode 100644 index 00000000..9452ed3b --- /dev/null +++ b/Vanara-Jnana/Helpers/ImageExtensions.cs @@ -0,0 +1,25 @@ +using Microsoft.UI.Xaml.Media.Imaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jnana.Helpers; + +public class ImageExtensions +{ + public static BitmapImage? ToBitmapImage(byte[]? bytes) + { + if (bytes == null || bytes.Length == 0) + return null; + + var bitmap = new BitmapImage(); + + using var ms = new MemoryStream(bytes); + var ras = ms.AsRandomAccessStream(); + + bitmap.SetSource(ras); + return bitmap; + } +} From e79ef7580b50811e66346b791eda37da82ded9ea Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Wed, 10 Jun 2026 12:19:12 +0200 Subject: [PATCH 32/47] Add Icon to `PackageContent` and `PackageVersionInfo` --- Vanara-Jnana/Services/INuGetCatalogService.cs | 9 +-- Vanara-Jnana/Services/NuGetCatalogService.cs | 64 +++++++++++++------ 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/Vanara-Jnana/Services/INuGetCatalogService.cs b/Vanara-Jnana/Services/INuGetCatalogService.cs index f602cfb3..3b0e13c8 100644 --- a/Vanara-Jnana/Services/INuGetCatalogService.cs +++ b/Vanara-Jnana/Services/INuGetCatalogService.cs @@ -1,4 +1,5 @@ -using NuGet.Versioning; +using Microsoft.UI.Xaml.Media.Imaging; +using NuGet.Versioning; using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +15,8 @@ public record PackageVersionInfo( string Version, Uri? IconUrl, string? Description, - string? Summary + string? Summary, + BitmapImage? Icon ); public record PackageContent( @@ -22,9 +24,8 @@ public record PackageContent( NuGetVersion Version, IReadOnlyList Assemblies, string? Readme, - Stream? ReadmeStream, byte[]? IconBytes, - Stream? IconBytesStream + BitmapImage? Icon ); public interface INuGetCatalogService diff --git a/Vanara-Jnana/Services/NuGetCatalogService.cs b/Vanara-Jnana/Services/NuGetCatalogService.cs index 98acdf41..c40d790b 100644 --- a/Vanara-Jnana/Services/NuGetCatalogService.cs +++ b/Vanara-Jnana/Services/NuGetCatalogService.cs @@ -1,16 +1,11 @@ -using NuGet.Common; -using NuGet.Configuration; +using Jnana.Helpers; +using Microsoft.UI.Xaml.Media.Imaging; +using NuGet.Common; using NuGet.Packaging; -using NuGet.Packaging.Core; -using NuGet.Protocol; using NuGet.Protocol.Core.Types; +using NuGet.Protocol; using NuGet.Versioning; -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; namespace Jnana.Services; @@ -32,6 +27,9 @@ public async IAsyncEnumerable SearchPackagesAsync( var search = await _repo.GetResourceAsync(token); var filter = new SearchFilter(includePrerelease: true); + // TODO: @dahall NuGet's search API doesn't support prefix searching, + // TODO: so we fetch a bunch of results and then would have to filter them ourselves. + // TODO: See the results, they include `MonitoringUtils` and `LeadManager.Common`. These appear in vs NuGet pack, too. var results = await search.SearchAsync(prefix, filter, 0, 200, _logger, token); foreach (var r in results) @@ -66,7 +64,8 @@ public async IAsyncEnumerable SearchPackagesAsync( latestStable.Version.ToString(), pkg.IconUrl, pkg.Description, - pkg.Summary + pkg.Summary, + Icon: null ); } @@ -88,26 +87,49 @@ public async Task DownloadPackageAsync( stream.Position = 0; using var reader = new PackageArchiveReader(stream); + var packageFiles = reader.GetFiles().ToList(); - var dlls = reader.GetFiles() + // First, extract all DLLs + var dlls = packageFiles .Where(f => f.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) .ToList(); - // TODO: Use ExtractFile to load the files - // var readme = await reader.GetReadmeAsync(token); - // var icon = await reader.GetIconAsync(token); - - var readmeStream = await reader.GetStreamAsync("readme.md", token); - var iconStream = await reader.GetStreamAsync("icon.png", token); + // Extract the readme if it exists + string? readmeText = null; + var readmePath = packageFiles.FirstOrDefault(f => + f.EndsWith("readme.md", StringComparison.OrdinalIgnoreCase)); + + if (readmePath != null) + { + using var readmeStream = await reader.GetStreamAsync(readmePath, token); + using var sr = new StreamReader(readmeStream); + readmeText = await sr.ReadToEndAsync(); + } + + // Extract the icon if it exists + byte[]? iconBytes = null; + var iconPath = packageFiles.FirstOrDefault(f => + f.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || + f.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)); + + BitmapImage? Icon = null; + if (iconPath != null) + { + using var iconStream = await reader.GetStreamAsync(iconPath, token); + using var ms = new MemoryStream(); + await iconStream.CopyToAsync(ms, token); + iconBytes = ms.ToArray(); + + Icon = ImageExtensions.ToBitmapImage(iconBytes); + } return new PackageContent( id, version, dlls, - null, - readmeStream, - null, - iconStream + readmeText, + iconBytes, + Icon ); } } From ab7207f2c881461aa925039c86dc50473fd6f02f Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Wed, 10 Jun 2026 14:25:23 +0200 Subject: [PATCH 33/47] Add `NuGetPackageDetailViewModel` and prepare Details View for data binding --- .../ViewModels/NuGetPackageDetailViewModel.cs | 68 +++++++++++++++++++ .../ViewModels/NuGetsAreaViewModel.cs | 16 +++-- Vanara-Jnana/Views/NuGetsPage.xaml | 22 ++++-- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 21 ++++++ 4 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs diff --git a/Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs b/Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs new file mode 100644 index 00000000..e87b9259 --- /dev/null +++ b/Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs @@ -0,0 +1,68 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Jnana.Services; +using Jnana.Helpers; +using Microsoft.UI.Xaml.Media.Imaging; +using NuGet.Versioning; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Jnana.ViewModels; + +public sealed partial class NuGetPackageDetailViewModel : ObservableObject +{ + private readonly INuGetCatalogService _catalog; + private PackageContent? _content; + public string? Id => _content?.Id; + public string? Version => _content?.Version?.ToNormalizedString(); + public string? Readme => _content?.Readme; + + private BitmapImage? _icon; + public BitmapImage? Icon + { + get + { + if (_icon == null && _content?.IconBytes != null) + _icon = ImageExtensions.ToBitmapImage(_content.IconBytes); + return _icon; + } + } + public NuGetPackageDetailViewModel(INuGetCatalogService catalog) + { + _catalog = catalog; + } + + public async Task LoadAsync(string id, NuGetVersion version, CancellationToken token) + { + _content = await _catalog.DownloadPackageAsync(id, version, token); + OnPropertyChanged(nameof(Id)); + OnPropertyChanged(nameof(Version)); + OnPropertyChanged(nameof(Readme)); + OnPropertyChanged(nameof(Icon)); + } + + public void Clear() + { + _content = null; + _icon = null; + OnPropertyChanged(nameof(Id)); + OnPropertyChanged(nameof(Version)); + OnPropertyChanged(nameof(Readme)); + OnPropertyChanged(nameof(Icon)); + } + + /* + // Handle the selection of the package version info + //await PackageDetailViewModel.LoadAsync(SelectedPackage.Id, NuGet.Versioning.NuGetVersion.Parse(SelectedPackage.Version), cancelToken).ContinueWith(t => + //{ + // if (t.IsFaulted) + // { + // Debug.Print($"Failed to load package details: {t.Exception}"); + // //logger?.LogError(t.Exception, "Failed to load package details"); + // } + //}, cancelToken); + // + //await PackageDetailViewModel.LoadAsync(SelectedPackage.Id, NuGet.Versioning.NuGetVersion.Parse(SelectedPackage.Version), CancellationToken.None); + + */ +} diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index 06ef0d89..c87168b0 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -12,9 +12,13 @@ namespace Jnana.ViewModels; public partial class NuGetsAreaViewModel : ObservableObject { - private INuGetCatalogService nuGetCatalogService; - private INuGetCatalogService nuGetCatalogMemoryCache; - private INuGetCatalogService NuGetCatalogDiskCache; + private readonly NuGetCatalogService nuGetCatalogService; + private readonly NuGetCatalogMemoryCache nuGetCatalogMemoryCache; + private readonly NuGetCatalogDiskCache _nuGetCatalogDiskCache; + + /// Get the current INuGetCatalogService that is in use. + public INuGetCatalogService NuGetCatalogService => _nuGetCatalogDiskCache; + public ObservableCollection Packages { get; } = new(); public NuGetsAreaViewModel(ILogger? logger = null) @@ -25,16 +29,16 @@ public NuGetsAreaViewModel(ILogger? logger = null) var cachePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Jnana", "nuget-cache.json"); - NuGetCatalogDiskCache = new NuGetCatalogDiskCache(nuGetCatalogMemoryCache, cachePath); + _nuGetCatalogDiskCache = new NuGetCatalogDiskCache(nuGetCatalogMemoryCache, cachePath); } public async Task LoadPackagesAsync() { Packages.Clear(); - await foreach (var pkg in NuGetCatalogDiskCache.SearchPackagesAsync("Vanara.", CancellationToken.None)) + await foreach (var pkg in _nuGetCatalogDiskCache.SearchPackagesAsync("Vanara.", CancellationToken.None)) { - var latest = await NuGetCatalogDiskCache.GetLatestStableVersionAsync(pkg.Id, CancellationToken.None); + var latest = await _nuGetCatalogDiskCache.GetLatestStableVersionAsync(pkg.Id, CancellationToken.None); if (latest != null) Packages.Add(latest); } diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index b2893c03..f04bddc3 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -19,13 +19,16 @@ + + + - + @@ -40,11 +43,20 @@ - + + - - + + + + + + + + + + + - diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 03639c3d..13c37a81 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -2,7 +2,9 @@ using Jnana.Services; using Jnana.ViewModels; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; using NuGet.Common; +using NuGet.Versioning; using System.Diagnostics; namespace Jnana.Views; @@ -12,7 +14,10 @@ public sealed partial class NuGetsPage : Page private readonly ILogger? logger; public NuGetsAreaViewModel ViewModel { get; } + public NuGetPackageDetailViewModel PackageDetailViewModel { get; } + private CancellationToken cancelToken = CancellationToken.None; + public PackageVersionInfo? SelectedPackage { get; private set; } = null; public NuGetsPage() { @@ -20,6 +25,7 @@ public NuGetsPage() logger = new NullLogger(); ViewModel = new NuGetsAreaViewModel(logger); + PackageDetailViewModel = new NuGetPackageDetailViewModel(ViewModel.NuGetCatalogService); ViewModel.LoadPackagesAsync().ContinueWith(t => { @@ -29,6 +35,21 @@ public NuGetsPage() //logger?.LogError(t.Exception, "Failed to load NuGet packages"); } }, cancelToken); + } + + private void PackageTreeView_SelectionChanged(TreeView sender, TreeViewSelectionChangedEventArgs args) + { + if (sender.Equals(PackageTreeView)) + { + SelectedPackage = args.AddedItems.FirstOrDefault() as PackageVersionInfo; + + if (SelectedPackage != null) + { + Debug.Print($"Selected package: {SelectedPackage.Id} {SelectedPackage.Version}"); + PackageDetailViewModel.Clear(); + _ = PackageDetailViewModel.LoadAsync(SelectedPackage.Id, NuGetVersion.Parse(SelectedPackage.Version), cancelToken); + } + } } } From 112dbaeac15d86dda65dc34f1fd80d181593467f Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Wed, 10 Jun 2026 18:44:47 +0200 Subject: [PATCH 34/47] Add `SelectVersionCommand` to `NuGetsPage` --- .../ViewModels/NuGetPackageDetailViewModel.cs | 8 ++--- .../ViewModels/NuGetsAreaViewModel.cs | 7 +---- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 29 +++++++++++++------ 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs b/Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs index e87b9259..be5d0484 100644 --- a/Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetPackageDetailViewModel.cs @@ -1,11 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; -using Jnana.Services; using Jnana.Helpers; +using Jnana.Services; using Microsoft.UI.Xaml.Media.Imaging; using NuGet.Versioning; -using System.IO; -using System.Threading; -using System.Threading.Tasks; namespace Jnana.ViewModels; @@ -30,6 +27,9 @@ public BitmapImage? Icon public NuGetPackageDetailViewModel(INuGetCatalogService catalog) { _catalog = catalog; + + // TODO: Consider adding a placeholder icon here while the real one is loading, or if it fails to load. + // TODO: Bind Details Pane against Package Details View Model, and show } public async Task LoadAsync(string id, NuGetVersion version, CancellationToken token) diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index c87168b0..786f9d5a 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -1,12 +1,7 @@ -using ClassicSamplesBrowser.Vanara.Reflection; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.ComponentModel; using Jnana.Services; using NuGet.Common; -using NuGet.Protocol.Core.Types; using System.Collections.ObjectModel; -using System.Runtime.CompilerServices; -using System.Windows.Input; namespace Jnana.ViewModels; diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 13c37a81..6ac64a4a 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -1,23 +1,23 @@ -using Jnana.Helpers; +using CommunityToolkit.Mvvm.Input; using Jnana.Services; using Jnana.ViewModels; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; using NuGet.Common; using NuGet.Versioning; using System.Diagnostics; +using System.Windows.Input; namespace Jnana.Views; public sealed partial class NuGetsPage : Page { private readonly ILogger? logger; - public NuGetsAreaViewModel ViewModel { get; } public NuGetPackageDetailViewModel PackageDetailViewModel { get; } private CancellationToken cancelToken = CancellationToken.None; public PackageVersionInfo? SelectedPackage { get; private set; } = null; + public ICommand SelectVersionCommand { get; } public NuGetsPage() { @@ -27,14 +27,16 @@ public NuGetsPage() ViewModel = new NuGetsAreaViewModel(logger); PackageDetailViewModel = new NuGetPackageDetailViewModel(ViewModel.NuGetCatalogService); + SelectVersionCommand = new RelayCommand(OnVersionSelected); + ViewModel.LoadPackagesAsync().ContinueWith(t => - { - if (t.IsFaulted) { - Debug.Print($"Failed to load NuGet packages: {t.Exception}"); - //logger?.LogError(t.Exception, "Failed to load NuGet packages"); - } - }, cancelToken); + if (t.IsFaulted) + { + Debug.Print($"Failed to load NuGet packages: {t.Exception}"); + //logger?.LogError(t.Exception, "Failed to load NuGet packages"); + } + }, cancelToken); } private void PackageTreeView_SelectionChanged(TreeView sender, TreeViewSelectionChangedEventArgs args) @@ -52,4 +54,13 @@ private void PackageTreeView_SelectionChanged(TreeView sender, TreeViewSelection } } } + + private async void OnVersionSelected(PackageVersionInfo? version) + { + if (version != null) + { + // TODO: Update package Detail view with the selected version + await PackageDetailViewModel.LoadAsync(version.Id, NuGetVersion.Parse(version.Version), CancellationToken.None); + } + } } From 414d1cc14441240f2a81e7cc7294f70654d25fcf Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Wed, 10 Jun 2026 19:07:54 +0200 Subject: [PATCH 35/47] Add `NuGetPackageDetailViewModel` to `NuGetsAreaViewModel` --- Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs | 13 ++++++++----- Vanara-Jnana/Views/NuGetsPage.xaml | 8 ++++---- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 8 +++----- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index 786f9d5a..90797908 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -9,13 +9,15 @@ public partial class NuGetsAreaViewModel : ObservableObject { private readonly NuGetCatalogService nuGetCatalogService; private readonly NuGetCatalogMemoryCache nuGetCatalogMemoryCache; - private readonly NuGetCatalogDiskCache _nuGetCatalogDiskCache; + private readonly NuGetCatalogDiskCache nuGetCatalogDiskCache; /// Get the current INuGetCatalogService that is in use. - public INuGetCatalogService NuGetCatalogService => _nuGetCatalogDiskCache; + public INuGetCatalogService NuGetCatalogService => nuGetCatalogDiskCache; public ObservableCollection Packages { get; } = new(); + public NuGetPackageDetailViewModel PackageDetailViewModel { get; } + public NuGetsAreaViewModel(ILogger? logger = null) { nuGetCatalogService = new NuGetCatalogService("https://api.nuget.org/v3/index.json", logger); @@ -24,16 +26,17 @@ public NuGetsAreaViewModel(ILogger? logger = null) var cachePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Jnana", "nuget-cache.json"); - _nuGetCatalogDiskCache = new NuGetCatalogDiskCache(nuGetCatalogMemoryCache, cachePath); + nuGetCatalogDiskCache = new NuGetCatalogDiskCache(nuGetCatalogMemoryCache, cachePath); + PackageDetailViewModel = new NuGetPackageDetailViewModel(nuGetCatalogDiskCache); } public async Task LoadPackagesAsync() { Packages.Clear(); - await foreach (var pkg in _nuGetCatalogDiskCache.SearchPackagesAsync("Vanara.", CancellationToken.None)) + await foreach (var pkg in nuGetCatalogDiskCache.SearchPackagesAsync("Vanara.", CancellationToken.None)) { - var latest = await _nuGetCatalogDiskCache.GetLatestStableVersionAsync(pkg.Id, CancellationToken.None); + var latest = await nuGetCatalogDiskCache.GetLatestStableVersionAsync(pkg.Id, CancellationToken.None); if (latest != null) Packages.Add(latest); } diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index f04bddc3..677ec358 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -12,7 +12,7 @@ - + @@ -48,10 +48,10 @@ - + - - + + diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 6ac64a4a..901d4e51 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -13,7 +13,6 @@ public sealed partial class NuGetsPage : Page { private readonly ILogger? logger; public NuGetsAreaViewModel ViewModel { get; } - public NuGetPackageDetailViewModel PackageDetailViewModel { get; } private CancellationToken cancelToken = CancellationToken.None; public PackageVersionInfo? SelectedPackage { get; private set; } = null; @@ -25,7 +24,6 @@ public NuGetsPage() logger = new NullLogger(); ViewModel = new NuGetsAreaViewModel(logger); - PackageDetailViewModel = new NuGetPackageDetailViewModel(ViewModel.NuGetCatalogService); SelectVersionCommand = new RelayCommand(OnVersionSelected); @@ -49,8 +47,7 @@ private void PackageTreeView_SelectionChanged(TreeView sender, TreeViewSelection { Debug.Print($"Selected package: {SelectedPackage.Id} {SelectedPackage.Version}"); - PackageDetailViewModel.Clear(); - _ = PackageDetailViewModel.LoadAsync(SelectedPackage.Id, NuGetVersion.Parse(SelectedPackage.Version), cancelToken); + OnVersionSelected(SelectedPackage); } } } @@ -60,7 +57,8 @@ private async void OnVersionSelected(PackageVersionInfo? version) if (version != null) { // TODO: Update package Detail view with the selected version - await PackageDetailViewModel.LoadAsync(version.Id, NuGetVersion.Parse(version.Version), CancellationToken.None); + await ViewModel.PackageDetailViewModel.LoadAsync(version.Id, NuGetVersion.Parse(version.Version), CancellationToken.None); } + // await PackageDetailViewModel.LoadAsync(version.Id, NuGetVersion.Parse(version.Version), CancellationToken.None); } } From ebfbc2adfc560e1fcd96cf27d448da4f433507a1 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Wed, 10 Jun 2026 22:15:44 +0200 Subject: [PATCH 36/47] Move `SelectVersionCommand` to `NuGetsAreaViewModel` and fix Binding --- .../ViewModels/NuGetsAreaViewModel.cs | 23 +++++++++++++++++++ Vanara-Jnana/Views/NuGetsPage.xaml | 21 +++++++++-------- Vanara-Jnana/Views/NuGetsPage.xaml.cs | 17 ++------------ 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs index 90797908..75aea8a5 100644 --- a/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs +++ b/Vanara-Jnana/ViewModels/NuGetsAreaViewModel.cs @@ -1,7 +1,11 @@ using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using Jnana.Services; using NuGet.Common; +using NuGet.Versioning; using System.Collections.ObjectModel; +using System.Windows.Input; +using Windows.ApplicationModel.Background; namespace Jnana.ViewModels; @@ -18,6 +22,8 @@ public partial class NuGetsAreaViewModel : ObservableObject public NuGetPackageDetailViewModel PackageDetailViewModel { get; } + public ICommand SelectVersionCommand { get; } + public NuGetsAreaViewModel(ILogger? logger = null) { nuGetCatalogService = new NuGetCatalogService("https://api.nuget.org/v3/index.json", logger); @@ -28,6 +34,8 @@ public NuGetsAreaViewModel(ILogger? logger = null) nuGetCatalogDiskCache = new NuGetCatalogDiskCache(nuGetCatalogMemoryCache, cachePath); PackageDetailViewModel = new NuGetPackageDetailViewModel(nuGetCatalogDiskCache); + + SelectVersionCommand = new RelayCommand(OnVersionSelected); } public async Task LoadPackagesAsync() @@ -41,4 +49,19 @@ public async Task LoadPackagesAsync() Packages.Add(latest); } } + + private async void OnVersionSelected(PackageVersionInfo? version) + { + if (version != null) + { + // TODO: Update package Detail view with the selected version + await PackageDetailViewModel.LoadAsync(version.Id, NuGetVersion.Parse(version.Version), CancellationToken.None); + } + else + { + // Make sure all values are cleared if no version is selected + PackageDetailViewModel.Clear(); + } + // TODO: Update Details pane to show the selected version's details + } } diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index 677ec358..2ec93dac 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -6,8 +6,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="using:Jnana.Services" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - DataContext="{Binding RelativeSource={RelativeSource Self}}"> + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> @@ -24,7 +23,7 @@ - + @@ -46,16 +45,18 @@ - - - + + + + + + + - - + + - - diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml.cs b/Vanara-Jnana/Views/NuGetsPage.xaml.cs index 901d4e51..c8180a30 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml.cs +++ b/Vanara-Jnana/Views/NuGetsPage.xaml.cs @@ -16,7 +16,6 @@ public sealed partial class NuGetsPage : Page private CancellationToken cancelToken = CancellationToken.None; public PackageVersionInfo? SelectedPackage { get; private set; } = null; - public ICommand SelectVersionCommand { get; } public NuGetsPage() { @@ -25,8 +24,7 @@ public NuGetsPage() logger = new NullLogger(); ViewModel = new NuGetsAreaViewModel(logger); - SelectVersionCommand = new RelayCommand(OnVersionSelected); - + ViewModel.LoadPackagesAsync().ContinueWith(t => { if (t.IsFaulted) @@ -46,19 +44,8 @@ private void PackageTreeView_SelectionChanged(TreeView sender, TreeViewSelection if (SelectedPackage != null) { Debug.Print($"Selected package: {SelectedPackage.Id} {SelectedPackage.Version}"); - - OnVersionSelected(SelectedPackage); + ViewModel.SelectVersionCommand.Execute(SelectedPackage); } } } - - private async void OnVersionSelected(PackageVersionInfo? version) - { - if (version != null) - { - // TODO: Update package Detail view with the selected version - await ViewModel.PackageDetailViewModel.LoadAsync(version.Id, NuGetVersion.Parse(version.Version), CancellationToken.None); - } - // await PackageDetailViewModel.LoadAsync(version.Id, NuGetVersion.Parse(version.Version), CancellationToken.None); - } } From 69f4254d7015bfbbe1d5bbac4f524636d7baa603 Mon Sep 17 00:00:00 2001 From: Thorsten Jung Date: Thu, 11 Jun 2026 00:08:40 +0200 Subject: [PATCH 37/47] Add `MarkdownTextBlock` for rendering the README --- Vanara-Jnana/Jnana.csproj | 3 ++- Vanara-Jnana/Views/NuGetsPage.xaml | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Vanara-Jnana/Jnana.csproj b/Vanara-Jnana/Jnana.csproj index f563cc91..35f7022c 100644 --- a/Vanara-Jnana/Jnana.csproj +++ b/Vanara-Jnana/Jnana.csproj @@ -26,7 +26,8 @@ - + + diff --git a/Vanara-Jnana/Views/NuGetsPage.xaml b/Vanara-Jnana/Views/NuGetsPage.xaml index 2ec93dac..2bf86796 100644 --- a/Vanara-Jnana/Views/NuGetsPage.xaml +++ b/Vanara-Jnana/Views/NuGetsPage.xaml @@ -6,7 +6,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="using:Jnana.Services" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"> @@ -49,6 +50,7 @@ + @@ -57,6 +59,14 @@ + + + + +