mirror of
https://github.com/aphrody-labs/NuGet.Client.git
synced 2026-10-17 12:45:02 +00:00
[Feature] Render package README from local storage in the PM UI (#6110)
This commit is contained in:
1 parent
fad3153291
commit
dddfdd8617
48 files changed
+1455
-412
No files matched your search
@@ -65,6 +65,7 @@
|
||||
<PackageVersion Include="Microsoft.Test.Apex.VisualStudio" Version="17.11.35005.70" />
|
||||
<PackageVersion Include="Microsoft.TestPlatform.Portable" Version="17.1.0" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.LanguageServices" Version="4.3.1" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Markdown.Platform" Version="17.11.119-preview" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.ProjectSystem" Version="17.4.221-pre" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.ProjectSystem.Managed" Version="17.2.0-beta1-20502-01" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.ProjectSystem.Managed.VS" Version="17.2.0-beta1-20502-01" />
|
||||
|
||||
+9
-8
@@ -15,7 +15,7 @@
|
||||
</auditSources>
|
||||
<packageSourceMapping>
|
||||
<clear />
|
||||
<packageSource key = "dotnet-public">
|
||||
<packageSource key="dotnet-public">
|
||||
<package pattern="Azure.Core " />
|
||||
<package pattern="ben.demystifier" />
|
||||
<package pattern="castle.core" />
|
||||
@@ -23,6 +23,7 @@
|
||||
<package pattern="Humanizer.Core" />
|
||||
<package pattern="ilmerge" />
|
||||
<package pattern="lucene.net" />
|
||||
<package pattern="Markdig.Signed" />
|
||||
<package pattern="messagepack" />
|
||||
<package pattern="messagepack.annotations" />
|
||||
<package pattern="messagepackanalyzer" />
|
||||
@@ -45,14 +46,14 @@
|
||||
<package pattern="xunit.*" />
|
||||
<package pattern="xunitxml.testlogger" />
|
||||
</packageSource>
|
||||
<packageSource key = "dotnet-eng">
|
||||
<packageSource key="dotnet-eng">
|
||||
<package pattern="Microsoft.DotNet.*" />
|
||||
</packageSource>
|
||||
<packageSource key = "nuget-build">
|
||||
<packageSource key="nuget-build">
|
||||
<package pattern="nuget.client.endtoend.testdata" />
|
||||
<package pattern="nugetvalidator" />
|
||||
</packageSource>
|
||||
<packageSource key = "vside">
|
||||
<packageSource key="vside">
|
||||
<package pattern="envdte" />
|
||||
<package pattern="envdte100" />
|
||||
<package pattern="envdte80" />
|
||||
@@ -65,7 +66,7 @@
|
||||
<package pattern="Microsoft.ServiceHub.Framework" />
|
||||
<package pattern="Microsoft.TeamFoundationServer.ExtendedClient" />
|
||||
<package pattern="microsoft.test.apex.visualstudio" />
|
||||
<package pattern="microsoft.visualstudio.*" />
|
||||
<package pattern="Microsoft.VisualStudio.*" />
|
||||
<package pattern="Microsoft.VisualStudioEng.MicroBuild.Core" />
|
||||
<package pattern="Microsoft.VSSDK.BuildTools" />
|
||||
<package pattern="stdole" />
|
||||
@@ -83,13 +84,13 @@
|
||||
<package pattern="vslangproj90" />
|
||||
<package pattern="vswebsite.interop" />
|
||||
</packageSource>
|
||||
<packageSource key = "myget-legacy@Local">
|
||||
<packageSource key="myget-legacy@Local">
|
||||
<package pattern="Microsoft.Extensions.CommandLineUtils.Sources" />
|
||||
</packageSource>
|
||||
<packageSource key = "VS">
|
||||
<packageSource key="VS">
|
||||
<package pattern="Microsoft.DevDiv.Validation.TestPlatform.Settings.Tasks" />
|
||||
</packageSource>
|
||||
<packageSource key = "dotnet-libraries">
|
||||
<packageSource key="dotnet-libraries">
|
||||
<package pattern="System.CommandLine" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
|
||||
@@ -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 = "<Pending>", 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 = "<Pending>", 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 = "<Pending>", 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 = "<Pending>", 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 = "<Pending>", 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 = "<Pending>", 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 = "<Pending>", Scope = "type", Target = "~T:NuGet.PackageManagement.UI.PRMigratorBar")]
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
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<DisplayVersion>();
|
||||
|
||||
Title = Resources.Label_PackageDetails;
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Framework" />
|
||||
<PackageReference Include="Microsoft.NET.StringTools" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Markdown.Platform" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Sdk" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -807,6 +807,15 @@ namespace NuGet.PackageManagement.UI {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An error occurred while trying to load the README..
|
||||
/// </summary>
|
||||
public static string Error_UnableToLoadReadme {
|
||||
get {
|
||||
return ResourceManager.GetString("Error_UnableToLoadReadme", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Operation failed.
|
||||
/// </summary>
|
||||
@@ -1230,6 +1239,15 @@ namespace NuGet.PackageManagement.UI {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Package Details.
|
||||
/// </summary>
|
||||
public static string Label_PackageDetails {
|
||||
get {
|
||||
return ResourceManager.GetString("Label_PackageDetails", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to NuGet Package Manager: {0}.
|
||||
/// </summary>
|
||||
@@ -1284,6 +1302,15 @@ namespace NuGet.PackageManagement.UI {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to README.
|
||||
/// </summary>
|
||||
public static string Label_Readme_Tab {
|
||||
get {
|
||||
return ResourceManager.GetString("Label_Readme_Tab", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Report Abuse:.
|
||||
/// </summary>
|
||||
@@ -2051,6 +2078,21 @@ namespace NuGet.PackageManagement.UI {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public static string Text_NoReadme {
|
||||
get {
|
||||
return ResourceManager.GetString("Text_NoReadme", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Not available in this source.
|
||||
/// </summary>
|
||||
|
||||
@@ -1076,4 +1076,23 @@ Please see https://aka.ms/troubleshoot_nuget_cache for more help.</value>
|
||||
<data name="ColumnHeader_PackageLevel" xml:space="preserve">
|
||||
<value>Package Level</value>
|
||||
</data>
|
||||
<data name="Label_PackageDetails" xml:space="preserve">
|
||||
<value>Package Details</value>
|
||||
</data>
|
||||
<data name="Error_UnableToLoadReadme" xml:space="preserve">
|
||||
<value>An error occurred while trying to load the README.</value>
|
||||
</data>
|
||||
<data name="Label_Readme_Tab" xml:space="preserve">
|
||||
<value>README</value>
|
||||
</data>
|
||||
<data name="Text_NoReadme" xml:space="preserve">
|
||||
<value>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)</value>
|
||||
<comment>{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</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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; }
|
||||
|
||||
+120
@@ -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<TitledPageViewModelBase> Tabs { get; private set; }
|
||||
|
||||
private TitledPageViewModelBase _selectedTab;
|
||||
public TitledPageViewModelBase SelectedTab
|
||||
{
|
||||
get => _selectedTab;
|
||||
set => SetAndRaisePropertyChanged(ref _selectedTab, value);
|
||||
}
|
||||
|
||||
public PackageDetailsTabViewModel()
|
||||
{
|
||||
_readmeTabEnabled = true;
|
||||
Tabs = new ObservableCollection<TitledPageViewModelBase>();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(DetailControlModel detailControlModel, INuGetPackageFileService nugetPackageFileService, ItemFilter currentFilter, PackageMetadataTab initialSelectedTab)
|
||||
{
|
||||
var nuGetFeatureFlagService = await ServiceLocator.GetComponentModelServiceAsync<INuGetFeatureFlagService>();
|
||||
_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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T>(ref T field, T newValue, [CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(field, newValue))
|
||||
{
|
||||
field = newValue;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,15 +42,7 @@
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Icon and Id -->
|
||||
@@ -158,39 +150,9 @@
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
BorderThickness="0,1" />
|
||||
|
||||
<!-- vulnerabilities info -->
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Margin="0,11,0,0"
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
Visibility="{Binding Path=IsPackageVulnerable,Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<Grid>
|
||||
<nuget:VulnerabilitiesControl
|
||||
x:Name="_vulnerabilitiesControl"
|
||||
DataContext="{Binding}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_PackageVulnerabilities}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- deprecation info -->
|
||||
<Border
|
||||
Grid.Row="4"
|
||||
Margin="0,11,0,0"
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
Visibility="{Binding Path=IsPackageDeprecated,Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<Grid>
|
||||
<nuget:DeprecationControl
|
||||
x:Name="_deprecationControl"
|
||||
DataContext="{Binding}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_PackageDeprecation}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- options -->
|
||||
<Border
|
||||
Grid.Row="5"
|
||||
Grid.Row="3"
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
BorderThickness="0,1,0,1">
|
||||
<Grid>
|
||||
@@ -216,12 +178,11 @@
|
||||
Visibility="{Binding Path=OptionsBlockedMessage,Converter={StaticResource InverseNullToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<nuget:PackageMetadataControl
|
||||
Grid.Row="7"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_PackageMetadata}"
|
||||
Margin="0,12,0,0"
|
||||
DataContext="{Binding PackageMetadata}" />
|
||||
|
||||
<nuget:PackageDetailsTabControl
|
||||
x:Name="_packageDetailsTabControl"
|
||||
Grid.Row="4"
|
||||
Margin="0,12,0,0"/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,153 @@
|
||||
<UserControl
|
||||
x:Class="NuGet.PackageManagement.UI.PackageDetailsTabControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:nuget="clr-namespace:NuGet.PackageManagement.UI"
|
||||
xmlns:vm="clr-namespace:NuGet.PackageManagement.UI.ViewModels"
|
||||
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
|
||||
xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
|
||||
xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
|
||||
Background="{DynamicResource {x:Static nuget:Brushes.DetailPaneBackground}}"
|
||||
Foreground="{DynamicResource {x:Static nuget:Brushes.UIText}}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="300">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<nuget:SharedResources />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<nuget:AccessibleConverter x:Key="AccNameWorkaround" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<TabControl
|
||||
Grid.Row="0"
|
||||
ItemsSource="{Binding Tabs}"
|
||||
SelectedItem="{Binding SelectedTab}"
|
||||
x:Name="tabsPackageDetails">
|
||||
<TabControl.Resources>
|
||||
<Style TargetType="TabItem">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static nuget:Brushes.HeaderBackground}}" />
|
||||
<Setter Property="FontSize" Value="{Binding FontSize, RelativeSource={RelativeSource AncestorType=UserControl},Converter={StaticResource Font122PercentSizeConverter}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static nuget:Brushes.UIText}}" />
|
||||
<Setter Property="Padding" Value="20,0,20,0" />
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource ControlsFocusVisualStyle}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border x:Name="mainBorder" Background="{TemplateBinding Background}" BorderThickness="0" Margin="0">
|
||||
</Border>
|
||||
<ContentPresenter Grid.Row="0" x:Name="contentPresenter" ContentSource="Header" Focusable="False" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
|
||||
<!--the line under the text to indicate that this label is selected-->
|
||||
<Rectangle
|
||||
x:Name="_underline"
|
||||
Grid.Row="1"
|
||||
Height="3"
|
||||
Visibility="Collapsed"
|
||||
Width="{Binding ActualWidth, ElementName=contentPresenter}"
|
||||
HorizontalAlignment="Center"
|
||||
Fill="{DynamicResource {x:Static nuget:Brushes.TabSelectedIndicatorBrushKey}}"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Visibility" TargetName="_underline" Value="Visible" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static nuget:Brushes.TabSelectedTextBrushKey}}" />
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsKeyboardFocused, RelativeSource={RelativeSource Self}}" Value="true" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static nuget:Brushes.TabTextFocusedBrushKey}}" />
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static nuget:Brushes.TabTextHoverBrushKey}}" />
|
||||
</MultiDataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TabControl}">
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Template" >
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabControl}">
|
||||
<Grid x:Name="templateRoot" ClipToBounds="True" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="ColumnDefinition0"/>
|
||||
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
|
||||
<RowDefinition x:Name="RowDefinition1" Height="Auto"/>
|
||||
<RowDefinition x:Name="RowDefinition2"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TabPanel x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="true" Margin="0,0,0,-1" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1"/>
|
||||
<Rectangle
|
||||
x:Name="gridBorder"
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
Panel.ZIndex="-1"
|
||||
Margin="0,1,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"/>
|
||||
<ContentPresenter
|
||||
Grid.Row="2"
|
||||
ClipToBounds="True"
|
||||
x:Name="PART_SelectedContentHost"
|
||||
ContentSource="SelectedContent"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
VerticalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</TabControl.Resources>
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsVisible}" Value="False">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<!-- Customize the content of the tabs here -->
|
||||
<ContentControl Content="{Binding}" Focusable="False">
|
||||
<ContentControl.Resources>
|
||||
<DataTemplate DataType="{x:Type nuget:DetailControlModel}">
|
||||
<nuget:PackageMetadataControl
|
||||
Focusable="True"
|
||||
Margin="0,12,0,0"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:ReadmePreviewViewModel}">
|
||||
<nuget:PackageReadmeControl
|
||||
x:Name="_packageReadmeControl"/>
|
||||
</DataTemplate>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
</UserControl>
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for PackageDetailsTabControl.xaml
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,7 @@
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
x:Name="_leftSideGridColumn"
|
||||
Width="65*"
|
||||
MinWidth="300">
|
||||
Width="67*">
|
||||
<ColumnDefinition.MaxWidth>
|
||||
<MultiBinding Converter="{StaticResource SubstractionConverter}">
|
||||
<Binding Path="ActualWidth" ElementName="_root" />
|
||||
@@ -93,17 +92,17 @@
|
||||
</MultiBinding>
|
||||
</ColumnDefinition.MaxWidth>
|
||||
</ColumnDefinition>
|
||||
<ColumnDefinition Width="224*"/>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition
|
||||
x:Name="_rightSideGridColumn"
|
||||
Width="35*"
|
||||
MinWidth="382" />
|
||||
Width="157*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- left side -->
|
||||
<Grid
|
||||
Name="_leftSide"
|
||||
Grid.Column="0">
|
||||
Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -145,11 +144,9 @@
|
||||
|
||||
<GridSplitter
|
||||
x:Name="_gridSplitter"
|
||||
Grid.Column="1"
|
||||
Grid.Column="2"
|
||||
Width="4"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_ThumbName}"
|
||||
AutomationProperties.HelpText="{x:Static nuget:Resources.Accessibility_ThumbHelp}"
|
||||
Background="{DynamicResource {x:Static nuget:Brushes.SplitterBackgroundKey}}"
|
||||
@@ -161,7 +158,8 @@
|
||||
x:Name="_packageDetail"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_PackagesDetails}"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2" />
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="4,0,0,0" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
|
||||
@@ -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<INuGetPackageFileService>(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();
|
||||
}
|
||||
|
||||
|
||||
@@ -180,18 +180,16 @@
|
||||
<!-- search control and include prerelease checkbox -->
|
||||
<Grid Grid.Row="2" Margin="20,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="269"/>
|
||||
<ColumnDefinition Width="0.01*" MinWidth="5" />
|
||||
<ColumnDefinition Width="*" MinWidth="192" />
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition MinWidth="192" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid
|
||||
Grid.Column="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MaxWidth="320" MinWidth="118"/>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition MaxWidth="320" MinWidth="118"/>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition MaxWidth="125" />
|
||||
<ColumnDefinition MaxWidth="150"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- container of the search control -->
|
||||
<Border
|
||||
@@ -226,12 +224,13 @@
|
||||
AutomationProperties.AutomationId="CheckBox_Prerelease"
|
||||
Margin="3,0"
|
||||
VerticalAlignment="Center"
|
||||
Content="{x:Static nuget:Resources.Checkbox_IncludePrerelease}"
|
||||
Foreground="{DynamicResource {x:Static nuget:Brushes.UIText}}"
|
||||
VerticalContentAlignment="Center"
|
||||
Checked="_checkboxPrerelease_Checked"
|
||||
Unchecked="_checkboxPrerelease_Unchecked"
|
||||
IsChecked="True" />
|
||||
IsChecked="True">
|
||||
<TextBlock TextTrimming="CharacterEllipsis" Text="{x:Static nuget:Resources.Checkbox_IncludePrerelease}" />
|
||||
</CheckBox>
|
||||
|
||||
<!-- show vulnerabilities checkbox -->
|
||||
<CheckBox
|
||||
@@ -246,7 +245,7 @@
|
||||
Unchecked="_checkboxVulnerabilities_Unchecked"
|
||||
Visibility="Hidden"
|
||||
IsChecked="False">
|
||||
<TextBlock Text="{x:Static nuget:Resources.Checkbox_Show_Vulnerable_Only}" />
|
||||
<TextBlock TextTrimming="CharacterEllipsis" Text="{x:Static nuget:Resources.Checkbox_Show_Vulnerable_Only}" />
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
<Grid
|
||||
|
||||
@@ -22,46 +22,46 @@
|
||||
<nuget:AccessibleConverter x:Key="AccNameWorkaround" />
|
||||
<DataTemplate DataType="{x:Type nuget:LicenseText}">
|
||||
<TextBlock>
|
||||
<Hyperlink
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.License}"
|
||||
ToolTip="{Binding Link}"
|
||||
NavigateUri="{Binding Link}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Label_License}">
|
||||
<Run Text="{Binding Text}"></Run>
|
||||
</Hyperlink>
|
||||
<Hyperlink
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.License}"
|
||||
ToolTip="{Binding Link}"
|
||||
NavigateUri="{Binding Link}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Label_License}">
|
||||
<Run Text="{Binding Text}"></Run>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type nuget:LicenseFileText}">
|
||||
<TextBlock
|
||||
AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseFile'}">
|
||||
<Hyperlink
|
||||
AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseFileLink'}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_License}"
|
||||
Style="{StaticResource WindowHyperlinkStyle}"
|
||||
Click="ViewLicense_Click">
|
||||
<Run Text="{Binding Text}"/>
|
||||
</Hyperlink>
|
||||
AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseFile'}">
|
||||
<Hyperlink
|
||||
AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseFileLink'}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_License}"
|
||||
Style="{StaticResource WindowHyperlinkStyle}"
|
||||
Click="ViewLicense_Click">
|
||||
<Run Text="{Binding Text}"/>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type nuget:WarningText}">
|
||||
<imaging:CrispImage
|
||||
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}" />
|
||||
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}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type nuget:FreeText}">
|
||||
<TextBlock
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_packageLicenseOperator}"
|
||||
AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseOperator_{0}'}"
|
||||
Text="{Binding Text}" />
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_packageLicenseOperator}"
|
||||
AutomationProperties.AutomationId="{Binding Id, Mode=OneWay, StringFormat='LicenseOperator_{0}'}"
|
||||
Text="{Binding Text}" />
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="TreeViewItemHeaderStyle" TargetType="TreeViewItem">
|
||||
@@ -78,304 +78,338 @@
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!-- vulnerabilities info -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
Visibility="{Binding Path=IsPackageVulnerable,Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid>
|
||||
<nuget:VulnerabilitiesControl
|
||||
x:Name="_vulnerabilitiesControl"
|
||||
DataContext="{Binding}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_PackageVulnerabilities}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- descriptions -->
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
x:Name="_descriptionLabel"
|
||||
Text="{x:Static nuget:Resources.Label_Description}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
<!-- deprecation info -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
x:Name="_description"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_descriptionLabel}"
|
||||
Margin="0,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Path=Description}" />
|
||||
|
||||
<!-- metadata -->
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
Visibility="{Binding Path=IsPackageDeprecated,Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid>
|
||||
<nuget:DeprecationControl
|
||||
x:Name="_deprecationControl"
|
||||
DataContext="{Binding}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Accessibility_PackageDeprecation}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
x:Name="_metadataGrid"
|
||||
Margin="0,8">
|
||||
Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
<RowDefinition
|
||||
Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- version -->
|
||||
<!-- descriptions -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=Version,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,0,0"
|
||||
FontWeight="Bold"
|
||||
x:Name="_versionLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Version}" />
|
||||
x:Name="_descriptionLabel"
|
||||
Text="{x:Static nuget:Resources.Label_Description}"
|
||||
FontWeight="Bold" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Visibility="{Binding Path=Version,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_versionLabel}"
|
||||
Text="{Binding Path=Version,Converter={StaticResource VersionToStringConverter},ConverterParameter=F}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Owner(s) -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=KnownOwnerViewModels,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
x:Name="_description"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_descriptionLabel}"
|
||||
Margin="0,8,0,0"
|
||||
FontWeight="Bold"
|
||||
x:Name="_ownersLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Owners}" />
|
||||
<ItemsControl
|
||||
x:Name="_panelOwners"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
ItemsSource="{Binding KnownOwnerViewModels}"
|
||||
Visibility="{Binding Path=KnownOwnerViewModels,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_ownersLabel}"
|
||||
Focusable="False"
|
||||
Margin="8,8,0,0"
|
||||
AlternationCount="{Binding RelativeSource={RelativeSource Self}, Path=Items.Count}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Link, Mode=OneWay}"
|
||||
ToolTip="{Binding Link, Mode=OneWay}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_Owner}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.OwnerProfileDetailsPane}">
|
||||
<Run Text="{Binding Name, Mode=OneTime}" />
|
||||
</Hyperlink><TextBlock Margin="0,0,2,0" Text=",">
|
||||
<TextBlock.Visibility>
|
||||
<MultiBinding Converter="{StaticResource LastItemToVisibilityConverter}">
|
||||
<Binding RelativeSource="{RelativeSource Mode=TemplatedParent}" Path="(ItemsControl.AlternationIndex)" />
|
||||
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" Path="Items.Count" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Visibility>
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Path=PackageMetadata.Description}" />
|
||||
|
||||
<!-- metadata -->
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
x:Name="_metadataGrid"
|
||||
Margin="0,8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- version -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.Version,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,0,0"
|
||||
FontWeight="Bold"
|
||||
x:Name="_versionLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Version}" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Visibility="{Binding Path=PackageMetadata.Version,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_versionLabel}"
|
||||
Text="{Binding Path=PackageMetadata.Version,Converter={StaticResource VersionToStringConverter},ConverterParameter=F}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Owner(s) -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.KnownOwnerViewModels,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,0,0"
|
||||
FontWeight="Bold"
|
||||
x:Name="_ownersLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Owners}" />
|
||||
<ItemsControl
|
||||
x:Name="_panelOwners"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
ItemsSource="{Binding PackageMetadata.KnownOwnerViewModels}"
|
||||
Visibility="{Binding Path=PackageMetadata.KnownOwnerViewModels,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_ownersLabel}"
|
||||
Focusable="False"
|
||||
Margin="8,8,0,0"
|
||||
AlternationCount="{Binding RelativeSource={RelativeSource Self}, Path=Items.Count}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Link, Mode=OneWay}"
|
||||
ToolTip="{Binding Link, Mode=OneWay}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_Owner}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.OwnerProfileDetailsPane}">
|
||||
<Run Text="{Binding Name, Mode=OneTime}" />
|
||||
</Hyperlink><TextBlock Margin="0,0,2,0" Text=",">
|
||||
<TextBlock.Visibility>
|
||||
<MultiBinding Converter="{StaticResource LastItemToVisibilityConverter}">
|
||||
<Binding RelativeSource="{RelativeSource Mode=TemplatedParent}" Path="(ItemsControl.AlternationIndex)" />
|
||||
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" Path="Items.Count" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Visibility>
|
||||
</TextBlock>
|
||||
</TextBlock>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Author(s) -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=Authors,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,0,0"
|
||||
FontWeight="Bold"
|
||||
x:Name="_authorsLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Authors}" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=Authors,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_authorsLabel}"
|
||||
Text="{Binding Path=Authors}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1" />
|
||||
<!-- Author(s) -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.Authors,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,8,0,0"
|
||||
FontWeight="Bold"
|
||||
x:Name="_authorsLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Authors}" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=PackageMetadata.Authors,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_authorsLabel}"
|
||||
Text="{Binding Path=PackageMetadata.Authors}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1" />
|
||||
|
||||
<!-- License -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=LicenseUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_licenseLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_License}"/>
|
||||
<!-- License -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.LicenseUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_licenseLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_License}"/>
|
||||
|
||||
<ItemsControl Grid.Row="3" Grid.Column="1" ItemsSource="{Binding LicenseLinks}" IsTabStop="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Margin="8,8,0,0" Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
<ItemsControl Grid.Row="3" Grid.Column="1" ItemsSource="{Binding PackageMetadata.LicenseLinks}" IsTabStop="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Margin="8,8,0,0" Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Readme -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=ReadmeUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_readmeLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Readme}"/>
|
||||
<!-- Readme -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.ReadmeUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_readmeLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_Readme}"/>
|
||||
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=ReadmeUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="8,8,0,0"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Path=ReadmeUrl}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.Readme}"
|
||||
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=NavigateUri}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_Readme}">
|
||||
<Run Text="{x:Static nuget:Resources.Text_ViewReadme}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.ReadmeUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="8,8,0,0"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Path=PackageMetadata.ReadmeUrl}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.Readme}"
|
||||
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=NavigateUri}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_Readme}">
|
||||
<Run Text="{x:Static nuget:Resources.Text_ViewReadme}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<!-- downloads -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=DownloadCount,Converter={StaticResource GreaterThanThresholdToVisibilityConverter},ConverterParameter=-1}"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_downloadsLabel"
|
||||
Text="{x:Static nuget:Resources.Label_Downloads}" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=DownloadCount,Converter={StaticResource GreaterThanThresholdToVisibilityConverter},ConverterParameter=-1}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_downloadsLabel}"
|
||||
Text="{Binding Path=DownloadCount,StringFormat={}{0:N0}}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1" />
|
||||
<!-- downloads -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.DownloadCount,Converter={StaticResource GreaterThanThresholdToVisibilityConverter},ConverterParameter=-1}"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_downloadsLabel"
|
||||
Text="{x:Static nuget:Resources.Label_Downloads}" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=PackageMetadata.DownloadCount,Converter={StaticResource GreaterThanThresholdToVisibilityConverter},ConverterParameter=-1}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_downloadsLabel}"
|
||||
Text="{Binding Path=PackageMetadata.DownloadCount,StringFormat={}{0:N0}}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1" />
|
||||
|
||||
<!--Date Published -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=Published,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_datePublishedLabel"
|
||||
Text="{x:Static nuget:Resources.Label_DatePublished}" />
|
||||
<TextBox
|
||||
Name="datePublished"
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=Published,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Text="{Binding Path=Published,ConverterCulture={x:Static glob:CultureInfo.CurrentCulture},StringFormat={}{0:D} ({0:d})}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_datePublishedLabel}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1" />
|
||||
<!--Date Published -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.Published,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_datePublishedLabel"
|
||||
Text="{x:Static nuget:Resources.Label_DatePublished}" />
|
||||
<TextBox
|
||||
Name="datePublished"
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=PackageMetadata.Published,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Text="{Binding Path=PackageMetadata.Published,ConverterCulture={x:Static glob:CultureInfo.CurrentCulture},StringFormat={}{0:D} ({0:d})}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_datePublishedLabel}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1" />
|
||||
|
||||
<!-- Project Url -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=ProjectUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_projectUrlLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_ProjectUrl}" />
|
||||
<!-- Project Url -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.ProjectUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_projectUrlLabel"
|
||||
AutomationProperties.Name="{Binding Text}"
|
||||
Text="{x:Static nuget:Resources.Label_ProjectUrl}" />
|
||||
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=ProjectUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="8,8,0,0"
|
||||
Grid.Row="7"
|
||||
Grid.Column="1">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Path=ProjectUrl}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.ProjectUri}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_ProjectUrl}">
|
||||
<Run Text="{Binding Path=ProjectUrl}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.ProjectUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="8,8,0,0"
|
||||
Grid.Row="7"
|
||||
Grid.Column="1">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Path=PackageMetadata.ProjectUrl}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.ProjectUri}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_ProjectUrl}">
|
||||
<Run Text="{Binding Path=PackageMetadata.ProjectUrl}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Report abuse Url -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=ReportAbuseUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="8"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_reportAbuseLabel"
|
||||
Text="{x:Static nuget:Resources.Label_ReportAbuse}" />
|
||||
<!-- Report abuse Url -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.ReportAbuseUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="8"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_reportAbuseLabel"
|
||||
Text="{x:Static nuget:Resources.Label_ReportAbuse}" />
|
||||
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=ReportAbuseUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="8,8,0,0"
|
||||
Grid.Row="8"
|
||||
Grid.Column="1">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Path=ReportAbuseUrl}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.ReportAbuse}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_ReportAbuse}">
|
||||
<Run Text="{Binding Path=ReportAbuseUrl}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.ReportAbuseUrl,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="8,8,0,0"
|
||||
Grid.Row="8"
|
||||
Grid.Column="1">
|
||||
<Hyperlink
|
||||
NavigateUri="{Binding Path=PackageMetadata.ReportAbuseUrl}"
|
||||
Style="{StaticResource HyperlinkStyle}"
|
||||
Command="{x:Static nuget:PackageManagerControlCommands.OpenExternalLink}"
|
||||
CommandParameter="{x:Static nugettel:HyperlinkType.ReportAbuse}"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Hyperlink_ReportAbuse}">
|
||||
<Run Text="{Binding Path=PackageMetadata.ReportAbuseUrl}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Tags -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=Tags,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="9"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_tagsLabel"
|
||||
Text="{x:Static nuget:Resources.Label_Tags}" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=Tags,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_tagsLabel}"
|
||||
Text="{Binding Path=Tags}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="9"
|
||||
Grid.Column="1" />
|
||||
</Grid>
|
||||
<!-- Tags -->
|
||||
<TextBlock
|
||||
Visibility="{Binding Path=PackageMetadata.Tags,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Grid.Row="9"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="0,8,0,0"
|
||||
x:Name="_tagsLabel"
|
||||
Text="{x:Static nuget:Resources.Label_Tags}" />
|
||||
<TextBox
|
||||
Style="{DynamicResource SelectableTextBlockStyle}"
|
||||
Visibility="{Binding Path=PackageMetadata.Tags,Converter={StaticResource NullToVisibilityConverter}}"
|
||||
AutomationProperties.LabeledBy="{Binding ElementName=_tagsLabel}"
|
||||
Text="{Binding Path=PackageMetadata.Tags}"
|
||||
Margin="8,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Grid.Row="9"
|
||||
Grid.Column="1" />
|
||||
</Grid>
|
||||
|
||||
<!-- dependencies treeview -->
|
||||
<TreeView
|
||||
<!-- dependencies treeview -->
|
||||
<TreeView
|
||||
x:Name="_dependencies"
|
||||
Grid.Row="3"
|
||||
Margin="-21,0,0,0"
|
||||
@@ -385,28 +419,29 @@
|
||||
LostFocus="TreeView_LostFocus"
|
||||
AutomationProperties.Name="{x:Static nuget:Resources.Label_Dependencies}"
|
||||
FocusVisualStyle="{StaticResource ControlsFocusVisualStyle}">
|
||||
<TreeView.Template>
|
||||
<!--
|
||||
<TreeView.Template>
|
||||
<!--
|
||||
Replaces default control template for this treeview with a slimmer control template.
|
||||
Specifically, this one has no ScrollViewer...which wasn't being used, and was making mousewheel not work for
|
||||
the larget detail scrollable area while the mouse was over the treeview.
|
||||
-->
|
||||
<ControlTemplate TargetType="TreeView">
|
||||
<ItemsPresenter />
|
||||
</ControlTemplate>
|
||||
</TreeView.Template>
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Dependencies}" DataType="{x:Type nuget:PackageDependencySetMetadata}">
|
||||
<TextBlock Text="{Binding TargetFrameworkDisplay}" Padding="0,2,0,0" />
|
||||
</HierarchicalDataTemplate>
|
||||
<DataTemplate DataType="{x:Type nuget:PackageDependencyMetadata}">
|
||||
<TextBlock Text="{Binding}" ToolTip="{Binding}" />
|
||||
</DataTemplate>
|
||||
</TreeView.Resources>
|
||||
<TreeViewItem Header="{x:Static nuget:Resources.Label_Dependencies}"
|
||||
ItemsSource="{Binding Path=DependencySets}"
|
||||
Style="{StaticResource TreeViewItemHeaderStyle}">
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
-->
|
||||
<ControlTemplate TargetType="TreeView">
|
||||
<ItemsPresenter />
|
||||
</ControlTemplate>
|
||||
</TreeView.Template>
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Dependencies}" DataType="{x:Type nuget:PackageDependencySetMetadata}">
|
||||
<TextBlock Text="{Binding TargetFrameworkDisplay}" Padding="0,2,0,0" />
|
||||
</HierarchicalDataTemplate>
|
||||
<DataTemplate DataType="{x:Type nuget:PackageDependencyMetadata}">
|
||||
<TextBlock Text="{Binding}" ToolTip="{Binding}" />
|
||||
</DataTemplate>
|
||||
</TreeView.Resources>
|
||||
<TreeViewItem Header="{x:Static nuget:Resources.Label_Dependencies}"
|
||||
ItemsSource="{Binding Path=PackageMetadata.DependencySets}"
|
||||
Style="{StaticResource TreeViewItemHeaderStyle}">
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -27,8 +27,9 @@ namespace NuGet.PackageManagement.UI
|
||||
|
||||
private void ViewLicense_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is DetailedPackageMetadata metadata)
|
||||
if (DataContext is DetailControlModel detailControlModel)
|
||||
{
|
||||
var metadata = detailControlModel.PackageMetadata;
|
||||
var window = new LicenseFileWindow()
|
||||
{
|
||||
DataContext = new LicenseFileData
|
||||
@@ -46,7 +47,7 @@ namespace NuGet.PackageManagement.UI
|
||||
flowDoc.Blocks.AddRange(PackageLicenseUtilities.GenerateParagraphs(content));
|
||||
await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
||||
(window.DataContext as LicenseFileData).LicenseText = flowDoc;
|
||||
}).PostOnFailure(nameof(PackageMetadataControl), nameof(ViewLicense_Click));
|
||||
}).PostOnFailure(nameof(PackageDetailsTabControl), nameof(ViewLicense_Click));
|
||||
|
||||
window.ShowModal();
|
||||
}
|
||||
@@ -54,7 +55,7 @@ namespace NuGet.PackageManagement.UI
|
||||
|
||||
private void PackageMetadataControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is DetailedPackageMetadata)
|
||||
if (DataContext is DetailControlModel)
|
||||
{
|
||||
Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<UserControl
|
||||
x:Class="NuGet.PackageManagement.UI.PackageReadmeControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:nuget="clr-namespace:NuGet.PackageManagement.UI"
|
||||
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
|
||||
xmlns:imagingTheme="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging"
|
||||
xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
|
||||
xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Utilities"
|
||||
DataContextChanged="UserControl_DataContextChanged"
|
||||
Unloaded="PackageReadmeControl_Unloaded"
|
||||
Loaded="PackageReadmeControl_Loaded"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="300">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<nuget:SharedResources/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<ContentControl
|
||||
x:Name="descriptionMarkdownPreview"
|
||||
x:Uid="descriptionMarkdownPreview"
|
||||
ClipToBounds="True"
|
||||
Focusable="False"
|
||||
Visibility="{Binding Path=ErrorLoadingReadme, Mode=OneWay, Converter={StaticResource NegatedBooleanToVisibilityConverter}}"
|
||||
/>
|
||||
<TextBlock
|
||||
TextWrapping="Wrap"
|
||||
Text="{x:Static nuget:Resources.Error_UnableToLoadReadme}"
|
||||
Visibility="{Binding Path=ErrorLoadingReadme, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,113 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.VisualStudio.Markdown.Platform;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using NuGet.PackageManagement.UI.ViewModels;
|
||||
using NuGet.VisualStudio;
|
||||
using NuGet.VisualStudio.Telemetry;
|
||||
|
||||
namespace NuGet.PackageManagement.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for PackageReadmeControl.xaml
|
||||
/// </summary>
|
||||
public partial class PackageReadmeControl : UserControl, IDisposable
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
private IMarkdownPreview _markdownPreview;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
private bool _disposed = false;
|
||||
|
||||
public PackageReadmeControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
_markdownPreview = new PreviewBuilder().Build();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
descriptionMarkdownPreview.Content = _markdownPreview.VisualElement;
|
||||
}
|
||||
|
||||
public ReadmePreviewViewModel ReadmeViewModel { get => (ReadmePreviewViewModel)DataContext; }
|
||||
|
||||
private void ReadmeViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(ReadmePreviewViewModel.ReadmeMarkdown))
|
||||
{
|
||||
NuGetUIThreadHelper.JoinableTaskFactory.Run(UpdateMarkdownAsync);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_markdownPreview?.Dispose();
|
||||
if (ReadmeViewModel is not null)
|
||||
{
|
||||
ReadmeViewModel.PropertyChanged -= ReadmeViewModel_PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private async Task UpdateMarkdownAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ReadmeViewModel.ReadmeMarkdown))
|
||||
{
|
||||
await _markdownPreview.UpdateContentAsync(ReadmeViewModel.ReadmeMarkdown, ScrollHint.None);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
|
||||
{
|
||||
ReadmeViewModel.ErrorLoadingReadme = true;
|
||||
ReadmeViewModel.ReadmeMarkdown = string.Empty;
|
||||
await TelemetryUtility.PostFaultAsync(ex, nameof(ReadmePreviewViewModel));
|
||||
}
|
||||
}
|
||||
|
||||
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.OldValue is ReadmePreviewViewModel oldMetadata)
|
||||
{
|
||||
oldMetadata.PropertyChanged -= ReadmeViewModel_PropertyChanged;
|
||||
}
|
||||
if (ReadmeViewModel is not null)
|
||||
{
|
||||
ReadmeViewModel.PropertyChanged += ReadmeViewModel_PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void PackageReadmeControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void PackageReadmeControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ThreadHelper.JoinableTaskFactory.Run(async () =>
|
||||
{
|
||||
await UpdateMarkdownAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -18,12 +18,16 @@
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<imaging:CrispImage
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<imaging:CrispImage
|
||||
Grid.Column="0"
|
||||
Margin="0,0,2,0"
|
||||
AutomationProperties.Name="{Binding MappingStatus, Mode=OneWay}"
|
||||
Moniker="{Binding MappingStatusIcon, Mode=OneWay}" />
|
||||
<TextBlock x:Name="_textStatus" Margin="0,0,9,0" TextWrapping="Wrap">
|
||||
<TextBlock Grid.Column="1" x:Name="_textStatus" Margin="0,0,9,0" TextWrapping="Wrap">
|
||||
<Run Text="{Binding MappingStatus, Mode=OneWay}" />
|
||||
<Control Margin="6,0,0,0" IsTabStop="False" />
|
||||
<controls:ButtonHyperlink Click="SettingsButtonClicked"
|
||||
@@ -32,6 +36,5 @@
|
||||
<TextBlock TextWrapping="Wrap" Text="{x:Static nuget:Resources.Hyperlink_PackageSourceMappingSettings}" />
|
||||
</controls:ButtonHyperlink>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
+28
-4
@@ -7,6 +7,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Services.Common;
|
||||
using NuGet.Common;
|
||||
using NuGet.Packaging.Core;
|
||||
using NuGet.ProjectManagement;
|
||||
@@ -255,15 +256,38 @@ namespace NuGet.PackageManagement.VisualStudio
|
||||
tasks.Add(_localRepository.GetPackageMetadataFromLocalSourceAsync(identity, cancellationToken));
|
||||
}
|
||||
|
||||
if (_globalLocalRepositories != null)
|
||||
{
|
||||
_globalLocalRepositories.ForEach(x =>
|
||||
tasks.Add(x.GetPackageMetadataFromLocalSourceAsync(identity, cancellationToken)));
|
||||
}
|
||||
|
||||
IEnumerable<IPackageSearchMetadata> completed = (await Task.WhenAll(tasks))
|
||||
.Where(m => m != null);
|
||||
|
||||
IPackageSearchMetadata master = completed.FirstOrDefault(m => !string.IsNullOrEmpty(m.Summary))
|
||||
IPackageSearchMetadata packageSearchMetadataBase = (completed.FirstOrDefault(m => !string.IsNullOrEmpty(m.Summary))
|
||||
?? completed.FirstOrDefault()
|
||||
?? PackageSearchMetadataBuilder.FromIdentity(identity).Build();
|
||||
?? PackageSearchMetadataBuilder.FromIdentity(identity).Build()).WithVersions(() => MergeVersionsAsync(identity, completed));
|
||||
|
||||
return master.WithVersions(
|
||||
asyncValueFactory: () => MergeVersionsAsync(identity, completed));
|
||||
|
||||
var clonedResult = packageSearchMetadataBase as PackageSearchMetadataBuilder.ClonedPackageSearchMetadata;
|
||||
|
||||
if (clonedResult == null)
|
||||
{
|
||||
return packageSearchMetadataBase;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(clonedResult.ReadmeFileUrl))
|
||||
{
|
||||
clonedResult.ReadmeFileUrl = completed.Select(m => m.ReadmeFileUrl).FirstOrDefault(u => !string.IsNullOrWhiteSpace(u));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(clonedResult.PackagePath))
|
||||
{
|
||||
clonedResult.PackagePath = completed.Select(m => (m as PackageSearchMetadataBuilder.ClonedPackageSearchMetadata)?.PackagePath).FirstOrDefault(uri => !string.IsNullOrWhiteSpace(uri));
|
||||
}
|
||||
|
||||
return clonedResult;
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<VersionInfo>> MergeVersionsAsync(PackageIdentity identity, IEnumerable<IPackageSearchMetadata> packages)
|
||||
|
||||
+17
@@ -164,6 +164,23 @@ namespace NuGet.PackageManagement.VisualStudio
|
||||
return stream;
|
||||
}
|
||||
|
||||
public async ValueTask<Stream?> GetReadmeAsync(Uri readmeUri, CancellationToken cancellationToken)
|
||||
{
|
||||
Assumes.NotNull(readmeUri);
|
||||
|
||||
Stream? stream;
|
||||
if (IsEmbeddedUri(readmeUri))
|
||||
{
|
||||
stream = await GetEmbeddedFileAsync(readmeUri, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = await GetStream(readmeUri);
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private async ValueTask<Stream?> GetEmbeddedFileAsync(Uri uri, CancellationToken cancellationToken)
|
||||
{
|
||||
string packagePath = uri.LocalPath;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
Azure.Core.dll
|
||||
Ben.Demystifier.dll
|
||||
Markdig.Signed.dll
|
||||
Microsoft.DataAI.NuGetRecommender.Contracts.dll
|
||||
Microsoft.IO.Redist.dll
|
||||
Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll
|
||||
@@ -76,6 +77,9 @@ Microsoft.VisualStudio.Debugger.UI.Interfaces.dll
|
||||
Microsoft.VisualStudio.Extensibility.Editor.Contracts.dll
|
||||
Microsoft.VisualStudio.HotReload.Components.dll
|
||||
Microsoft.VisualStudio.Linux.ConnectionManager.Store.dll
|
||||
Microsoft.VisualStudio.Markdown.Core.dll
|
||||
Microsoft.VisualStudio.Markdown.Platform.dll
|
||||
Microsoft.VisualStudio.Markdown.Platform.resources.dll
|
||||
Microsoft.VisualStudio.ProjectSystem.Managed.VS.dll
|
||||
Microsoft.VisualStudio.ProjectSystem.Managed.VS.resources.dll
|
||||
Microsoft.VisualStudio.ProjectSystem.Managed.dll
|
||||
@@ -85,6 +89,7 @@ Microsoft.VisualStudio.ProjectSystem.Query.resources.dll
|
||||
Microsoft.VisualStudio.Services.TestManagement.TestPlanning.WebApi.dll
|
||||
Microsoft.VisualStudio.Services.TestResults.WebApi.dll
|
||||
System.Configuration.ConfigurationManager.dll
|
||||
System.Drawing.Common.dll
|
||||
System.IdentityModel.Tokens.Jwt.dll
|
||||
System.Management.Automation.dll
|
||||
System.Memory.Data.dll
|
||||
|
||||
@@ -32,5 +32,6 @@ namespace NuGet.VisualStudio
|
||||
|
||||
public static readonly NuGetFeatureFlagConstants BulkRestoreCoordination = new("NuGet.BulkRestoreCoordination", "NUGET_BULK_RESTORE_COORDINATION", defaultState: true);
|
||||
public static readonly NuGetFeatureFlagConstants NuGetSolutionCacheInitilization = new("NuGet.SolutionCacheInitialization", "NUGET_SOLUTION_CACHE_INITIALIZATION", defaultState: true);
|
||||
public static readonly NuGetFeatureFlagConstants RenderReadmeInPMUI = new("NuGet.RenderReadmeInPMUI", "NUGET_RENDER_README_IN_PMUI", defaultState: true);
|
||||
}
|
||||
}
|
||||
+2
@@ -21,6 +21,7 @@ namespace NuGet.VisualStudio.Internal.Contracts
|
||||
public Uri? IconUrl { get; internal set; }
|
||||
public string? Tags { get; internal set; }
|
||||
public Uri? LicenseUrl { get; internal set; }
|
||||
public string? ReadmeFileUrl { get; internal set; }
|
||||
public Uri? ReadmeUrl { get; internal set; }
|
||||
public Uri? ProjectUrl { get; internal set; }
|
||||
public DateTimeOffset? Published { get; internal set; }
|
||||
@@ -59,6 +60,7 @@ namespace NuGet.VisualStudio.Internal.Contracts
|
||||
Tags = packageSearchMetadata.Tags,
|
||||
Identity = packageSearchMetadata.Identity,
|
||||
LicenseUrl = packageSearchMetadata.LicenseUrl,
|
||||
ReadmeFileUrl = packageSearchMetadata.ReadmeFileUrl,
|
||||
ReadmeUrl = packageSearchMetadata.ReadmeUrl,
|
||||
LicenseMetadata = packageSearchMetadata.LicenseMetadata,
|
||||
IsRecommended = recommendedPackageSearchMetadata?.IsRecommended ?? false,
|
||||
|
||||
@@ -15,5 +15,6 @@ namespace NuGet.VisualStudio.Internal.Contracts
|
||||
{
|
||||
ValueTask<Stream?> GetPackageIconAsync(PackageIdentity packageIdentity, CancellationToken cancellationToken);
|
||||
ValueTask<Stream?> GetEmbeddedLicenseAsync(PackageIdentity packageIdentity, CancellationToken cancellationToken);
|
||||
ValueTask<Stream?> GetReadmeAsync(Uri readmeUri, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
+1
@@ -33,6 +33,7 @@ namespace NuGet.VisualStudio.Internal.Contracts
|
||||
public PackageIdentity Identity => _inner.Identity;
|
||||
public Uri LicenseUrl => _inner.LicenseUrl;
|
||||
public Uri ProjectUrl => _inner.ProjectUrl;
|
||||
public string ReadmeFileUrl => _inner.ReadmeFileUrl;
|
||||
public Uri ReadmeUrl => _inner.ReadmeUrl;
|
||||
public Uri ReportAbuseUrl => _inner.ReportAbuseUrl;
|
||||
public Uri PackageDetailsUrl => _inner.PackageDetailsUrl;
|
||||
|
||||
+2
@@ -33,6 +33,8 @@ namespace NuGet.VisualStudio.Internal.Contracts
|
||||
|
||||
public Uri ReadmeUrl => _packageSearchMetadata.ReadmeUrl;
|
||||
|
||||
public string ReadmeFileUrl => _packageSearchMetadata.ReadmeFileUrl;
|
||||
|
||||
public Uri ReportAbuseUrl => _packageSearchMetadata.ReportAbuseUrl;
|
||||
|
||||
public Uri PackageDetailsUrl => _packageSearchMetadata.PackageDetailsUrl;
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace NuGet.Protocol
|
||||
public const string LicenseExpressionVersion = "licenseExpressionVersion";
|
||||
public const string ProjectUrl = "projectUrl";
|
||||
public const string ReadmeUrl = "readmeUrl";
|
||||
public const string ReadmeFileUrl = "readmeFileUrl";
|
||||
public const string Tags = "tags";
|
||||
public const string DownloadCount = "totalDownloads";
|
||||
public const string Created = "created";
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace NuGet.Protocol.Core.Types
|
||||
Uri LicenseUrl { get; }
|
||||
Uri ProjectUrl { get; }
|
||||
Uri ReadmeUrl { get; }
|
||||
string ReadmeFileUrl { get; }
|
||||
Uri ReportAbuseUrl { get; }
|
||||
Uri PackageDetailsUrl { get; }
|
||||
DateTimeOffset? Published { get; }
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace NuGet.Protocol
|
||||
|
||||
public Uri IconUrl => GetIconUri();
|
||||
|
||||
public string ReadmeFileUrl => GetReadmeUri();
|
||||
|
||||
public PackageIdentity Identity => _nuspec.GetIdentity();
|
||||
|
||||
public Uri LicenseUrl => Convert(_nuspec.GetLicenseUrl());
|
||||
@@ -206,5 +208,23 @@ namespace NuGet.Protocol
|
||||
// get the special icon url
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private string GetReadmeUri()
|
||||
{
|
||||
string embeddedReadme = _nuspec.GetReadme();
|
||||
if (embeddedReadme == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var baseUri = Convert(_package.Path);
|
||||
UriBuilder builder = new UriBuilder(baseUri)
|
||||
{
|
||||
Fragment = embeddedReadme
|
||||
};
|
||||
|
||||
// get the readme url
|
||||
return builder.Uri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,9 @@ namespace NuGet.Protocol
|
||||
[JsonConverter(typeof(SafeUriConverter))]
|
||||
public Uri ReadmeUrl { get; private set; }
|
||||
|
||||
[JsonProperty(PropertyName = JsonProperties.ReadmeFileUrl)]
|
||||
public string ReadmeFileUrl { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Uri ReportAbuseUrl { get; set; }
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace NuGet.Protocol.Core.Types
|
||||
public Uri ProjectUrl { get; set; }
|
||||
public DateTimeOffset? Published { get; set; }
|
||||
public Uri ReadmeUrl { get; set; }
|
||||
public string ReadmeFileUrl { get; set; }
|
||||
public Uri ReportAbuseUrl { get; set; }
|
||||
public Uri PackageDetailsUrl { get; set; }
|
||||
public bool RequireLicenseAcceptance { get; set; }
|
||||
@@ -98,6 +99,7 @@ namespace NuGet.Protocol.Core.Types
|
||||
ProjectUrl = _metadata.ProjectUrl,
|
||||
Published = _metadata.Published,
|
||||
ReadmeUrl = _metadata.ReadmeUrl,
|
||||
ReadmeFileUrl = _metadata.ReadmeFileUrl,
|
||||
ReportAbuseUrl = _metadata.ReportAbuseUrl,
|
||||
PackageDetailsUrl = _metadata.PackageDetailsUrl,
|
||||
RequireLicenseAcceptance = _metadata.RequireLicenseAcceptance,
|
||||
|
||||
@@ -105,6 +105,8 @@ namespace NuGet.Protocol
|
||||
|
||||
public Uri ReadmeUrl { get; } = null; // The ReadmeUrl has not been added to the V2 feed.
|
||||
|
||||
public string ReadmeFileUrl { get; } = null; // The ReadmeFileUrl has not been added to the V2 feed.
|
||||
|
||||
public Uri ReportAbuseUrl { get; private set; }
|
||||
|
||||
public Uri PackageDetailsUrl { get; private set; }
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
#nullable enable
|
||||
~NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer(string rawPluginPaths) -> void
|
||||
~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
|
||||
@@ -1,2 +1,9 @@
|
||||
#nullable enable
|
||||
~NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer(string rawPluginPaths) -> void
|
||||
~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
|
||||
@@ -1,2 +1,9 @@
|
||||
#nullable enable
|
||||
~NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer(string rawPluginPaths) -> void
|
||||
~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
|
||||
+1
-1
@@ -41,7 +41,7 @@ namespace NuGet.PackageManagement.UI.Test
|
||||
{
|
||||
IReadOnlyList<PropertyInfo> properties = GetSerializableProperties<UserSettings>();
|
||||
|
||||
Assert.Equal(12, properties.Count);
|
||||
Assert.Equal(13, properties.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// 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.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Sdk.TestFramework;
|
||||
using Moq;
|
||||
using NuGet.PackageManagement.UI.ViewModels;
|
||||
using NuGet.VisualStudio.Internal.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace NuGet.PackageManagement.UI.Test.ViewModels
|
||||
{
|
||||
[Collection(MockedVS.Collection)]
|
||||
public class ReadmePreviewViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithNullPackageFileService_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
{
|
||||
new ReadmePreviewViewModel(null, ItemFilter.All, true);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Defaults()
|
||||
{
|
||||
//Arrange
|
||||
var mockFileService = new Mock<INuGetPackageFileService>();
|
||||
|
||||
//Act
|
||||
var target = new ReadmePreviewViewModel(mockFileService.Object, ItemFilter.All, true);
|
||||
|
||||
//Assert
|
||||
Assert.False(target.ErrorLoadingReadme);
|
||||
Assert.Equal(string.Empty, target.ReadmeMarkdown);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public async Task SetPackageMetadataAsync_WithoutReadmeUrl_NoReadmeReturned(string readmeUrl)
|
||||
{
|
||||
//Arrange
|
||||
var mockFileService = new Mock<INuGetPackageFileService>();
|
||||
var target = new ReadmePreviewViewModel(mockFileService.Object, ItemFilter.Installed, true);
|
||||
var package = new DetailedPackageMetadata();
|
||||
package.ReadmeFileUrl = readmeUrl;
|
||||
|
||||
//Act
|
||||
await target.SetPackageMetadataAsync(package, CancellationToken.None);
|
||||
|
||||
//Assert
|
||||
Assert.False(target.ErrorLoadingReadme);
|
||||
Assert.Equal(string.Empty, target.ReadmeMarkdown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetCurrentFilter_ItemFilterAll_NoLocalReadmeReturned()
|
||||
{
|
||||
//Arrange
|
||||
var readmeContents = "readme contents";
|
||||
using Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(readmeContents));
|
||||
var mockFileService = new Mock<INuGetPackageFileService>();
|
||||
mockFileService.Setup(x => x.GetReadmeAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>())).ReturnsAsync(stream);
|
||||
var target = new ReadmePreviewViewModel(mockFileService.Object, ItemFilter.Installed, true);
|
||||
var package = new DetailedPackageMetadata();
|
||||
package.ReadmeFileUrl = "C://path/to/readme.md";
|
||||
await target.SetPackageMetadataAsync(package, CancellationToken.None);
|
||||
|
||||
//Act
|
||||
await target.ItemFilterChangedAsync(ItemFilter.All);
|
||||
|
||||
//Assert
|
||||
Assert.False(target.ErrorLoadingReadme);
|
||||
Assert.Equal(string.Empty, target.ReadmeMarkdown);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ItemFilter.Installed)]
|
||||
[InlineData(ItemFilter.Consolidate)]
|
||||
[InlineData(ItemFilter.UpdatesAvailable)]
|
||||
public async Task SetCurrentFilter_ItemFilterRenderingLocalReadme_LocalReadmeReturned(ItemFilter filter)
|
||||
{
|
||||
//Arrange
|
||||
var readmeContents = "readme contents";
|
||||
using Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(readmeContents));
|
||||
var mockFileService = new Mock<INuGetPackageFileService>();
|
||||
mockFileService.Setup(x => x.GetReadmeAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>())).ReturnsAsync(stream);
|
||||
var target = new ReadmePreviewViewModel(mockFileService.Object, ItemFilter.All, true);
|
||||
var package = new DetailedPackageMetadata();
|
||||
package.ReadmeFileUrl = "C://path/to/readme.md";
|
||||
await target.SetPackageMetadataAsync(package, CancellationToken.None);
|
||||
|
||||
//Act
|
||||
await target.ItemFilterChangedAsync(filter);
|
||||
|
||||
//Assert
|
||||
Assert.False(target.ErrorLoadingReadme);
|
||||
Assert.Equal(readmeContents, target.ReadmeMarkdown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetPackageMetadataAsync_WithLocalReadmeUrl_RenderLocalReadmeTrue_LocalReadmeReturned()
|
||||
{
|
||||
//Arrange
|
||||
var readmeContents = "readme contents";
|
||||
using Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(readmeContents));
|
||||
var mockFileService = new Mock<INuGetPackageFileService>();
|
||||
mockFileService.Setup(x => x.GetReadmeAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>())).ReturnsAsync(stream);
|
||||
var target = new ReadmePreviewViewModel(mockFileService.Object, ItemFilter.Installed, true);
|
||||
var package = new DetailedPackageMetadata();
|
||||
package.ReadmeFileUrl = "C://path/to/readme.md";
|
||||
|
||||
//Act
|
||||
await target.SetPackageMetadataAsync(package, CancellationToken.None);
|
||||
|
||||
//Assert
|
||||
Assert.False(target.ErrorLoadingReadme);
|
||||
Assert.Equal(readmeContents, target.ReadmeMarkdown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetPackageMetadataAsync_WithLocalReadmeUrl_RenderLocalReadmeFalse_NoLocalReadmeReturned()
|
||||
{
|
||||
//Arrange
|
||||
var readmeContents = "readme contents";
|
||||
using Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(readmeContents));
|
||||
var mockFileService = new Mock<INuGetPackageFileService>();
|
||||
mockFileService.Setup(x => x.GetReadmeAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>())).ReturnsAsync(stream);
|
||||
var target = new ReadmePreviewViewModel(mockFileService.Object, ItemFilter.All, true);
|
||||
var package = new DetailedPackageMetadata();
|
||||
package.ReadmeFileUrl = "C://path/to/readme.md";
|
||||
|
||||
//Act
|
||||
await target.SetPackageMetadataAsync(package, CancellationToken.None);
|
||||
|
||||
//Assert
|
||||
Assert.False(target.ErrorLoadingReadme);
|
||||
Assert.Equal(string.Empty, target.ReadmeMarkdown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetPackageMetadataAsync_WithLocalReadmeUrl_FileNotFound_NoReadmeFoundTextReturned()
|
||||
{
|
||||
//Arrange
|
||||
var mockFileService = new Mock<INuGetPackageFileService>();
|
||||
mockFileService.Setup(x => x.GetReadmeAsync(It.IsAny<Uri>(), It.IsAny<CancellationToken>())).Returns(null);
|
||||
var target = new ReadmePreviewViewModel(mockFileService.Object, ItemFilter.Installed, true);
|
||||
var package = new DetailedPackageMetadata();
|
||||
package.ReadmeFileUrl = "C://path/to/readme.md";
|
||||
|
||||
//Act
|
||||
await target.SetPackageMetadataAsync(package, CancellationToken.None);
|
||||
|
||||
//Assert
|
||||
Assert.False(target.ErrorLoadingReadme);
|
||||
Assert.Equal(Resources.Text_NoReadme, target.ReadmeMarkdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
-3
@@ -449,8 +449,41 @@ namespace NuGet.PackageManagement.VisualStudio.Test
|
||||
Assert.Null(licenseStream);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Readme_WhenAdded_IsFound()
|
||||
{
|
||||
using (TestDirectory testDir = TestDirectory.Create())
|
||||
{
|
||||
// Create decoy nuget package
|
||||
var readmeContent = "Hello this is README";
|
||||
var readmeName = "readme.md";
|
||||
var zipPath = Path.Combine(testDir.Path, "file.nupkg");
|
||||
CreateDummyPackage(
|
||||
zipPath: zipPath,
|
||||
readmeName: readmeName,
|
||||
readmeContent: readmeContent);
|
||||
|
||||
// prepare test
|
||||
var builder = new UriBuilder(new Uri(zipPath, UriKind.Absolute))
|
||||
{
|
||||
Fragment = readmeName
|
||||
};
|
||||
|
||||
var packageFileService = new NuGetPackageFileService(
|
||||
default(ServiceActivationOptions),
|
||||
Mock.Of<IServiceBroker>(),
|
||||
new AuthorizationServiceClient(Mock.Of<IAuthorizationService>()),
|
||||
_telemetryProvider.Object);
|
||||
|
||||
Stream readmeStream = await packageFileService.GetReadmeAsync(builder.Uri, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(readmeStream);
|
||||
Assert.Equal(readmeContent.Length, readmeStream.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NuGet package with .nupkg extension and with a PNG image named "icon.png"
|
||||
/// Creates a NuGet package with .nupkg extension and with a PNG image named "icon.png" and a MD readme named "readme.md"
|
||||
/// </summary>
|
||||
/// <param name="path">Path to NuGet package</param>
|
||||
/// <param name="iconName">Icon filename with .png extension</param>
|
||||
@@ -459,7 +492,9 @@ namespace NuGet.PackageManagement.VisualStudio.Test
|
||||
string iconName = "icon.png",
|
||||
string iconFile = "icon.png",
|
||||
string iconFileSourceElement = "icon.png",
|
||||
string iconFileTargetElement = "")
|
||||
string iconFileTargetElement = "",
|
||||
string readmeName = "readme.md",
|
||||
string readmeContent = "readme content")
|
||||
{
|
||||
var dir = Path.GetDirectoryName(zipPath);
|
||||
var holdDir = "pkg";
|
||||
@@ -471,7 +506,9 @@ namespace NuGet.PackageManagement.VisualStudio.Test
|
||||
// create nuspec
|
||||
var nuspec = NuspecBuilder.Create()
|
||||
.WithIcon(iconName)
|
||||
.WithFile(iconFileSourceElement, iconFileTargetElement);
|
||||
.WithFile(iconFileSourceElement, iconFileTargetElement)
|
||||
.WithReadme(readmeName)
|
||||
.WithFile(readmeName);
|
||||
|
||||
// create png image
|
||||
var iconPath = Path.Combine(folderPath, iconFile);
|
||||
@@ -480,6 +517,13 @@ namespace NuGet.PackageManagement.VisualStudio.Test
|
||||
|
||||
File.WriteAllText(iconPath, StreamContents);
|
||||
|
||||
// create md readme
|
||||
var readmePath = Path.Combine(folderPath, readmeName);
|
||||
var readmeDir = Path.GetDirectoryName(readmePath);
|
||||
Directory.CreateDirectory(readmeDir);
|
||||
|
||||
File.WriteAllText(readmePath, readmeContent);
|
||||
|
||||
// Create nuget package
|
||||
using (var nuspecStream = new MemoryStream())
|
||||
using (FileStream nupkgStream = File.Create(zipPath))
|
||||
|
||||
@@ -66,5 +66,16 @@ namespace NuGet.Protocol.Tests
|
||||
Assert.True(_testData.TestData.IconUrl.IsAbsoluteUri);
|
||||
Assert.NotNull(_testData.TestData.IconUrl.Fragment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadmeFileUrl_ReturnsEmbeddedReadmeUri()
|
||||
{
|
||||
Assert.NotNull(_testData.TestData.ReadmeFileUrl);
|
||||
|
||||
var readmeFileUri = new Uri(_testData.TestData.ReadmeFileUrl);
|
||||
Assert.True(readmeFileUri.IsFile);
|
||||
Assert.True(readmeFileUri.IsAbsoluteUri);
|
||||
Assert.NotNull(readmeFileUri.Fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ namespace NuGet.Test.Utility
|
||||
.WithPackageId(pkgId.Id)
|
||||
.WithPackageVersion(pkgId.Version.ToNormalizedString())
|
||||
.WithIcon("icon.png")
|
||||
.WithReadme("readme.md")
|
||||
.Build();
|
||||
pkg.Nuspec = XDocument.Parse(nuspec.ToString());
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace NuGet.Test.Utility
|
||||
|
||||
public Uri ReadmeUrl => null;
|
||||
|
||||
public string ReadmeFileUrl => null;
|
||||
|
||||
public Uri LicenseUrl => null;
|
||||
|
||||
public Uri ProjectUrl => null;
|
||||
|
||||
Reference in new issue
Block a user