diff --git a/DeliCounter/Backend/ModManagement.cs b/DeliCounter/Backend/ModManagement.cs index 407f84a..a245651 100644 --- a/DeliCounter/Backend/ModManagement.cs +++ b/DeliCounter/Backend/ModManagement.cs @@ -16,11 +16,26 @@ internal static class ModManagement internal static void InstallMod(Mod mod, Version versionNumber) { App.RunInBackgroundThread(() => { ExecuteOperations(EnumerateInstallDependencies(mod, versionNumber).Concat(new[] { new InstallModOperation(mod, versionNumber) })); }); + } + + internal static void InstallMods(IEnumerable mods) + { + App.RunInBackgroundThread(() => { + IEnumerable enumerable = Array.Empty(); + foreach (Mod mod in mods) + enumerable = enumerable.Concat(EnumerateInstallDependencies(mod, mod.LatestVersion).Concat(new[] { new InstallModOperation(mod, mod.LatestVersion) })); + ExecuteOperations(enumerable); + }); } - internal static void UninstallMod(Mod mod) + internal static void UninstallMods(IEnumerable mods) { - App.RunInBackgroundThread(() => { ExecuteOperations(EnumerateUninstallDependencies(mod).Concat(new[] { new UninstallModOperation(mod) })); }); + App.RunInBackgroundThread(() => { + IEnumerable enumerable = Array.Empty(); + foreach (Mod mod in mods) + enumerable = enumerable.Concat(EnumerateUninstallDependencies(mod).Concat(new[] { new UninstallModOperation(mod) })); + ExecuteOperations(enumerable); + }); } internal static void UpdateMod(Mod mod, Version versionNumber) @@ -61,6 +76,11 @@ internal static void UpdateMod(Mod mod, Version versionNumber) dialogue.ShowAsync(); }); }); + } + + internal static void UpdateMods(IEnumerable mods) + { + foreach (Mod mod in mods) UpdateMod(mod, mod.LatestVersion); } internal static void DefaultAction(Mod mod) diff --git a/DeliCounter/Backend/ModRepository.cs b/DeliCounter/Backend/ModRepository.cs index c444084..97887d7 100644 --- a/DeliCounter/Backend/ModRepository.cs +++ b/DeliCounter/Backend/ModRepository.cs @@ -73,7 +73,7 @@ public void Refresh() LoadModCache(); Exception = updateResult; RepositoryUpdated?.Invoke(); - App.RunInMainThread(() => MainWindow.Instance.ModManagementDrawer.SetMod(null)); + App.RunInMainThread(MainWindow.Instance.ModManagementDrawer.ClearSelected); } /// diff --git a/DeliCounter/Controls/ModManagementDrawer.xaml.cs b/DeliCounter/Controls/ModManagementDrawer.xaml.cs index df38ede..90b756c 100644 --- a/DeliCounter/Controls/ModManagementDrawer.xaml.cs +++ b/DeliCounter/Controls/ModManagementDrawer.xaml.cs @@ -1,6 +1,8 @@ using DeliCounter.Backend; using ModernWpf.Controls; using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Windows; @@ -16,7 +18,7 @@ namespace DeliCounter.Controls /// public partial class ModManagementDrawer : UserControl { - private Mod _selectedMod; + private List _selectedMods = new(); private Version _selectedVersion; public ModManagementDrawer() @@ -25,30 +27,62 @@ public ModManagementDrawer() UpdateDisplay(); } - public void SetMod(Mod mod) + public void ClearSelected() { - _selectedMod = mod; - if (mod is not null) + _selectedMods.Clear(); + SelectedUpdated(); + } + + public void AddSelected(IList selected) + { + foreach (ModListItem item in selected) + _selectedMods.Add(item.Mod); + } + + public void RemoveSelected(IList selected) + { + foreach (ModListItem item in selected) + _selectedMods.Remove(item.Mod); + } + + public void SelectedUpdated() + { + if (_selectedMods.Count == 1) { - _selectedVersion = mod.InstalledVersion ?? mod.LatestVersion; - ComboBoxVersion.IsEnabled = _selectedMod.Versions.Count > 1; - ComboBoxVersion.Visibility = Visibility.Visible; - ComboBoxVersion.ItemsSource = _selectedMod.Versions.Keys.OrderByDescending(x => x); - _selectedVersion = _selectedMod.Versions.Keys.Max(); - ComboBoxVersion.SelectedItem = _selectedVersion; + Mod mod = _selectedMods[0]; + if (mod is not null) + { + _selectedVersion = mod.InstalledVersion ?? mod.LatestVersion; + ComboBoxVersion.IsEnabled = mod.Versions.Count > 1; + ComboBoxVersion.Visibility = Visibility.Visible; + ComboBoxVersion.ItemsSource = mod.Versions.Keys.OrderByDescending(x => x); + _selectedVersion = mod.Versions.Keys.Max(); + ComboBoxVersion.SelectedItem = _selectedVersion; + } } + UpdateDisplay(); } public void UpdateDisplay() { - if (_selectedMod == null) + if (_selectedMods.Count == 0) { UpdateShowNone(); return; + } else if (_selectedMods.Count > 1) + { + UpdateShowMany(); + return; } - Mod.ModVersion version = _selectedMod.Versions[_selectedVersion ?? _selectedMod.LatestVersion]; + Mod mod = _selectedMods[0]; + Mod.ModVersion version = mod.Versions[_selectedVersion ?? mod.LatestVersion]; + + // Reset the button contents + ButtonInstall.Content = "Install"; + ButtonUpdate.Content = "Update"; + ButtonUninstall.Content = "Uninstall"; // Update text block visibility TextBlockDescriptionWrapper.Visibility = Visibility.Visible; @@ -62,21 +96,21 @@ public void UpdateDisplay() TextBlockTitle.Text = version.Name; TextBlockDescription.Text = version.Description; TextBlockAuthors.Text = string.Join(", ", version.Authors); - TextBlockLatest.Text = _selectedMod.LatestVersion.ToString(); - TextBlockInstalled.Text = _selectedMod.IsInstalled ? _selectedMod.Installed?.VersionNumber?.ToString() ?? "0.0.0" : "No"; + TextBlockLatest.Text = mod.LatestVersion.ToString(); + TextBlockInstalled.Text = mod.IsInstalled ? mod.Installed?.VersionNumber?.ToString() ?? "0.0.0" : "No"; TextBlockDependencies.Text = string.Join(", ", version.Dependencies.Select(x => x.Key + " " + x.Value)); TextBlockSource.Text = version.SourceUrl; HyperlinkSource.NavigateUri = !string.IsNullOrEmpty(version.SourceUrl) ? new Uri(version.SourceUrl, UriKind.Absolute) : null; // Update the action button visibility - if (_selectedMod.IsInstalled) + if (mod.IsInstalled) { ButtonInstall.IsEnabled = false; ButtonInstall.Visibility = Visibility.Collapsed; ButtonUninstall.IsEnabled = true; ButtonUninstall.Visibility = Visibility.Visible; - ButtonUpdate.IsEnabled = !_selectedMod.UpToDate; - ButtonUpdate.Visibility = _selectedMod.UpToDate ? Visibility.Collapsed : Visibility.Visible; + ButtonUpdate.IsEnabled = !mod.UpToDate; + ButtonUpdate.Visibility = mod.UpToDate ? Visibility.Collapsed : Visibility.Visible; } else { @@ -125,7 +159,46 @@ private void UpdateShowNone() // Combobox ComboBoxVersion.IsEnabled = false; - ComboBoxVersion.Visibility = Visibility.Collapsed; + ComboBoxVersion.Visibility = Visibility.Collapsed; + ComboBoxVersion.ItemsSource = null; + + // Preview Image + ModPreviewImage.Visibility = Visibility.Collapsed; + } + + private void UpdateShowMany() + { + // Hide all the text blocks + TextBlockTitle.Text = $"{_selectedMods.Count} mods selected"; + TextBlockDescriptionWrapper.Visibility = Visibility.Collapsed; + TextBlockLatestWrapper.Visibility = Visibility.Collapsed; + TextBlockInstalledWrapper.Visibility = Visibility.Collapsed; + TextBlockAuthorsWrapper.Visibility = Visibility.Collapsed; + TextBlockDependenciesWrapper.Visibility = Visibility.Collapsed; + TextBlockSourceWrapper.Visibility = Visibility.Collapsed; + + // Counts + int installable = _selectedMods.Count(x => !x.IsInstalled); + int updatable = _selectedMods.Count(x => !x.UpToDate && x.IsInstalled); + int removable = _selectedMods.Count(x => x.IsInstalled); + + // Button text + ButtonInstall.Content = $"Install {installable}"; + ButtonUpdate.Content = $"Update {updatable}"; + ButtonUninstall.Content = $"Uninstall {removable}"; + + // Enable and make the buttons visible + ButtonInstall.IsEnabled = installable > 0; + ButtonInstall.Visibility = installable > 0 ? Visibility.Visible : Visibility.Collapsed; + ButtonUpdate.IsEnabled = updatable > 0; + ButtonUpdate.Visibility = updatable > 0 ? Visibility.Visible : Visibility.Collapsed; + ButtonUninstall.IsEnabled = removable > 0; + ButtonUninstall.Visibility = removable > 0 ? Visibility.Visible : Visibility.Collapsed; + + // Combobox + ComboBoxVersion.IsEnabled = false; + ComboBoxVersion.Visibility = Visibility.Collapsed; + ComboBoxVersion.ItemsSource = null; // Preview Image ModPreviewImage.Visibility = Visibility.Collapsed; @@ -142,19 +215,20 @@ private void HyperlinkSource_OnRequestNavigate(object sender, RequestNavigateEve } private void ButtonInstall_Click(object sender, RoutedEventArgs e) - { - ModManagement.InstallMod(_selectedMod, _selectedVersion); + { + if (_selectedMods.Count == 1) + ModManagement.InstallMod(_selectedMods[0], _selectedVersion); + else + ModManagement.InstallMods(_selectedMods); } private async void ButtonUninstall_Click(object sender, RoutedEventArgs e) - { - var mod = _selectedMod; - - var dependentCount = mod.InstalledDependents.Count(); + { + int dependentCount = _selectedMods.SelectMany(x => x.InstalledDependents).Distinct().Count(); if (dependentCount > 0) { // Confirm they want to do this - var alert = new AlertDialogue("Are you sure?", $"You have {dependentCount} mod(s) installed which directly depend on this one, they will also be uninstalled. This operation is cascading.") + var alert = new AlertDialogue("Are you sure?", $"Removing the selected mods will also remove their dependencies! This will uninstall {dependentCount + 1} mods.") { DefaultButton = ContentDialogButton.Primary, PrimaryButtonText = "Ok", @@ -165,12 +239,15 @@ private async void ButtonUninstall_Click(object sender, RoutedEventArgs e) } // Uninstall - ModManagement.UninstallMod(mod); + ModManagement.UninstallMods(_selectedMods.Where(x => x.IsInstalled)); } private void ButtonUpdate_OnClick(object sender, RoutedEventArgs e) - { - ModManagement.UpdateMod(_selectedMod, _selectedVersion); + { + if (_selectedMods.Count == 1) + ModManagement.UpdateMod(_selectedMods[0], _selectedVersion); + else + ModManagement.UpdateMods(_selectedMods.Where(x => !x.UpToDate && x.IsInstalled)); } private void ComboBoxVersion_OnSelectionChanged(object sender, SelectionChangedEventArgs e) @@ -180,8 +257,9 @@ private void ComboBoxVersion_OnSelectionChanged(object sender, SelectionChangedE } private void UpdateUpdateButton() - { - var mod = _selectedMod; + { + if (_selectedMods.Count != 1) return; + var mod = _selectedMods[0]; if (!mod.IsInstalled) return; // Set the update button text to either update or downgrade depending on the selected version diff --git a/DeliCounter/MainWindow.xaml.cs b/DeliCounter/MainWindow.xaml.cs index 11edf50..31171ba 100644 --- a/DeliCounter/MainWindow.xaml.cs +++ b/DeliCounter/MainWindow.xaml.cs @@ -80,7 +80,8 @@ private void NavView_OnItemInvoked(NavigationView sender, NavigationViewItemInvo CurrentPage = tag; var page = _pages[tag].Item1; NavViewContent.Navigate(page); - Drawer.IsPaneOpen = _pages[tag].Item2; + Drawer.IsPaneOpen = _pages[tag].Item2; + ModManagementDrawer.ClearSelected(); } private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) diff --git a/DeliCounter/Pages/ModListing.xaml b/DeliCounter/Pages/ModListing.xaml index f18e1e4..4276251 100644 --- a/DeliCounter/Pages/ModListing.xaml +++ b/DeliCounter/Pages/ModListing.xaml @@ -16,7 +16,7 @@ x:Name="ModList" DockPanel.Dock="Bottom" BorderThickness="1" - SelectionMode="Single" + SelectionMode="Extended" BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" SelectionChanged="ModList_SelectionChanged"> diff --git a/DeliCounter/Pages/ModListing.xaml.cs b/DeliCounter/Pages/ModListing.xaml.cs index 5e25be1..d4b92ae 100644 --- a/DeliCounter/Pages/ModListing.xaml.cs +++ b/DeliCounter/Pages/ModListing.xaml.cs @@ -31,8 +31,10 @@ private void Update() private void ModList_SelectionChanged(object sender, SelectionChangedEventArgs e) { - var drawer = MainWindow.Instance.ModManagementDrawer; - drawer.SetMod(((ModListItem)ModList.SelectedItem)?.Mod); + var drawer = MainWindow.Instance.ModManagementDrawer; + drawer.AddSelected(e.AddedItems); + drawer.RemoveSelected(e.RemovedItems); + drawer.SelectedUpdated(); } private void ModItem_DoubleClick(object sender, MouseButtonEventArgs e) diff --git a/DeliCounter/Pages/SearchPage.xaml b/DeliCounter/Pages/SearchPage.xaml index d9d649d..16d60bd 100644 --- a/DeliCounter/Pages/SearchPage.xaml +++ b/DeliCounter/Pages/SearchPage.xaml @@ -17,7 +17,7 @@ x:Name="ModList" DockPanel.Dock="Bottom" BorderThickness="1" - SelectionMode="Single" + SelectionMode="Extended" BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" SelectionChanged="ModList_SelectionChanged"> diff --git a/DeliCounter/Pages/SearchPage.xaml.cs b/DeliCounter/Pages/SearchPage.xaml.cs index c1a2ab5..50bf03e 100644 --- a/DeliCounter/Pages/SearchPage.xaml.cs +++ b/DeliCounter/Pages/SearchPage.xaml.cs @@ -22,8 +22,10 @@ private void Instance_InstalledModsUpdated() private void ModList_SelectionChanged(object sender, SelectionChangedEventArgs e) { - var drawer = MainWindow.Instance.ModManagementDrawer; - drawer.SetMod(((ModListItem)ModList.SelectedItem)?.Mod); + var drawer = MainWindow.Instance.ModManagementDrawer; + drawer.AddSelected(e.AddedItems); + drawer.RemoveSelected(e.RemovedItems); + drawer.SelectedUpdated(); } private void TextBoxSearch_OnTextChanged(object sender, TextChangedEventArgs e)