diff --git a/src/NuGet.Clients/NuGet.Indexing/PublicAPI.Unshipped.txt b/src/NuGet.Clients/NuGet.Indexing/PublicAPI.Unshipped.txt index 3a9c211a476..7dc5c58110b 100644 --- a/src/NuGet.Clients/NuGet.Indexing/PublicAPI.Unshipped.txt +++ b/src/NuGet.Clients/NuGet.Indexing/PublicAPI.Unshipped.txt @@ -1,8 +1 @@ #nullable enable -~const NuGet.Protocol.JsonProperties.ReadmeFileUrl = "readmeFileUrl" -> string -~NuGet.Protocol.Core.Types.IPackageSearchMetadata.ReadmeFileUrl.get -> string -~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.ReadmeFileUrl.get -> string -~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.ReadmeFileUrl.set -> void -~NuGet.Protocol.LocalPackageSearchMetadata.ReadmeFileUrl.get -> string -~NuGet.Protocol.PackageSearchMetadata.ReadmeFileUrl.get -> string -~NuGet.Protocol.PackageSearchMetadataV2Feed.ReadmeFileUrl.get -> string diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs index 31038104ff5..d1ee59605cd 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs @@ -87,7 +87,7 @@ [assembly: SuppressMessage("Build", "CA1501:'PackageManagerControl' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PackageManagerControl")] [assembly: SuppressMessage("Build", "CA1501:'PackageItemDeprecationLabel' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "Default WPF Hierarchy", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PackageItemDeprecationLabel")] [assembly: SuppressMessage("Build", "CA1501:'PackageManagerTopPanel' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PackageManagerTopPanel")] -[assembly: SuppressMessage("Build", "CA1501:'PackageMetadataControl' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PackageMetadataControl")] +[assembly: SuppressMessage("Build", "CA1501:'PackageMetadataControl' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PackageDetailsTabControl")] [assembly: SuppressMessage("Build", "CA1501:'PackageRestoreBar' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PackageRestoreBar")] [assembly: SuppressMessage("Build", "CA1501:'PreviewWindow' has an object hierarchy '11' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'DialogWindow, DialogWindowBase, Window, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PreviewWindow")] [assembly: SuppressMessage("Build", "CA1501:'PRMigratorBar' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PRMigratorBar")] diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs index 124559b48ff..1ba6d67d775 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs @@ -24,7 +24,7 @@ namespace NuGet.PackageManagement.UI /// The base class of PackageDetailControlModel and PackageSolutionDetailControlModel. /// When user selects an action, this triggers version list update. /// - public abstract class DetailControlModel : INotifyPropertyChanged, IDisposable + public abstract class DetailControlModel : TitledPageViewModelBase, IDisposable { private static readonly string StarAll = VersionRangeFormatter.Instance.Format("p", VersionRange.Parse("*"), VersionRangeFormatter.Instance); private static readonly string StarAllFloating = VersionRangeFormatter.Instance.Format("p", VersionRange.Parse("*-*"), VersionRangeFormatter.Instance); @@ -70,6 +70,9 @@ protected DetailControlModel( _options.SelectedChanged += DependencyBehavior_SelectedChanged; _versions = new ItemsChangeObservableCollection(); + + Title = Resources.Label_PackageDetails; + IsVisible = true; } /// @@ -385,11 +388,9 @@ private async Task> GetDependencies(IProjectCon // Called after package install/uninstall. public abstract Task RefreshAsync(CancellationToken cancellationToken); - public event PropertyChangedEventHandler PropertyChanged; - protected void OnPropertyChanged(string propertyName) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + RaisePropertyChanged(propertyName); } public string Id => _searchResultPackage?.Id; diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailedPackageMetadata.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailedPackageMetadata.cs index 800efffb7d4..d685df31f76 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailedPackageMetadata.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailedPackageMetadata.cs @@ -33,6 +33,7 @@ public DetailedPackageMetadata(PackageSearchMetadataContextInfo serverData, Pack IconUrl = serverData.IconUrl; LicenseUrl = serverData.LicenseUrl; ProjectUrl = serverData.ProjectUrl; + ReadmeFileUrl = serverData.ReadmeFileUrl; ReadmeUrl = serverData.ReadmeUrl; ReportAbuseUrl = serverData.ReportAbuseUrl; // Some server implementations send down an array with an empty string, which ends up as an empty string. @@ -95,6 +96,8 @@ public DetailedPackageMetadata(PackageSearchMetadataContextInfo serverData, Pack public Uri? ProjectUrl { get; set; } + public string? ReadmeFileUrl { get; set; } + public Uri? ReadmeUrl { get; set; } public Uri? ReportAbuseUrl { get; set; } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageMetadataTab.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageMetadataTab.cs new file mode 100644 index 00000000000..87447b0dfd3 --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageMetadataTab.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace NuGet.PackageManagement.UI +{ + public enum PackageMetadataTab + { + Readme, + PackageDetails, + } +} diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs index 30a08ee717b..b5b2880d9b8 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs @@ -807,6 +807,15 @@ public static string Error_SourceMapping_GPF_NotEnabled { } } + /// + /// Looks up a localized string similar to An error occurred while trying to load the README.. + /// + public static string Error_UnableToLoadReadme { + get { + return ResourceManager.GetString("Error_UnableToLoadReadme", resourceCulture); + } + } + /// /// Looks up a localized string similar to Operation failed. /// @@ -1230,6 +1239,15 @@ public static string Label_PackageDeprecatedToolTip { } } + /// + /// Looks up a localized string similar to Package Details. + /// + public static string Label_PackageDetails { + get { + return ResourceManager.GetString("Label_PackageDetails", resourceCulture); + } + } + /// /// Looks up a localized string similar to NuGet Package Manager: {0}. /// @@ -1284,6 +1302,15 @@ public static string Label_Readme { } } + /// + /// Looks up a localized string similar to README. + /// + public static string Label_Readme_Tab { + get { + return ResourceManager.GetString("Label_Readme_Tab", resourceCulture); + } + } + /// /// Looks up a localized string similar to Report Abuse:. /// @@ -2051,6 +2078,21 @@ public static string Text_NoPackagesFound { } } + /// + /// Looks up a localized string similar to Could not read package README from selected package. + /// + ///Only the package maintainer can add a README. + /// + ///If you are not the maintainer, please consider filing an issue or contacting the maintainer to request a README. + /// + ///For instructions on how to add a README, please visit [aka.ms/nuget/readme](https://aka.ms/nuget/readme). + /// + public static string Text_NoReadme { + get { + return ResourceManager.GetString("Text_NoReadme", resourceCulture); + } + } + /// /// Looks up a localized string similar to Not available in this source. /// diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.resx b/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.resx index 879d025c61d..4a40914b2d1 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.resx +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.resx @@ -1076,4 +1076,23 @@ Please see https://aka.ms/troubleshoot_nuget_cache for more help. Package Level + + Package Details + + + An error occurred while trying to load the README. + + + README + + + Could not read package README from selected package. + +Only the package maintainer can add a README. + +If you are not the maintainer, please consider filing an issue or contacting the maintainer to request a README. + +For instructions on how to add a README, please visit [aka.ms/nuget/readme](https://aka.ms/nuget/readme) + {Locked="[aka.ms/nuget/readme](https://aka.ms/nuget/readme)"} "[aka.ms/nuget/readme](https://aka.ms/nuget/readme)" this is a URL link and should not be translated + diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/UserSettings.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/UserSettings.cs index 4eca016167a..a2e99cf127b 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/UserSettings.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/UserSettings.cs @@ -23,6 +23,7 @@ public UserSettings() SelectedFilter = ItemFilter.Installed; DependencyBehavior = DependencyBehavior.Lowest; FileConflictAction = FileConflictAction.PromptUser; + SelectedPackageMetadataTab = PackageMetadataTab.Readme; OptionsExpanded = false; } @@ -40,6 +41,8 @@ public UserSettings() public ItemFilter SelectedFilter { get; set; } + public PackageMetadataTab SelectedPackageMetadataTab { get; set; } + public DependencyBehavior DependencyBehavior { get; set; } public FileConflictAction FileConflictAction { get; set; } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageDetailsTabViewModel.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageDetailsTabViewModel.cs new file mode 100644 index 00000000000..51a7b000e0f --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageDetailsTabViewModel.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Shell; +using NuGet.VisualStudio; +using NuGet.VisualStudio.Internal.Contracts; + +namespace NuGet.PackageManagement.UI.ViewModels +{ + public class PackageDetailsTabViewModel : ViewModelBase, IDisposable + { + private bool _disposed = false; + private bool _readmeTabEnabled; + + public ReadmePreviewViewModel ReadmePreviewViewModel { get; private set; } + + public DetailControlModel DetailControlModel { get; private set; } + + public ObservableCollection Tabs { get; private set; } + + private TitledPageViewModelBase _selectedTab; + public TitledPageViewModelBase SelectedTab + { + get => _selectedTab; + set + { + SetAndRaisePropertyChanged(ref _selectedTab, value); + } + } + + public PackageDetailsTabViewModel() + { + _readmeTabEnabled = true; + Tabs = new ObservableCollection(); + } + + public async Task InitializeAsync(DetailControlModel detailControlModel, INuGetPackageFileService nugetPackageFileService, ItemFilter currentFilter, PackageMetadataTab initialSelectedTab) + { + var nuGetFeatureFlagService = await ServiceLocator.GetComponentModelServiceAsync(); + _readmeTabEnabled = await nuGetFeatureFlagService.IsFeatureEnabledAsync(NuGetFeatureFlagConstants.RenderReadmeInPMUI); + + ReadmePreviewViewModel = new ReadmePreviewViewModel(nugetPackageFileService, currentFilter, _readmeTabEnabled); + DetailControlModel = detailControlModel; + + Tabs.Add(ReadmePreviewViewModel); + Tabs.Add(DetailControlModel); + + SelectedTab = Tabs.FirstOrDefault(t => t.IsVisible && ConvertFromTabType(t) == initialSelectedTab) ?? Tabs.FirstOrDefault(t => t.IsVisible); + + DetailControlModel.PropertyChanged += DetailControlModel_PropertyChanged; + + foreach (var tab in Tabs) + { + tab.PropertyChanged += IsVisible_PropertyChanged; + } + } + + public static PackageMetadataTab ConvertFromTabType(TitledPageViewModelBase vm) + { + if (vm is DetailControlModel) + { + return PackageMetadataTab.PackageDetails; + } + return PackageMetadataTab.Readme; + } + + public async Task SetCurrentFilterAsync(ItemFilter filter) + { + if (_readmeTabEnabled) + { + await ReadmePreviewViewModel.SetCurrentFilterAsync(filter); + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + _disposed = true; + DetailControlModel.PropertyChanged -= DetailControlModel_PropertyChanged; + + foreach (var tab in Tabs) + { + tab.PropertyChanged -= IsVisible_PropertyChanged; + } + } + + private void IsVisible_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TitledPageViewModelBase.IsVisible)) + { + if (SelectedTab == null || (SelectedTab == sender && !SelectedTab.IsVisible)) + { + SelectedTab = Tabs.FirstOrDefault(t => t.IsVisible); + } + } + } + + private void DetailControlModel_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + ThreadHelper.JoinableTaskFactory.Run(async () => + { + if (_readmeTabEnabled) + { + await ReadmePreviewViewModel.SetPackageMetadataAsync(DetailControlModel.PackageMetadata, CancellationToken.None); + } + }); + } + } +} diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ReadmePreviewViewModel.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ReadmePreviewViewModel.cs index 7fa7cd83a38..f5a7f7601b5 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ReadmePreviewViewModel.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ReadmePreviewViewModel.cs @@ -5,24 +5,30 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; using NuGet.VisualStudio.Internal.Contracts; namespace NuGet.PackageManagement.UI.ViewModels { - public sealed class ReadmePreviewViewModel : ViewModelBase + public sealed class ReadmePreviewViewModel : TitledPageViewModelBase { - private bool _canDetermineReadmeDefined; private bool _errorLoadingReadme; - private INuGetPackageFileService _packageFileService; + private INuGetPackageFileService _nugetPackageFileService; private string _rawReadme; + private DetailedPackageMetadata _packageMetadata; + private ItemFilter _currentItemFilter; - public ReadmePreviewViewModel(INuGetPackageFileService packageFileService) + public ReadmePreviewViewModel(INuGetPackageFileService packageFileService, ItemFilter itemFilter, bool isReadmeFeatureEnabled) { - _packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService)); + _nugetPackageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService)); + _currentItemFilter = itemFilter; + _nugetPackageFileService = packageFileService; _errorLoadingReadme = false; - _canDetermineReadmeDefined = true; _rawReadme = string.Empty; + _packageMetadata = null; + Title = Resources.Label_Readme_Tab; + IsVisible = isReadmeFeatureEnabled; } public bool ErrorLoadingReadme @@ -37,38 +43,71 @@ public string ReadmeMarkdown set => SetAndRaisePropertyChanged(ref _rawReadme, value); } - public bool CanDetermineReadmeDefined + public bool RenderLocalReadme { - get => _canDetermineReadmeDefined; - set => SetAndRaisePropertyChanged(ref _canDetermineReadmeDefined, value); + get => _currentItemFilter != ItemFilter.All; } - public async Task LoadReadmeAsync(string rawReadmeUrl, CancellationToken cancellationToken) + public async Task SetCurrentFilterAsync(ItemFilter filter) { - ReadmeMarkdown = string.Empty; - ErrorLoadingReadme = false; - CanDetermineReadmeDefined = false; + var oldRenderLocalReadme = RenderLocalReadme; + _currentItemFilter = filter; + if (RenderLocalReadme != oldRenderLocalReadme) + { + if (_packageMetadata != null) + { + await LoadReadmeAsync(CancellationToken.None); + } + } + } - if (string.IsNullOrWhiteSpace(rawReadmeUrl)) + public async Task SetPackageMetadataAsync(DetailedPackageMetadata packageMetadata, CancellationToken cancellationToken) + { + if (packageMetadata != null && (!string.Equals(packageMetadata.Id, _packageMetadata?.Id) || packageMetadata.Version != _packageMetadata?.Version)) { - return; + _packageMetadata = packageMetadata; + await LoadReadmeAsync(cancellationToken); } + } - await TaskScheduler.Default; + private async Task LoadReadmeAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(_packageMetadata.ReadmeFileUrl)) + { + ReadmeMarkdown = RenderLocalReadme && !string.IsNullOrWhiteSpace(_packageMetadata.PackagePath) ? Resources.Text_NoReadme : string.Empty; + IsVisible = !string.IsNullOrWhiteSpace(ReadmeMarkdown); + ErrorLoadingReadme = false; + return; + } - var readmeStream = await _packageFileService.GetReadmeAsync(new Uri(rawReadmeUrl), cancellationToken); - if (readmeStream is null) + var readmeUrl = new Uri(_packageMetadata.ReadmeFileUrl); + if (!RenderLocalReadme && readmeUrl.IsFile) { + ReadmeMarkdown = string.Empty; + IsVisible = false; + ErrorLoadingReadme = false; return; } - using StreamReader streamReader = new StreamReader(readmeStream); - var readme = await streamReader.ReadToEndAsync(); - if (!string.IsNullOrWhiteSpace(readme)) + var readme = Resources.Text_NoReadme; + await ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await TaskScheduler.Default; + using var readmeStream = await _nugetPackageFileService.GetReadmeAsync(readmeUrl, cancellationToken); + if (readmeStream is null) + { + return; + } + + using StreamReader streamReader = new StreamReader(readmeStream); + readme = await streamReader.ReadToEndAsync(); + }); + + if (!cancellationToken.IsCancellationRequested) { ReadmeMarkdown = readme; + IsVisible = !string.IsNullOrWhiteSpace(readme); ErrorLoadingReadme = false; - CanDetermineReadmeDefined = true; } } } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/TitledPageViewModelBase.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/TitledPageViewModelBase.cs new file mode 100644 index 00000000000..96ace72cd0d --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/TitledPageViewModelBase.cs @@ -0,0 +1,25 @@ +namespace NuGet.PackageManagement.UI.ViewModels +{ + public class TitledPageViewModelBase : ViewModelBase + { + private string _title; + public string Title + { + get => _title; + set + { + SetAndRaisePropertyChanged(ref _title, value); + } + } + + private bool _isVisible; + public bool IsVisible + { + get => _isVisible; + protected set + { + SetAndRaisePropertyChanged(ref _isVisible, value); + } + } + } +} diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/DetailControl.xaml b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/DetailControl.xaml index 9d7a48df2b0..a9cf78a1445 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/DetailControl.xaml +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/DetailControl.xaml @@ -42,15 +42,7 @@ - - - - + Height="*" /> @@ -158,39 +150,9 @@ BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" BorderThickness="0,1" /> - - - - - - - - - - - - - - @@ -216,12 +178,12 @@ Visibility="{Binding Path=OptionsBlockedMessage,Converter={StaticResource InverseNullToVisibilityConverter}}" /> - - + Margin="0,12,0,0"/> diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageDetailsTabControl.xaml b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageDetailsTabControl.xaml new file mode 100644 index 00000000000..7a05a3284a8 --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageDetailsTabControl.xaml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageDetailsTabControl.xaml.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageDetailsTabControl.xaml.cs new file mode 100644 index 00000000000..4bd3925898c --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageDetailsTabControl.xaml.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Windows.Controls; +using NuGet.PackageManagement.UI.ViewModels; + +namespace NuGet.PackageManagement.UI +{ + + /// + /// Interaction logic for PackageDetailsTabControl.xaml + /// + public partial class PackageDetailsTabControl : UserControl, IDisposable + { + public PackageDetailsTabViewModel PackageDetailsTabViewModel + { + get => DataContext as PackageDetailsTabViewModel; + } + + private bool _disposed = false; + + public PackageDetailsTabControl() + { + InitializeComponent(); + DataContext = new PackageDetailsTabViewModel(); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + if (disposing) + { + PackageDetailsTabViewModel.Dispose(); + } + _disposed = true; + } + } +} diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml index a418cbbd996..0218652ee8e 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml @@ -14,56 +14,56 @@ d:DesignHeight="523" d:DesignWidth="900" AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_PackageManager}"> - - - - - - - - - + + + + + + + + - - - - - - - - + + - + - - - - - - - - - + + + + + + + - - - - - + - - - - - - - - - - + + + + + + + + + + + - + Width="157*"/> + - - + - - - - - + Grid.Column="0" Grid.ColumnSpan="2"> + + + + + - - - - - - + + + - - - + - - - - + Grid.Column="2" Grid.ColumnSpan="2" Margin="4,0,0,0" /> + + + + diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs index 3f3b9507e9d..75dec45190c 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs @@ -67,6 +67,8 @@ public partial class PackageManagerControl : UserControl, IVsWindowSearch, IDisp private IServiceBroker _serviceBroker; private bool _disposed = false; private IPackageVulnerabilityService _packageVulnerabilityService; + private INuGetPackageFileService _nugetPackageFileService; + private PackageManagerInstalledTabData _installedTabTelemetryData; @@ -141,6 +143,12 @@ private async ValueTask InitializeAsync(PackageManagerModel model, INuGetUILogge _settingsKey = await GetSettingsKeyAsync(CancellationToken.None); UserSettings settings = LoadSettings(); InitializeFilterList(settings); + + _nugetPackageFileService?.Dispose(); + _nugetPackageFileService = await _serviceBroker.GetProxyAsync(NuGetServices.PackageFileService, CancellationToken.None); + + await _packageDetail._packageDetailsTabControl.PackageDetailsTabViewModel.InitializeAsync(_detailModel, _nugetPackageFileService, _topPanel.Filter, settings.SelectedPackageMetadataTab); + await InitPackageSourcesAsync(settings, CancellationToken.None); ApplySettings(settings, Settings); _initialized = true; @@ -678,7 +686,8 @@ public void SaveSettings() FileConflictAction = _detailModel.Options.SelectedFileConflictAction.Action, IncludePrerelease = _topPanel.CheckboxPrerelease.IsChecked == true, SelectedFilter = _topPanel.Filter, - OptionsExpanded = _packageDetail._optionsControl.IsExpanded + OptionsExpanded = _packageDetail._optionsControl.IsExpanded, + SelectedPackageMetadataTab = PackageDetailsTabViewModel.ConvertFromTabType(_packageDetail._packageDetailsTabControl.PackageDetailsTabViewModel.SelectedTab) }; _packageDetail._solutionView.SaveSettings(settings); @@ -1269,6 +1278,7 @@ private void Filter_SelectionChanged(object sender, FilterChangedEventArgs e) NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async () => { + await _packageDetail._packageDetailsTabControl.PackageDetailsTabViewModel.SetCurrentFilterAsync(_topPanel.Filter); await RunAndEmitRefreshAsync(async () => { await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -1836,6 +1846,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { + _nugetPackageFileService.Dispose(); CleanUp(); } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml index 33d888ae16f..4dcf7d7927a 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml @@ -180,18 +180,17 @@ - - - + + - + - - - + + + + IsChecked="True"> + + - + - - - + + + - - - + AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseFile'}"> + + + + x:Name="_licenseWarning" + Margin="0,0,4,0" + Visibility="{Binding Text, Converter={StaticResource NullToVisibilityConverter}}" + ToolTip="{Binding Text}" + AutomationProperties.Name="{Binding Text}" + Moniker="{x:Static catalog:KnownMonikers.StatusWarning}" /> + AutomationProperties.LabeledBy="{Binding ElementName=_packageLicenseOperator}" + AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseOperator_{0}'}" + Text="{Binding Text}" />