diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6fcfb7f81..ec9c0e35d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -65,6 +65,7 @@
+
diff --git a/NuGet.Config b/NuGet.Config
index ca0754e53..3ea1bced5 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -15,7 +15,7 @@
-
+
@@ -23,6 +23,7 @@
+
@@ -45,14 +46,14 @@
-
+
-
+
-
+
@@ -65,7 +66,7 @@
-
+
@@ -83,13 +84,13 @@
-
+
-
+
-
+
diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs
index 31038104f..d1ee59605 100644
--- a/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/GlobalSuppressions.cs
@@ -87,7 +87,7 @@ using System.Diagnostics.CodeAnalysis;
[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 124559b48..1ba6d67d7 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 @@ namespace NuGet.PackageManagement.UI
_options.SelectedChanged += DependencyBehavior_SelectedChanged;
_versions = new ItemsChangeObservableCollection();
+
+ Title = Resources.Label_PackageDetails;
+ IsVisible = true;
}
///
@@ -385,11 +388,9 @@ namespace NuGet.PackageManagement.UI
// 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 800efffb7..d685df31f 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 @@ namespace NuGet.PackageManagement.UI
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 @@ namespace NuGet.PackageManagement.UI
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 000000000..87447b0df
--- /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/NuGet.PackageManagement.UI.csproj b/src/NuGet.Clients/NuGet.PackageManagement.UI/NuGet.PackageManagement.UI.csproj
index 541da3a91..d77850135 100644
--- a/src/NuGet.Clients/NuGet.PackageManagement.UI/NuGet.PackageManagement.UI.csproj
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/NuGet.PackageManagement.UI.csproj
@@ -41,6 +41,9 @@
+
+
+
diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs
index 30a08ee71..b5b2880d9 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 @@ namespace NuGet.PackageManagement.UI {
}
}
+ ///
+ /// 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 @@ namespace NuGet.PackageManagement.UI {
}
}
+ ///
+ /// 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 @@ namespace NuGet.PackageManagement.UI {
}
}
+ ///
+ /// 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 @@ namespace NuGet.PackageManagement.UI {
}
}
+ ///
+ /// 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 879d025c6..4a40914b2 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 4eca01616..a2e99cf12 100644
--- a/src/NuGet.Clients/NuGet.PackageManagement.UI/UserSettings.cs
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/UserSettings.cs
@@ -23,6 +23,7 @@ namespace NuGet.PackageManagement.UI
SelectedFilter = ItemFilter.Installed;
DependencyBehavior = DependencyBehavior.Lowest;
FileConflictAction = FileConflictAction.PromptUser;
+ SelectedPackageMetadataTab = PackageMetadataTab.Readme;
OptionsExpanded = false;
}
@@ -40,6 +41,8 @@ namespace NuGet.PackageManagement.UI
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 000000000..a51f64f9e
--- /dev/null
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageDetailsTabViewModel.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.VisualStudio;
+using NuGet.VisualStudio.Internal.Contracts;
+using NuGet.VisualStudio.Telemetry;
+
+namespace NuGet.PackageManagement.UI.ViewModels
+{
+ public class PackageDetailsTabViewModel : ViewModelBase, IDisposable
+ {
+ private bool _disposed = false;
+ private bool _readmeTabEnabled;
+
+ public ReadmePreviewViewModel ReadmePreviewViewModel { get; private set; }
+
+ private DetailControlModel DetailControlModel { get; 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;
+
+ if (_readmeTabEnabled)
+ {
+ 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 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 (_readmeTabEnabled && sender is ReadmePreviewViewModel)
+ {
+ if (ReadmePreviewViewModel.IsVisible)
+ {
+ Tabs.Insert(0, ReadmePreviewViewModel);
+ }
+ else
+ {
+ Tabs.Remove(ReadmePreviewViewModel);
+ }
+ }
+
+ if (SelectedTab == null || (SelectedTab == sender && !SelectedTab.IsVisible))
+ {
+ SelectedTab = Tabs.FirstOrDefault(t => t.IsVisible);
+ }
+ }
+ }
+
+ private void DetailControlModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async () =>
+ {
+ if (_readmeTabEnabled)
+ {
+ await ReadmePreviewViewModel.SetPackageMetadataAsync(DetailControlModel.PackageMetadata, CancellationToken.None);
+ }
+ }).PostOnFailure(nameof(PackageDetailsTabViewModel), nameof(DetailControlModel_PropertyChanged));
+ }
+ }
+}
diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ReadmePreviewViewModel.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ReadmePreviewViewModel.cs
new file mode 100644
index 000000000..ff897fe3b
--- /dev/null
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ReadmePreviewViewModel.cs
@@ -0,0 +1,114 @@
+// 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.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 : TitledPageViewModelBase
+ {
+ private bool _errorLoadingReadme;
+ private INuGetPackageFileService _nugetPackageFileService;
+ private string _rawReadme;
+ private DetailedPackageMetadata _packageMetadata;
+ private bool _canRenderLocalReadme;
+
+ public ReadmePreviewViewModel(INuGetPackageFileService packageFileService, ItemFilter itemFilter, bool isReadmeFeatureEnabled)
+ {
+ _nugetPackageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService));
+ _canRenderLocalReadme = CanRenderLocalReadme(itemFilter);
+ _nugetPackageFileService = packageFileService;
+ _errorLoadingReadme = false;
+ _rawReadme = string.Empty;
+ _packageMetadata = null;
+ Title = Resources.Label_Readme_Tab;
+ IsVisible = isReadmeFeatureEnabled;
+ }
+
+ public bool ErrorLoadingReadme
+ {
+ get => _errorLoadingReadme;
+ set => SetAndRaisePropertyChanged(ref _errorLoadingReadme, value);
+ }
+
+ public string ReadmeMarkdown
+ {
+ get => _rawReadme;
+ set => SetAndRaisePropertyChanged(ref _rawReadme, value);
+ }
+
+ public async Task ItemFilterChangedAsync(ItemFilter filter)
+ {
+ var oldRenderLocalReadme = _canRenderLocalReadme;
+ _canRenderLocalReadme = CanRenderLocalReadme(filter);
+ if (_canRenderLocalReadme != oldRenderLocalReadme)
+ {
+ if (_packageMetadata != null)
+ {
+ await LoadReadmeAsync(CancellationToken.None);
+ }
+ }
+ }
+
+ public async Task SetPackageMetadataAsync(DetailedPackageMetadata packageMetadata, CancellationToken cancellationToken)
+ {
+ if (packageMetadata != null && (!string.Equals(packageMetadata.Id, _packageMetadata?.Id) || packageMetadata.Version != _packageMetadata?.Version))
+ {
+ _packageMetadata = packageMetadata;
+ await LoadReadmeAsync(cancellationToken);
+ }
+ }
+
+ private static bool CanRenderLocalReadme(ItemFilter filter)
+ {
+ return filter != ItemFilter.All;
+ }
+
+ private async Task LoadReadmeAsync(CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(_packageMetadata.ReadmeFileUrl))
+ {
+ ReadmeMarkdown = _canRenderLocalReadme && !string.IsNullOrWhiteSpace(_packageMetadata.PackagePath) ? Resources.Text_NoReadme : string.Empty;
+ IsVisible = !string.IsNullOrWhiteSpace(ReadmeMarkdown);
+ ErrorLoadingReadme = false;
+ return;
+ }
+
+ var readmeUrl = new Uri(_packageMetadata.ReadmeFileUrl);
+ if (!_canRenderLocalReadme && readmeUrl.IsFile)
+ {
+ ReadmeMarkdown = string.Empty;
+ IsVisible = false;
+ ErrorLoadingReadme = false;
+ return;
+ }
+
+ 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;
+ }
+ }
+ }
+}
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 000000000..313d42c26
--- /dev/null
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/TitledPageViewModelBase.cs
@@ -0,0 +1,24 @@
+namespace NuGet.PackageManagement.UI.ViewModels
+{
+ public class TitledPageViewModelBase : ViewModelBase
+ {
+ private string _title;
+ public string Title
+ {
+ get => _title;
+ protected set => SetAndRaisePropertyChanged(ref _title, value);
+ }
+
+ private bool _isVisible;
+ public bool IsVisible
+ {
+ get => _isVisible;
+ protected set => SetAndRaisePropertyChanged(ref _isVisible, value);
+ }
+
+ public override string ToString()
+ {
+ return Title;
+ }
+ }
+}
diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ViewModelBase.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ViewModelBase.cs
index 075579e13..cbf50daab 100644
--- a/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ViewModelBase.cs
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/ViewModelBase.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
@@ -11,5 +12,17 @@ namespace NuGet.PackageManagement.UI.ViewModels
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+ public bool SetAndRaisePropertyChanged(ref T field, T newValue, [CallerMemberName] string propertyName = "")
+ {
+ if (!EqualityComparer.Default.Equals(field, newValue))
+ {
+ field = newValue;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/DetailControl.xaml b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/DetailControl.xaml
index 9d7a48df2..30ada73fe 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,11 @@
Visibility="{Binding Path=OptionsBlockedMessage,Converter={StaticResource InverseNullToVisibilityConverter}}" />
-
-
+
+
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 000000000..fd0e47d1c
--- /dev/null
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageDetailsTabControl.xaml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..4bd392589
--- /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 a418cbbd9..a6a39bceb 100644
--- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml
@@ -83,8 +83,7 @@
+ Width="67*">
@@ -93,17 +92,17 @@
+
+ Width="157*"/>
+ Grid.Column="0" Grid.ColumnSpan="2">
@@ -145,11 +144,9 @@
+ 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 3f3b9507e..5d71167d5 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 @@ namespace NuGet.PackageManagement.UI
private IServiceBroker _serviceBroker;
private bool _disposed = false;
private IPackageVulnerabilityService _packageVulnerabilityService;
+ private INuGetPackageFileService _nugetPackageFileService;
+
private PackageManagerInstalledTabData _installedTabTelemetryData;
@@ -141,6 +143,12 @@ namespace NuGet.PackageManagement.UI
_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 @@ namespace NuGet.PackageManagement.UI
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 @@ namespace NuGet.PackageManagement.UI
NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async () =>
{
+ await _packageDetail._packageDetailsTabControl.PackageDetailsTabViewModel.ReadmePreviewViewModel.ItemFilterChangedAsync(ActiveFilter);
await RunAndEmitRefreshAsync(async () =>
{
await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
@@ -1836,6 +1846,7 @@ namespace NuGet.PackageManagement.UI
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 33d888ae1..edeb4d1e4 100644
--- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml
+++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml
@@ -180,18 +180,16 @@
-
-
-
+
+
-
-
-
-
+
+
+
+ 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}" />