Skip to content

Commit

Permalink
Updated PM UI to render the README inside of a tab in the README Deta…
Browse files Browse the repository at this point in the history
…ils pane. (#6094)

* Add ReadmeFileUrl field to IPackageSearchMetadata (#6014)

Adds the ReadmeFileUrl field to IPackageSearchMetadata and implements the ability to get the URL for local packages. Future PRs will include implementations for getting the URL for remote READMEs and downloading them.

---------

Co-authored-by: Andy Zivkovic <zivkan@users.noreply.github.com>

* Added new user controls, updated details

* Adjust readme viewmodel name

* update views

* Ensured ReadmefileUrl is set when merging sources

* Fix delegation, update ux to render properly

* Limit local readme rendering to excluse browse tab

* ensure no readme found message for local packages. Added tests

* update property count

* lock url

* fix rebase weirdness.

* PR comments

* Simplify

* Move model to viewmodel

* Renamed controls so they're less confusing

* Switched to templates for tabs

* fix typo

* Update src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/PackageFeeds/MultiSourcePackageMetadataProvider.cs

Co-authored-by: Donnie Goodson <49205731+donnie-msft@users.noreply.github.com>

* PR comment

* rename

* rename tests

* change spacing to make pr easier to see

* more spacing

* Update src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageDetailsTabViewModel.cs

Co-authored-by: Donnie Goodson <49205731+donnie-msft@users.noreply.github.com>

* Revert "Update src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageDetailsTabViewModel.cs"

This reverts commit add1b00.

* IsVisible rename

* add is visible

* extra line

* rename tsts

* Reduce tab dependencies

* pr comments

* Adjust how selected tab is set

* rename loaded/unloaded

* avoid setting visibility, make tabs more generic

* create conversion for vm to enum

* pr comments

* Update src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageDetailsTabViewModel.cs

Co-authored-by: Jean-Pierre Briedé <jebriede@microsoft.com>

---------

Co-authored-by: Andy Zivkovic <zivkan@users.noreply.github.com>
Co-authored-by: Donnie Goodson <49205731+donnie-msft@users.noreply.github.com>
Co-authored-by: Jean-Pierre Briedé <jebriede@microsoft.com>
  • Loading branch information
4 people committed Oct 21, 2024
1 parent 575b1e8 commit 2c9fde3
Show file tree
Hide file tree
Showing 28 changed files with 1,242 additions and 504 deletions.
7 changes: 0 additions & 7 deletions src/NuGet.Clients/NuGet.Indexing/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
#nullable enable
~const NuGet.Protocol.JsonProperties.ReadmeFileUrl = "readmeFileUrl" -> string
~NuGet.Protocol.Core.Types.IPackageSearchMetadata.ReadmeFileUrl.get -> string
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.ReadmeFileUrl.get -> string
~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.ReadmeFileUrl.set -> void
~NuGet.Protocol.LocalPackageSearchMetadata.ReadmeFileUrl.get -> string
~NuGet.Protocol.PackageSearchMetadata.ReadmeFileUrl.get -> string
~NuGet.Protocol.PackageSearchMetadataV2Feed.ReadmeFileUrl.get -> string
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
[assembly: SuppressMessage("Build", "CA1501:'PackageManagerControl' has an object hierarchy '9' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'UserControl, ContentControl, Control, FrameworkElement, UIElement, Visual, DependencyObject, DispatcherObject, Object'", Justification = "<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")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -70,6 +70,9 @@ protected DetailControlModel(
_options.SelectedChanged += DependencyBehavior_SelectedChanged;

_versions = new ItemsChangeObservableCollection<DisplayVersion>();

Title = Resources.Label_PackageDetails;
IsVisible = true;
}

/// <summary>
Expand Down Expand Up @@ -385,11 +388,9 @@ private async Task<IReadOnlyList<PackageDependency>> GetDependencies(IProjectCon
// Called after package install/uninstall.
public abstract Task RefreshAsync(CancellationToken cancellationToken);

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
RaisePropertyChanged(propertyName);
}

public string Id => _searchResultPackage?.Id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public DetailedPackageMetadata(PackageSearchMetadataContextInfo serverData, Pack
IconUrl = serverData.IconUrl;
LicenseUrl = serverData.LicenseUrl;
ProjectUrl = serverData.ProjectUrl;
ReadmeFileUrl = serverData.ReadmeFileUrl;
ReadmeUrl = serverData.ReadmeUrl;
ReportAbuseUrl = serverData.ReportAbuseUrl;
// Some server implementations send down an array with an empty string, which ends up as an empty string.
Expand Down Expand Up @@ -95,6 +96,8 @@ public DetailedPackageMetadata(PackageSearchMetadataContextInfo serverData, Pack

public Uri? ProjectUrl { get; set; }

public string? ReadmeFileUrl { get; set; }

public Uri? ReadmeUrl { get; set; }

public Uri? ReportAbuseUrl { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
}
}
42 changes: 42 additions & 0 deletions src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions src/NuGet.Clients/NuGet.PackageManagement.UI/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
3 changes: 3 additions & 0 deletions src/NuGet.Clients/NuGet.PackageManagement.UI/UserSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public UserSettings()
SelectedFilter = ItemFilter.Installed;
DependencyBehavior = DependencyBehavior.Lowest;
FileConflictAction = FileConflictAction.PromptUser;
SelectedPackageMetadataTab = PackageMetadataTab.Readme;
OptionsExpanded = false;
}

Expand All @@ -40,6 +41,8 @@ public UserSettings()

public ItemFilter SelectedFilter { get; set; }

public PackageMetadataTab SelectedPackageMetadataTab { get; set; }

public DependencyBehavior DependencyBehavior { get; set; }

public FileConflictAction FileConflictAction { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using NuGet.VisualStudio;
using NuGet.VisualStudio.Internal.Contracts;

namespace NuGet.PackageManagement.UI.ViewModels
{
public class PackageDetailsTabViewModel : ViewModelBase, IDisposable
{
private bool _disposed = false;
private bool _readmeTabEnabled;

public ReadmePreviewViewModel ReadmePreviewViewModel { get; private set; }

public DetailControlModel DetailControlModel { get; private set; }

public ObservableCollection<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;

Tabs.Add(ReadmePreviewViewModel);
Tabs.Add(DetailControlModel);

SelectedTab = Tabs.FirstOrDefault(t => t.IsVisible && ConvertFromTabType(t) == initialSelectedTab) ?? Tabs.FirstOrDefault(t => t.IsVisible);

DetailControlModel.PropertyChanged += DetailControlModel_PropertyChanged;

foreach (var tab in Tabs)
{
tab.PropertyChanged += IsVisible_PropertyChanged;
}
}

public static PackageMetadataTab ConvertFromTabType(TitledPageViewModelBase vm)
{
if (vm is DetailControlModel)
{
return PackageMetadataTab.PackageDetails;
}
return PackageMetadataTab.Readme;
}

public async Task SetCurrentFilterAsync(ItemFilter filter)
{
if (_readmeTabEnabled)
{
await ReadmePreviewViewModel.SetCurrentFilterAsync(filter);
}
}

public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
DetailControlModel.PropertyChanged -= DetailControlModel_PropertyChanged;

foreach (var tab in Tabs)
{
tab.PropertyChanged -= IsVisible_PropertyChanged;
}
}

private void IsVisible_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TitledPageViewModelBase.IsVisible))
{
if (SelectedTab == null || (SelectedTab == sender && !SelectedTab.IsVisible))
{
SelectedTab = Tabs.FirstOrDefault(t => t.IsVisible);
}
}
}

private void DetailControlModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
ThreadHelper.JoinableTaskFactory.Run(async () =>
{
if (_readmeTabEnabled)
{
await ReadmePreviewViewModel.SetPackageMetadataAsync(DetailControlModel.PackageMetadata, CancellationToken.None);
}
});
}
}
}
Loading

0 comments on commit 2c9fde3

Please sign in to comment.