diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs index 0dca72d3572..0bfe73c24ca 100644 --- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs +++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs @@ -6540,6 +6540,15 @@ public static string PackageManagerPackageSettingsTab { } } + /// + /// Looks up a localized string similar to New version available. + /// + public static string PackageManagerPackageUpdateAvailable { + get { + return ResourceManager.GetString("PackageManagerPackageUpdateAvailable", resourceCulture); + } + } + /// /// Looks up a localized string similar to Updated. /// @@ -6675,6 +6684,24 @@ public static string PackageManagerTitle { } } + /// + /// Looks up a localized string similar to Uninstall. + /// + public static string PackageManagerUninstall { + get { + return ResourceManager.GetString("PackageManagerUninstall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Update. + /// + public static string PackageManagerUpdate { + get { + return ResourceManager.GetString("PackageManagerUpdate", resourceCulture); + } + } + /// /// Looks up a localized string similar to Once signed in, you can find your published packages here.. /// diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx index 3e4a9f28836..4875701090a 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx @@ -3034,10 +3034,22 @@ This package will be unloaded after the next Dynamo restart. Install Used on the package manager search result card. If the package is not installed, the button will say 'Install'. + + Update + Used on the package manager search result card when a different version is selected. + + + Uninstall + Used on the package manager search result card when the installed version is selected. + Updated Displays next to the package name in the Package Search window if the package has been updated in the last 30 days. + + New version available + Displays next to the package name in the Package Search window if a newer version of the package is available. + New Displays next to the package name in the Package Search window if the package has been added in the last 30 days. diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx index 8f20115172f..6fa168fc2b2 100644 --- a/src/DynamoCoreWpf/Properties/Resources.resx +++ b/src/DynamoCoreWpf/Properties/Resources.resx @@ -1291,10 +1291,22 @@ Don't worry, you'll have the option to save your work. Install Used on the package manager search result card. If the package is not installed, the button will say 'Install'. + + Update + Used on the package manager search result card when a different version is selected. + + + Uninstall + Used on the package manager search result card when the installed version is selected. + Updated Displays next to the package name in the Package Search window if the package has been updated in the last 30 days. + + New version available + Displays next to the package name in the Package Search window if a newer version of the package is available. + New Displays next to the package name in the Package Search window if the package has been added in the last 30 days. diff --git a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt index 7ad7160276a..0036df78e18 100644 --- a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt +++ b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt @@ -1364,13 +1364,20 @@ Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.DownloadLa Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.DownloadLatestToCustomPathCommand.set -> void Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.Downloads.get -> int Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.Equals(Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel other) -> bool +Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.HasUpdateAvailable.get -> bool +Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.HasUpdateAvailable.set -> void Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.HasUpvote.get -> bool Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.Hosts.get -> System.Collections.Generic.List +Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.InstallActionCommand.get -> System.Windows.Input.ICommand +Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.InstallActionText.get -> string Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsDeprecated.get -> bool Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsEnabledForInstall.get -> bool +Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsInstalledFallback.get -> bool +Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsInstalledVersionSelected.get -> bool Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsOnwer.get -> bool Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsSelectedVersionCompatible.get -> bool? Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsSelectedVersionCompatible.set -> void +Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.IsUninstallState.get -> bool Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.LatestVersion.get -> string Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.LatestVersionCreated.get -> string Dynamo.PackageManager.ViewModels.PackageManagerSearchElementViewModel.Maintainers.get -> string @@ -5160,6 +5167,9 @@ static Dynamo.Wpf.Properties.Resources.NodeAutoCompleteToolTip.get -> string static Dynamo.Wpf.Properties.Resources.OfflineStatusTooltip.get -> string static Dynamo.Wpf.Properties.Resources.OnlineStatusTooltip.get -> string static Dynamo.Wpf.Properties.Resources.PackageDuplicateAssembliesFoundMessage.get -> string +static Dynamo.Wpf.Properties.Resources.PackageManagerPackageUpdateAvailable.get -> string +static Dynamo.Wpf.Properties.Resources.PackageManagerUninstall.get -> string +static Dynamo.Wpf.Properties.Resources.PackageManagerUpdate.get -> string static Dynamo.Wpf.Properties.Resources.PreferencesViewEnableNodeAutoCompleteNewUI.get -> string static Dynamo.Wpf.Properties.Resources.PreferencesViewEnableNodeAutoCompleteNewUITooltipText.get -> string static Dynamo.Wpf.Properties.Resources.NodeContextMenuEnablePeriodicUpdate.get -> string diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchElementViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchElementViewModel.cs index fecf1340d3c..2f016eefa59 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchElementViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchElementViewModel.cs @@ -7,6 +7,7 @@ using System.Windows.Data; using System.Windows.Input; using Dynamo.ViewModels; +using Dynamo.Wpf.Properties; using Dynamo.Wpf.ViewModels; using Greg.Responses; using Prism.Commands; @@ -20,6 +21,10 @@ public class PackageManagerSearchElementViewModel : BrowserItemViewModel, IEquat public ICommand VisitSiteCommand { get; set; } public ICommand VisitRepositoryCommand { get; set; } public ICommand DownloadLatestToCustomPathCommand { get; set; } + public ICommand InstallActionCommand + { + get { return IsInstalledVersionSelected ? uninstallCommand : DownloadLatestCommand; } + } /// /// VM IsDeprecated property @@ -69,6 +74,24 @@ public class PackageManagerSearchElementViewModel : BrowserItemViewModel, IEquat /// The currently selected version of a package /// private VersionInformation selectedVersion; + private string installedVersion; + private bool hasUpdateAvailable; + private readonly DelegateCommand uninstallCommand; + private Func canUninstall; + private bool HasInstalledVersion => !string.IsNullOrEmpty(installedVersion); + + /// + /// True when the selected version is installed. + /// + public bool IsInstalledVersionSelected => SelectedVersion?.IsInstalled == true; + /// + /// True when the selected installed version cannot be uninstalled. + /// + public bool IsInstalledFallback => IsInstalledVersionSelected && canUninstall == null; + /// + /// True when the selected installed version can be uninstalled. + /// + public bool IsUninstallState => IsInstalledVersionSelected && canUninstall != null; public bool? IsSelectedVersionCompatible { @@ -96,8 +119,71 @@ public VersionInformation SelectedVersion selectedVersion = value; // Update the compatibility info so the icon of the currently selected version is updated - IsSelectedVersionCompatible = selectedVersion.IsCompatible; + IsSelectedVersionCompatible = selectedVersion?.IsCompatible; SearchElementModel.SelectedVersion = selectedVersion; + RaisePropertyChanged(nameof(SelectedVersion)); + RaisePropertyChanged(nameof(InstallActionText)); + RaisePropertyChanged(nameof(InstallActionCommand)); + RaisePropertyChanged(nameof(IsInstalledVersionSelected)); + RaisePropertyChanged(nameof(IsInstalledFallback)); + RaisePropertyChanged(nameof(IsUninstallState)); + RefreshUninstallCommandCanExecute(); + } + } + } + + /// + /// The text to display for the install action button, which changes based on the package's installation state. + /// + public string InstallActionText + { + get + { + if (HasInstalledVersion) + { + if (IsInstalledVersionSelected) + { + return IsInstalledFallback + ? Resources.PackageDownloadStateInstalled + : Resources.PackageManagerUninstall; + } + + return Resources.PackageManagerUpdate; + } + + return Resources.PackageManagerInstall; + } + } + + /// + /// True if newer version is available for an installed package + /// + public bool HasUpdateAvailable + { + get { return hasUpdateAvailable; } + set + { + if (hasUpdateAvailable != value) + { + hasUpdateAvailable = value; + RaisePropertyChanged(nameof(HasUpdateAvailable)); + } + } + } + + internal ICommand UninstallCommand => uninstallCommand; + internal Func CanUninstall + { + get { return canUninstall; } + set + { + if (canUninstall != value) + { + canUninstall = value; + RaisePropertyChanged(nameof(InstallActionText)); + RaisePropertyChanged(nameof(IsInstalledFallback)); + RaisePropertyChanged(nameof(IsUninstallState)); + RefreshUninstallCommandCanExecute(); } } } @@ -116,6 +202,7 @@ public PackageManagerSearchElementViewModel(PackageManagerSearchElement element, this.SearchElementModel = element; CanInstall = install; IsEnabledForInstall = isEnabledForInstall; + uninstallCommand = new DelegateCommand(OnRequestUninstall, () => CanUninstall?.Invoke() == true); // Attempts to show the latest compatible version. If no compatible, will return the latest instead. this.SelectedVersion = this.SearchElementModel.LatestCompatibleVersion; @@ -128,7 +215,7 @@ public PackageManagerSearchElementViewModel(PackageManagerSearchElement element, this.DownloadLatestCommand = new DelegateCommand( () => OnRequestDownload(false), - () => !SearchElementModel.IsDeprecated && CanInstall); + () => !SearchElementModel.IsDeprecated); this.DownloadLatestToCustomPathCommand = new DelegateCommand(() => OnRequestDownload(true)); this.UpvoteCommand = new DelegateCommand(SearchElementModel.Upvote, () => canLogin); @@ -143,11 +230,11 @@ private void OnSearchElementModelPropertyChanged(object sender, PropertyChangedE { if (e.PropertyName == nameof(SearchElementModel.LatestCompatibleVersion)) { - this.SelectedVersion = this.SearchElementModel.LatestCompatibleVersion; + SetDefaultSelectedVersion(); } if (e.PropertyName == nameof(SearchElementModel.VersionDetails)) { - this.VersionInformationList = this.SearchElementModel.VersionDetails; + SetDefaultSelectedVersion(); } } @@ -191,8 +278,11 @@ public bool CanInstall internal set { - canInstall = value; - RaisePropertyChanged(nameof(CanInstall)); + if (canInstall != value) + { + canInstall = value; + RaisePropertyChanged(nameof(CanInstall)); + } } } @@ -262,6 +352,7 @@ public List VersionInformationList { versionInformationList = value; RaisePropertyChanged(nameof(VersionInformationList)); + UpdateInstalledVersionFlags(); } } } @@ -278,13 +369,75 @@ public ICollectionView ReversedVersionInformationList } } + internal void UpdateInstalledVersion(string version, bool setDefaultSelection) + { + installedVersion = version; + UpdateInstalledVersionFlags(); + + if (setDefaultSelection) + { + SetDefaultSelectedVersion(); + } + RaisePropertyChanged(nameof(InstallActionText)); + RaisePropertyChanged(nameof(InstallActionCommand)); + RaisePropertyChanged(nameof(IsInstalledVersionSelected)); + RaisePropertyChanged(nameof(IsInstalledFallback)); + RaisePropertyChanged(nameof(IsUninstallState)); + RefreshUninstallCommandCanExecute(); + } + + private void SetDefaultSelectedVersion() + { + var defaultVersion = GetDefaultSelectedVersion(); + if (defaultVersion != null && SelectedVersion != defaultVersion) + { + SelectedVersion = defaultVersion; + } + } + + private VersionInformation GetDefaultSelectedVersion() + { + if (!string.IsNullOrEmpty(installedVersion)) + { + var installedInfo = VersionInformationList?.FirstOrDefault(v => v.Version == installedVersion); + if (installedInfo != null) + { + return installedInfo; + } + } + + return SearchElementModel?.LatestCompatibleVersion; + } + + private void UpdateInstalledVersionFlags() + { + if (VersionInformationList == null) return; + + foreach (var versionInfo in VersionInformationList) + { + versionInfo.IsInstalled = !string.IsNullOrEmpty(installedVersion) && + string.Equals(versionInfo.Version, installedVersion, StringComparison.OrdinalIgnoreCase); + } + + RaisePropertyChanged(nameof(ReversedVersionInformationList)); + } + + private void RefreshUninstallCommandCanExecute() + { + if (uninstallCommand is DelegateCommand delegateCommand) + { + delegateCommand.RaiseCanExecuteChanged(); + } + } + private List CustomPackageFolders; private bool? isSelectedVersionCompatible; public delegate void PackageSearchElementDownloadHandler( PackageManagerSearchElement element, PackageVersion version, string downloadPath = null); public event PackageSearchElementDownloadHandler RequestDownload; - + internal event Action RequestUninstall; + public void OnRequestDownload(PackageVersion version, bool downloadToCustomPath) { string downloadPath = String.Empty; @@ -301,9 +454,19 @@ public void OnRequestDownload(PackageVersion version, bool downloadToCustomPath) RequestDownload(this.SearchElementModel, version, downloadPath); } + private void OnRequestUninstall() + { + if (RequestUninstall != null) + { + RequestUninstall(this); + } + } + private void OnRequestDownload(bool downloadToCustomPath) { - var version = this.SearchElementModel.Header.versions.First(x => x.version.Equals(SelectedVersion.Version)); + var version = this.SearchElementModel?.Header?.versions + ?.FirstOrDefault(x => x.version.Equals(SelectedVersion.Version)); + if (version == null) return; string downloadPath = String.Empty; diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs index 1ea7c92f3be..eb9d0fbb5c3 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs @@ -496,6 +496,203 @@ internal bool CanInstallPackage(PackageDownloadHandle dh) } } + private void UpdateInstallState(PackageManagerSearchElementViewModel element, bool setDefaultSelection = false) + { + if (element?.SearchElementModel == null) + { + return; + } + + var installedPackages = PackageManagerClientViewModel.PackageManagerExtension.PackageLoader.LocalPackages + .Where(x => (x.Name == element.SearchElementModel.Name) && !x.BuiltInPackage) + .ToList(); + + var installedVersion = installedPackages.Any() ? GetNewestInstalledVersion(installedPackages) : null; + element.UpdateInstalledVersion(installedVersion, setDefaultSelection); + element.HasUpdateAvailable = IsUpdateAvailable(installedVersion, element.VersionInformationList); + + var hasBlockingDownload = PackageManagerClientViewModel.Downloads.Any(handle => + handle.Name == element.SearchElementModel.Name && + (handle.DownloadState == PackageDownloadHandle.State.Downloaded || + handle.DownloadState == PackageDownloadHandle.State.Downloading || + handle.DownloadState == PackageDownloadHandle.State.Installing)); + if (hasBlockingDownload) + { + element.CanInstall = false; + return; + } + if (!installedPackages.Any()) + { + element.CanInstall = true; + return; + } + element.CanInstall = false; + } + + private void UninstallPackage(PackageManagerSearchElementViewModel element) + { + var installedPackage = GetInstalledPackageForSelectedVersion(element); + if (installedPackage == null) + { + return; + } + + var dynamoViewModel = PackageManagerClientViewModel?.DynamoViewModel; + if (dynamoViewModel == null) + { + return; + } + + if (installedPackage.LoadedAssemblies.Any()) + { + var message = string.Format(Resources.MessageNeedToRestartAfterDelete, + dynamoViewModel.BrandingResourceProvider.ProductName); + var title = Resources.MessageNeedToRestartAfterDeleteTitle; + var result = MessageBoxService.Show(dynamoViewModel.Owner, message, title, + MessageBoxButton.OKCancel, MessageBoxImage.Exclamation); + if (result == MessageBoxResult.Cancel || result == MessageBoxResult.None) + { + return; + } + } + + var confirmResult = MessageBoxService.Show(dynamoViewModel.Owner, + string.Format(Resources.MessageConfirmToDeletePackage, installedPackage.Name), + Resources.MessageNeedToRestartAfterDeleteTitle, + MessageBoxButton.YesNo, MessageBoxImage.Question); + + if (confirmResult == MessageBoxResult.No || confirmResult == MessageBoxResult.None) + { + return; + } + + try + { + installedPackage.UninstallCore(dynamoViewModel.Model.CustomNodeManager, + PackageManagerClientViewModel.PackageManagerExtension.PackageLoader, + dynamoViewModel.Model.PreferenceSettings); + } + catch (Exception ex) + { + var baseMessage = string.Format(Resources.MessageFailedToDelete, + dynamoViewModel.BrandingResourceProvider.ProductName); + var detailedMessage = baseMessage + Environment.NewLine + ex.Message; + + MessageBoxService.Show(dynamoViewModel.Owner, + detailedMessage, + Resources.DeleteFailureMessageBoxTitle, + MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + UpdateInstallState(element); + } + } + + private bool CanUninstallPackage(PackageManagerSearchElementViewModel element) + { + var installedPackage = GetInstalledPackageForSelectedVersion(element); + if (installedPackage == null) + { + return false; + } + + var dynamoModel = PackageManagerClientViewModel?.DynamoViewModel?.Model; + if (dynamoModel == null) + { + return false; + } + + if (!installedPackage.InUse(dynamoModel) || installedPackage.LoadedAssemblies.Any()) + { + return IsLoadedWithNoScheduledOperation(installedPackage); + } + + return false; + } + + private static bool IsLoadedWithNoScheduledOperation(Package package) + { + return package.BuiltInPackage + ? package.LoadState.State != PackageLoadState.StateTypes.Unloaded && + package.LoadState.ScheduledState != PackageLoadState.ScheduledTypes.ScheduledForUnload + : package.LoadState.ScheduledState != PackageLoadState.ScheduledTypes.ScheduledForDeletion; + } + + private Package GetInstalledPackageForSelectedVersion(PackageManagerSearchElementViewModel element) + { + if (element?.SelectedVersion?.IsInstalled != true || element.SearchElementModel == null) + { + return null; + } + + var installedPackages = PackageManagerClientViewModel.PackageManagerExtension.PackageLoader.LocalPackages + .Where(x => x.Name == element.SearchElementModel.Name && !x.BuiltInPackage) + .ToList(); + + if (!installedPackages.Any()) + { + return null; + } + + return installedPackages.FirstOrDefault(pkg => + string.Equals(pkg.VersionName, element.SelectedVersion.Version, StringComparison.OrdinalIgnoreCase)); + } + + private static string GetNewestInstalledVersion(IEnumerable installedPackages) + { + if (installedPackages == null) + { + return null; + } + + var parsedVersions = installedPackages + .Select(pkg => new { Version = pkg.VersionName, Parsed = VersionUtilities.Parse(pkg.VersionName) }) + .Where(x => x.Parsed != null) + .OrderBy(x => x.Parsed) + .LastOrDefault(); + + if (parsedVersions != null) + { + return parsedVersions.Version; + } + + return installedPackages.Select(pkg => pkg.VersionName).FirstOrDefault(); + } + + private static bool IsUpdateAvailable(string installedVersion, IEnumerable availableVersions) + { + if (string.IsNullOrEmpty(installedVersion) || availableVersions == null) + { + return false; + } + + var parsedInstalledVersion = VersionUtilities.Parse(installedVersion); + if (parsedInstalledVersion == null) + { + return false; + } + + var newestAvailableVersion = availableVersions + .Select(version => VersionUtilities.Parse(version.Version)) + .Where(parsedVersion => parsedVersion != null) + .OrderBy(parsedVersion => parsedVersion) + .LastOrDefault(); + + if (newestAvailableVersion == null) + { + return false; + } + + return newestAvailableVersion > parsedInstalledVersion; + } + + private PackageManagerSearchElementViewModel GetSearchElementViewModelByName(string name) + { + return SearchResults?.FirstOrDefault(x => x.SearchElementModel.Name == name) + ?? SearchMyResults?.FirstOrDefault(x => x.SearchElementModel.Name == name); + } + public PackageSearchState _searchState; // TODO: Set private for 3.0. /// @@ -724,6 +921,7 @@ private void PopulateMyPackages() var p = GetSearchElementViewModel(pkg, true); p.RequestDownload += this.PackageOnExecuted; + p.RequestUninstall += SearchElementViewModelOnRequestUninstall; p.IsOnwer = true; myPackages.Add(p); @@ -739,6 +937,8 @@ private void ClearMySearchResults() { ele.RequestDownload -= PackageOnExecuted; ele.RequestShowFileDialog -= OnRequestShowFileDialog; + ele.PropertyChanged -= SearchElementViewModelOnPropertyChanged; + ele.RequestUninstall -= SearchElementViewModelOnRequestUninstall; } this.SearchMyResults = null; @@ -1239,6 +1439,8 @@ internal void AddToSearchResults(PackageManagerSearchElementViewModel element) { element.RequestDownload += this.PackageOnExecuted; element.RequestShowFileDialog += this.OnRequestShowFileDialog; + element.PropertyChanged += SearchElementViewModelOnPropertyChanged; + element.RequestUninstall += SearchElementViewModelOnRequestUninstall; this.SearchResults.Add(element); } @@ -1250,10 +1452,26 @@ internal void ClearSearchResults() { ele.RequestDownload -= PackageOnExecuted; ele.RequestShowFileDialog -= OnRequestShowFileDialog; + ele.PropertyChanged -= SearchElementViewModelOnPropertyChanged; + ele.RequestUninstall -= SearchElementViewModelOnRequestUninstall; + ele?.Dispose(); } this.SearchResults.Clear(); } + private void SearchElementViewModelOnRequestUninstall(PackageManagerSearchElementViewModel element) + { + UninstallPackage(element); + } + + private void SearchElementViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (sender is PackageManagerSearchElementViewModel element && + e.PropertyName == nameof(PackageManagerSearchElementViewModel.SelectedVersion)) + { + UpdateInstallState(element); + } + } internal void PackageOnExecuted(PackageManagerSearchElement element, PackageVersion version, string downloadPath) { @@ -1297,10 +1515,9 @@ void canInstallHandler(object o, PropertyChangedEventArgs eArgs) // the Downloads collection before Download/Install begins. if (eArgs.PropertyName == nameof(PackageDownloadHandle.DownloadState)) { - PackageManagerSearchElementViewModel sr = SearchResults.FirstOrDefault(x => x.SearchElementModel.Name == handle.Name); - if (sr == null) return; - - sr.CanInstall = CanInstallPackage(o as PackageDownloadHandle); + var searchElement = GetSearchElementViewModelByName(handle.Name); + if (searchElement == null) return; + UpdateInstallState(searchElement); } } @@ -1649,10 +1866,15 @@ private PackageManagerSearchElementViewModel GetViewModelForPackageSearchElement private PackageManagerSearchElementViewModel GetSearchElementViewModel(PackageManagerSearchElement package, bool bypassCustomPackageLocations = false) { var isEnabledForInstall = bypassCustomPackageLocations || !(Preferences as IDisablePackageLoadingPreferences).DisableCustomPackageLocations; - return new PackageManagerSearchElementViewModel(package, + var viewModel = new PackageManagerSearchElementViewModel(package, PackageManagerClientViewModel.AuthenticationManager.HasAuthProvider, CanInstallPackage(package.Name), isEnabledForInstall); + + viewModel.CanUninstall = () => CanUninstallPackage(viewModel); + UpdateInstallState(viewModel, true); + + return viewModel; } /// diff --git a/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerPackagesControl.xaml b/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerPackagesControl.xaml index 91f81833522..55c11334742 100644 --- a/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerPackagesControl.xaml +++ b/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerPackagesControl.xaml @@ -116,6 +116,25 @@ + + + + + @@ -306,13 +325,15 @@ public bool? IsCompatible { get; set; } + /// + /// Indicates whether this version is currently installed locally. + /// + public bool IsInstalled { get; set; } + /// /// A helper method to determine the compatibility of a specific package version from the provided version details. ///