diff --git a/src/IronyModManager.Shared/LocalizationResources.cs b/src/IronyModManager.Shared/LocalizationResources.cs index af7db1e9..e70209f9 100644 --- a/src/IronyModManager.Shared/LocalizationResources.cs +++ b/src/IronyModManager.Shared/LocalizationResources.cs @@ -839,6 +839,7 @@ public static class Mod_App_Actions public const string OpenInSteam = Prefix + "OpenInSteam"; public const string Open = Prefix + "Open"; public const string Copy = Prefix + "Copy"; + public const string CopyPath = Prefix + "CopyPath"; public const string OpenInAssociatedApp = Prefix + "OpenInAssociatedApp"; } public static class Descriptor_Actions diff --git a/src/IronyModManager/Localization/de.json b/src/IronyModManager/Localization/de.json index d1c49d07..09f19aef 100644 --- a/src/IronyModManager/Localization/de.json +++ b/src/IronyModManager/Localization/de.json @@ -627,6 +627,7 @@ "OpenInSteam": "In Steam Öffnen", "Open": "Mod-URL Öffnen", "Copy": "Mod-URL Kopieren", + "CopyPath": "Mod-Pfad kopieren", "OpenInAssociatedApp": "Mod-Ordner/Mod-Datei Öffnen" }, "Descriptor_Actions": { diff --git a/src/IronyModManager/Localization/en.json b/src/IronyModManager/Localization/en.json index 479fe1dc..027f2dbb 100644 --- a/src/IronyModManager/Localization/en.json +++ b/src/IronyModManager/Localization/en.json @@ -627,6 +627,7 @@ "OpenInSteam": "Open In Steam", "Open": "Open Mod Url", "Copy": "Copy Mod Url", + "CopyPath": "Copy Mod Path", "OpenInAssociatedApp": "Open Mod Folder/File" }, "Descriptor_Actions": { diff --git a/src/IronyModManager/Localization/es.json b/src/IronyModManager/Localization/es.json index dd415c7d..02a0c425 100644 --- a/src/IronyModManager/Localization/es.json +++ b/src/IronyModManager/Localization/es.json @@ -627,6 +627,7 @@ "OpenInSteam": "Abrir en Steam", "Open": "Abrir Mod Url", "Copy": "Copiar Mod Url", + "CopyPath": "Copiar Ruta Mod", "OpenInAssociatedApp": "Abrir Mod Folder/Archivo" }, "Descriptor_Actions": { diff --git a/src/IronyModManager/Localization/fr.json b/src/IronyModManager/Localization/fr.json index be2d8e9c..66d379fd 100644 --- a/src/IronyModManager/Localization/fr.json +++ b/src/IronyModManager/Localization/fr.json @@ -627,6 +627,7 @@ "OpenInSteam": "Ouvrir dans Steam", "Open": "Ouvrir l'URL du mod", "Copy": "Copier l'URL du mod", + "CopyPath": "Copier le chemin du module", "OpenInAssociatedApp": "Ouvrir le dossier/fichier de mod" }, "Descriptor_Actions": { diff --git a/src/IronyModManager/Localization/hr.json b/src/IronyModManager/Localization/hr.json index e13ef6d3..bb089968 100644 --- a/src/IronyModManager/Localization/hr.json +++ b/src/IronyModManager/Localization/hr.json @@ -627,6 +627,7 @@ "OpenInSteam": "Otvori u steamu", "Open": "Otvori adresu moda", "Copy": "Kopiraj adresu moda", + "CopyPath": "Kopiraj putanju moda", "OpenInAssociatedApp": "Otvorite Mod mapu/datoteku" }, "Descriptor_Actions": { diff --git a/src/IronyModManager/Localization/ru.json b/src/IronyModManager/Localization/ru.json index 15e50d0d..1398584d 100644 --- a/src/IronyModManager/Localization/ru.json +++ b/src/IronyModManager/Localization/ru.json @@ -627,6 +627,7 @@ "OpenInSteam": "Открыть в Steam", "Open": "Открыть ссылку на мод", "Copy": "Копировать ссылку на мод", + "CopyPath": "Копирование пути к модулю", "OpenInAssociatedApp": "Открыть папку/файл мода" }, "Descriptor_Actions": { diff --git a/src/IronyModManager/Localization/zh.json b/src/IronyModManager/Localization/zh.json index 2baa9298..9fea8174 100644 --- a/src/IronyModManager/Localization/zh.json +++ b/src/IronyModManager/Localization/zh.json @@ -627,6 +627,7 @@ "OpenInSteam": "在 Steam 中打开", "Open": "打开模组链接", "Copy": "复制模组链接", + "CopyPath": "复制模块路径", "OpenInAssociatedApp": "打开模组目录或文件" }, "Descriptor_Actions": { diff --git a/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs index 0c619376..0e6fa004 100644 --- a/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs @@ -1,5 +1,4 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 03-03-2020 @@ -12,6 +11,7 @@ // // // *********************************************************************** + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -47,7 +47,6 @@ namespace IronyModManager.ViewModels.Controls { - /// /// Class CollectionModsControlViewModel. /// Implements the @@ -176,7 +175,7 @@ public class CollectionModsControlViewModel : BaseViewModel /// /// The enable all toggled state /// - private bool enableAllToggledState = false; + private bool enableAllToggledState; /// /// The mod export progress @@ -196,7 +195,7 @@ public class CollectionModsControlViewModel : BaseViewModel /// /// The refresh in progress /// - private bool refreshInProgress = false; + private bool refreshInProgress; /// /// The reorder token @@ -211,17 +210,17 @@ public class CollectionModsControlViewModel : BaseViewModel /// /// The skip mod collection save /// - private bool skipModCollectionSave = false; + private bool skipModCollectionSave; /// /// The skip mod selection save /// - private bool skipModSelectionSave = false; + private bool skipModSelectionSave; /// /// The skip reorder /// - private bool skipReorder = false; + private bool skipReorder; #endregion Fields @@ -453,6 +452,23 @@ public enum ImportProviderType /// The context menu mod. public virtual IMod ContextMenuMod { get; set; } + /// + /// Gets or sets a value representing the copy mod path. + /// + /// + /// The copy mod path. + /// + [StaticLocalization(LocalizationResources.Mod_App_Actions.CopyPath)] + public virtual string CopyModPath { get; protected set; } + + /// + /// Gets or sets a value representing the copy mod path command. + /// + /// + /// The copy mod path command. + /// + public virtual ReactiveCommand CopyModPathCommand { get; protected set; } + /// /// Gets or sets the copy URL. /// @@ -845,6 +861,7 @@ public virtual string GetContextMenuModSteamUrl() var url = modService.BuildSteamUrl(ContextMenuMod); return url; } + return string.Empty; } @@ -859,6 +876,7 @@ public virtual string GetContextMenuModUrl() var url = modService.BuildModUrl(ContextMenuMod); return url; } + return string.Empty; } @@ -874,6 +892,7 @@ public virtual void HandleEnableAllToggled(bool toggledState, bool enabled, IEnu { skipModCollectionSave = true; } + if (!toggledState && enableAllToggledState) { skipModCollectionSave = false; @@ -905,6 +924,7 @@ public virtual void HandleEnableAllToggled(bool toggledState, bool enabled, IEnu SaveSelectedCollection(); RecognizeSortOrder(SelectedModCollection); } + enableAllToggledState = toggledState; } @@ -920,6 +940,7 @@ public virtual void HandleModRefresh(bool isRefreshing, IEnumerable mods, { refreshInProgress = true; } + if (!isRefreshing && mods?.Count() > 0) { SetMods(mods, activeGame); @@ -943,8 +964,10 @@ async Task reorder() { reorderQueue.Add(mod); } + await PerformModReorderAsync(true, reorderToken.Token); } + reorder().ConfigureAwait(false); } @@ -997,6 +1020,7 @@ public virtual void Reset(bool fullReset = false) { previousValidatedMods.Clear(); } + PatchMod.SetParameters(SelectedModCollection); HandleCollectionPatchStateAsync(SelectedModCollection?.Name).ConfigureAwait(false); } @@ -1035,9 +1059,6 @@ protected virtual void ApplySort() SelectedMod = null; SetSelectedModsState(SelectedMods.OrderByDescending(x => x.Name, StringComparer.OrdinalIgnoreCase).ToObservableCollection()); break; - - default: - break; } } @@ -1060,10 +1081,8 @@ protected virtual void AssignOptionalCollectionMetadata(IModCollection collectio case ModSource.Paradox: result.ParadoxId = p.RemoteId; break; - - default: - break; } + return result; }).ToList(); } @@ -1075,7 +1094,7 @@ protected virtual void AssignOptionalCollectionMetadata(IModCollection collectio protected virtual void EvalGameSpecificVisibility(IGame game = null) { game ??= gameService.GetSelected(); - ShowAdvancedFeatures = (game?.AdvancedFeatures) == GameAdvancedFeatures.Full; + ShowAdvancedFeatures = game?.AdvancedFeatures == GameAdvancedFeatures.Full; SearchModsColSpan = ShowAdvancedFeatures ? 1 : 2; } @@ -1096,10 +1115,7 @@ protected virtual void EvaluateHighlight() protected virtual async Task ExportCollectionAsync(string path, ImportProviderType providerType) { var id = idGenerator.GetNextId(); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new - { - PercentDone = 0.ToLocalizedPercentage() - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new { PercentDone = 0.ToLocalizedPercentage() }); await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Exporting_Message), overlayProgress); var collection = modCollectionService.Get(SelectedModCollection.Name); if (providerType == ImportProviderType.ParadoxLauncherJson) @@ -1115,15 +1131,14 @@ protected virtual async Task ExportCollectionAsync(string path, ImportProviderTy modExportProgress?.Dispose(); modExportProgress = modExportProgressHandler.Subscribe(s => { - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new - { - PercentDone = s.Progress.ToLocalizedPercentage() - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new { PercentDone = s.Progress.ToLocalizedPercentage() }); TriggerOverlay(id, true, localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Exporting_Message), overlayProgress); }).DisposeWith(Disposables); - await Task.Run(async () => await modCollectionService.ExportAsync(path, collection, providerType == ImportProviderType.DefaultOrderOnly, providerType == ImportProviderType.DefaultWithAllMods).ConfigureAwait(false)).ConfigureAwait(false); + await Task.Run(async () => await modCollectionService.ExportAsync(path, collection, providerType == ImportProviderType.DefaultOrderOnly, providerType == ImportProviderType.DefaultWithAllMods).ConfigureAwait(false)) + .ConfigureAwait(false); modExportProgress?.Dispose(); } + var title = localizationManager.GetResource(LocalizationResources.Notifications.CollectionExported.Title); var message = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Notifications.CollectionExported.Message), new { CollectionName = collection.Name }); notificationAction.ShowNotification(title, message, NotificationType.Success); @@ -1141,7 +1156,7 @@ protected virtual async Task HandleCollectionPatchStateAsync(string collection) if (activeGame != null && SelectedMods?.Count > 0) { ConflictSolverStateChanged?.Invoke(collection, true); - if (!string.IsNullOrWhiteSpace(collection) && currentCollection.Equals(collection, StringComparison.OrdinalIgnoreCase) && SelectedModCollection.Game.Equals(activeGame.Type)) + if (!string.IsNullOrWhiteSpace(collection) && currentCollection.Equals(collection, StringComparison.OrdinalIgnoreCase) && SelectedModCollection!.Game.Equals(activeGame.Type)) { var result = await Task.Run(async () => await modPatchCollectionService.PatchModNeedsUpdateAsync(collection, SelectedMods.Select(p => p.DescriptorFile).ToList())); ConflictSolverStateChanged?.Invoke(collection, !result); @@ -1171,8 +1186,10 @@ protected virtual void HandleModCollectionChange(bool resetStack) SelectedModCollection = restored; } } + restoreCollectionSelection = string.Empty; } + skipModCollectionSave = true; skipModSelectionSave = true; ExportCollection.CollectionName = SelectedModCollection?.Name; @@ -1184,6 +1201,7 @@ protected virtual void HandleModCollectionChange(bool resetStack) item.IsSelected = false; } } + var existingCollection = modCollectionService.Get(SelectedModCollection?.Name ?? string.Empty); var selectedMods = new ObservableCollection(); if (existingCollection?.Mods?.Count() > 0 && localMods != null) @@ -1193,7 +1211,7 @@ protected virtual void HandleModCollectionChange(bool resetStack) var mods = existingCollection.Mods.ToList(); var modPaths = existingCollection.ModPaths != null ? existingCollection.ModPaths.ToList() : []; var modNames = hasModNames ? existingCollection.ModNames.ToList() : []; - for (int i = 0; i < mods.Count; i++) + for (var i = 0; i < mods.Count; i++) { var item = mods[i]; var mod = localMods.FirstOrDefault(p => p.DescriptorFile.Equals(item, StringComparison.InvariantCultureIgnoreCase)); @@ -1202,6 +1220,7 @@ protected virtual void HandleModCollectionChange(bool resetStack) item = modPaths[i]; mod = localMods.FirstOrDefault(p => p.FullPath.Equals(item, StringComparison.OrdinalIgnoreCase)); } + if (mod != null) { mod.IsSelected = true; @@ -1219,6 +1238,7 @@ protected virtual void HandleModCollectionChange(bool resetStack) } } } + if (missingMods.Count != 0 && activeGame != null && existingCollection.Game.Equals(activeGame.Type)) { var title = localizationManager.GetResource(LocalizationResources.Collection_Mods.Prompts.ModsMissingTitle); @@ -1226,11 +1246,13 @@ protected virtual void HandleModCollectionChange(bool resetStack) Dispatcher.UIThread.SafeInvoke(() => notificationAction.ShowPromptAsync(title, title, message, NotificationType.Warning, PromptType.OK)); } } + if (resetStack) { undoStack.Clear(); redoStack.Clear(); } + SetSelectedModsState(selectedMods, ignoreStack: true); AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); var state = appStateService.Get(); @@ -1250,15 +1272,13 @@ protected virtual void HandleModCollectionChange(bool resetStack) protected virtual async Task ImportCollectionAsync(string path, ImportProviderType type) { List modNames = null; + async Task importDefault(long messageId) { modExportProgress?.Dispose(); modExportProgress = modExportProgressHandler.Subscribe(s => { - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new - { - PercentDone = s.Progress.ToLocalizedPercentage() - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new { PercentDone = s.Progress.ToLocalizedPercentage() }); TriggerOverlay(messageId, true, localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Importing_Message), overlayProgress); }).DisposeWith(Disposables); var collection = await Task.Run(async () => await modCollectionService.ImportAsync(path)); @@ -1272,6 +1292,7 @@ async Task importDefault(long messageId) return collection; } } + return null; } @@ -1286,14 +1307,12 @@ Task importInstance(IModCollection importData) return Task.FromResult(importData); } } + return Task.FromResult((IModCollection)null); } var id = idGenerator.GetNextId(); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new - { - PercentDone = 0.ToLocalizedPercentage() - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Import_Export_Progress), new { PercentDone = 0.ToLocalizedPercentage() }); await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Collection_Mods.Overlay_Importing_Message), overlayProgress); var importData = type switch { @@ -1302,13 +1321,14 @@ Task importInstance(IModCollection importData) ImportProviderType.ParadoxLauncher => await modCollectionService.ImportParadoxLauncherAsync(), ImportProviderType.ParadoxLauncherBeta => await modCollectionService.ImportParadoxLauncherBetaAsync(), ImportProviderType.ParadoxLauncherJson => await modCollectionService.ImportParadoxLauncherJsonAsync(path), - _ => await modCollectionService.GetImportedCollectionDetailsAsync(path), + _ => await modCollectionService.GetImportedCollectionDetailsAsync(path) }; if (importData == null) { await TriggerOverlayAsync(id, false); return; } + bool proceed; if (modCollectionService.Exists(importData.Name)) { @@ -1320,13 +1340,14 @@ Task importInstance(IModCollection importData) { proceed = true; } + var endOverlay = true; if (proceed) { var result = type switch { ImportProviderType.Default => await importDefault(id), - _ => await importInstance(importData), + _ => await importInstance(importData) }; if (result != null) { @@ -1340,11 +1361,11 @@ Task importInstance(IModCollection importData) var importedMods = new List(); var descriptors = result.Mods.ToList(); var paths = result.ModPaths != null ? result.ModPaths.ToList() : []; - for (int i = 0; i < descriptors.Count; i++) + for (var i = 0; i < descriptors.Count; i++) { var descriptor = descriptors[i]; var name = modNames[i]; - var mod = mods.FirstOrDefault(p => p.Name.Equals(name) && System.IO.Path.GetDirectoryName(p.FullPath).EndsWith(System.IO.Path.DirectorySeparatorChar + result.MergedFolderName)); + var mod = mods.FirstOrDefault(p => p.Name.Equals(name) && System.IO.Path.GetDirectoryName(p.FullPath)!.EndsWith(System.IO.Path.DirectorySeparatorChar + result.MergedFolderName)); if (mod != null) { importedMods.Add(mod.DescriptorFile); @@ -1363,9 +1384,11 @@ Task importInstance(IModCollection importData) } } } + result.Mods = importedMods; modCollectionService.Save(result); } + var modDescriptorPaths = result.Mods != null ? result.Mods.ToList() : []; var modPaths = result.ModPaths != null ? result.ModPaths.ToList() : []; restoreCollectionSelection = result.Name; @@ -1392,6 +1415,7 @@ Task importInstance(IModCollection importData) isInvalid = false; } } + if (isInvalid) { if (hasModNames) @@ -1404,10 +1428,12 @@ Task importInstance(IModCollection importData) } } } + if (nonExistingModNames.Count != 0) { var notExistingModTitle = localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Title); - var nonExistingModMessage = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Message), new { Environment.NewLine, Mods = string.Join(Environment.NewLine, nonExistingModNames) }); + var nonExistingModMessage = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Message), + new { Environment.NewLine, Mods = string.Join(Environment.NewLine, nonExistingModNames) }); endOverlay = false; showImportNotification = false; var title = localizationManager.GetResource(LocalizationResources.Notifications.CollectionImported.Title); @@ -1418,6 +1444,7 @@ Task importInstance(IModCollection importData) } } } + if (showImportNotification) { var title = localizationManager.GetResource(LocalizationResources.Notifications.CollectionImported.Title); @@ -1426,6 +1453,7 @@ Task importInstance(IModCollection importData) } } } + if (endOverlay) { await TriggerOverlayAsync(id, false); @@ -1443,14 +1471,16 @@ protected virtual void InitSortersAndFilters(IAppState state, bool setFlag = tru { skipModSelectionSave = true; } + CollectionJumpOnPositionChange = state.CollectionJumpOnPositionChange; SearchMods.WatermarkText = SearchModsWatermark; - SearchMods.Text = state?.CollectionModsSearchTerm; + SearchMods.Text = state.CollectionModsSearchTerm; ModNameSortOrder.Text = ModName; if (!string.IsNullOrWhiteSpace(state.CollectionModsSelectedMod) && SelectedMods != null) { SelectedMod = SelectedMods.FirstOrDefault(p => p.DescriptorFile.Equals(state.CollectionModsSelectedMod, StringComparison.OrdinalIgnoreCase)); } + RecognizeSortOrder(SelectedModCollection); if (setFlag) { @@ -1522,14 +1552,12 @@ protected override void OnActivated(CompositeDisposable disposables) HandleModCollectionChange(true); }).DisposeWith(disposables); - this.WhenAnyValue(v => v.AddNewCollection.IsActivated).Where(p => p == true).Subscribe(activated => + // ReSharper disable once RedundantBoolCompare + this.WhenAnyValue(v => v.AddNewCollection.IsActivated).Where(p => p == true).Subscribe(_ => { - Observable.Merge(AddNewCollection.CreateCommand, AddNewCollection.CancelCommand.Select(_ => new CommandResult(string.Empty, CommandState.NotExecuted))).Subscribe(result => + AddNewCollection.CreateCommand.Merge(AddNewCollection.CancelCommand.Select(_ => new CommandResult(string.Empty, CommandState.NotExecuted))).Subscribe(result => { - var notification = new - { - CollectionName = result.Result - }; + var notification = new { CollectionName = result.Result }; switch (result.State) { case CommandState.Success: @@ -1543,6 +1571,7 @@ protected override void OnActivated(CompositeDisposable disposables) mod.IsSelected = false; } } + SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : []); AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); LoadModCollections(); @@ -1565,6 +1594,7 @@ await Task.Run(async () => notificationAction.ShowNotification(successTitle, successMessage, NotificationType.Success); PatchMod.SetParameters(SelectedModCollection); } + handleRenamePatchCollection().ConfigureAwait(true); } else @@ -1574,6 +1604,7 @@ await Task.Run(async () => successMessage = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Notifications.CollectionCreated.Message), notification); notificationAction.ShowNotification(successTitle, successMessage, NotificationType.Success); } + break; case CommandState.Exists: @@ -1586,43 +1617,40 @@ await Task.Run(async () => MessageBus.Publish(new AllowEnterHotKeysEvent(false)); EnteringNewCollection = false; break; - - default: - break; } }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(v => v.ExportCollection.IsActivated).Where(p => p).Subscribe(activated => + this.WhenAnyValue(v => v.ExportCollection.IsActivated).Where(p => p).Subscribe(_ => { Observable.Merge(ExportCollection.ExportCommand.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.Default)), - ExportCollection.ExportOrderOnlyCommand.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.DefaultOrderOnly)), - ExportCollection.ExportWholeCollectionCommand.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.DefaultWithAllMods)), - ExportCollection.ImportCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.Default)), - ExportCollection.ImportOtherParadoxosCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.Paradoxos)), - ExportCollection.ImportOtherParadoxCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.Paradox)), - ExportCollection.ImportOtherParadoxLauncherCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.ParadoxLauncher)), - ExportCollection.ImportOtherParadoxLauncherBetaCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.ParadoxLauncherBeta)), - ExportCollection.ImportOtherParadoxLauncherJsonCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.ParadoxLauncherJson)), - ExportCollection.ExportParadoxLauncherJsonCommand.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.ParadoxLauncherJson)), - ExportCollection.ExportParadoxLauncherJson202110Command.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.ParadoxLauncherJson202110))) - .Subscribe(s => - { - if (s.Item2.State == CommandState.Success) + ExportCollection.ExportOrderOnlyCommand.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.DefaultOrderOnly)), + ExportCollection.ExportWholeCollectionCommand.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.DefaultWithAllMods)), + ExportCollection.ImportCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.Default)), + ExportCollection.ImportOtherParadoxosCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.Paradoxos)), + ExportCollection.ImportOtherParadoxCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.Paradox)), + ExportCollection.ImportOtherParadoxLauncherCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.ParadoxLauncher)), + ExportCollection.ImportOtherParadoxLauncherBetaCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.ParadoxLauncherBeta)), + ExportCollection.ImportOtherParadoxLauncherJsonCommand.Select(p => Tuple.Create(ImportActionType.Import, p, ImportProviderType.ParadoxLauncherJson)), + ExportCollection.ExportParadoxLauncherJsonCommand.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.ParadoxLauncherJson)), + ExportCollection.ExportParadoxLauncherJson202110Command.Select(p => Tuple.Create(ImportActionType.Export, p, ImportProviderType.ParadoxLauncherJson202110))) + .Subscribe(s => { - if (s.Item1 == ImportActionType.Export) + if (s.Item2.State == CommandState.Success) { - ExportCollectionAsync(s.Item2.Result, s.Item3).ConfigureAwait(true); - } - else - { - ImportCollectionAsync(s.Item2.Result, s.Item3).ConfigureAwait(true); + if (s.Item1 == ImportActionType.Export) + { + ExportCollectionAsync(s.Item2.Result, s.Item3).ConfigureAwait(true); + } + else + { + ImportCollectionAsync(s.Item2.Result, s.Item3).ConfigureAwait(true); + } } - } - }).DisposeWith(disposables); + }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(v => v.ModifyCollection.IsActivated).Where(p => p).Subscribe(activated => + this.WhenAnyValue(v => v.ModifyCollection.IsActivated).Where(p => p).Subscribe(_ => { Observable.Merge(ModifyCollection.RenameCommand, ModifyCollection.DuplicateCommand, ModifyCollection.MergeBasicCommand, ModifyCollection.MergeCompressCommand).Subscribe(s => { @@ -1630,6 +1658,7 @@ await Task.Run(async () => { return; } + if (s.Result == ModifyAction.Rename) { EnteringNewCollection = true; @@ -1643,7 +1672,7 @@ await Task.Run(async () => { ModCollections = modCollectionService.GetAll(); var selected = ModCollections?.FirstOrDefault(p => p.IsSelected); - restoreCollectionSelection = selected.Name; + restoreCollectionSelection = selected!.Name; NeedsModListRefresh = true; var existsTitle = localizationManager.GetResource(LocalizationResources.Notifications.CollectionMerged.Title); var existsMessage = localizationManager.GetResource(LocalizationResources.Notifications.CollectionMerged.Message); @@ -1663,6 +1692,7 @@ await Task.Run(async () => mod.IsSelected = false; } } + SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : []); AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); LoadModCollections(); @@ -1677,7 +1707,7 @@ await Task.Run(async () => }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(s => s.SearchMods.IsActivated).Where(p => p).Subscribe(s => + this.WhenAnyValue(s => s.SearchMods.IsActivated).Where(p => p).Subscribe(_ => { this.WhenAnyValue(s => s.SearchMods.Text).Subscribe(async s => { @@ -1688,8 +1718,10 @@ await Task.Run(async () => { await MessageBus.PublishAsync(new EvalModAchievementsCompatibilityEvent(Mods, true, true)); } + SelectedMod = modService.FindMod(SelectedMods, s, false, null); } + var modCount = SelectedModCollection?.Mods.Count(); var selectedModsCount = SelectedMods?.Count; @@ -1700,23 +1732,27 @@ await Task.Run(async () => { SelectedMod = null; } + SaveState(); } + skipModSelectionSave = false; }).DisposeWith(disposables); - Observable.Merge(SearchMods.DownArrowCommand, SearchMods.UpArrowCommand).Subscribe(async s => + SearchMods.DownArrowCommand.Merge(SearchMods.UpArrowCommand).Subscribe(async s => { if (SelectedMods == null) { return; } + skipModSelectionSave = true; var index = -1; if (SelectedMod != null) { index = SelectedMods.IndexOf(SelectedMod); } + var searchString = SearchMods.Text ?? string.Empty; if (!s.Result) { @@ -1724,6 +1760,7 @@ await Task.Run(async () => { await MessageBus.PublishAsync(new EvalModAchievementsCompatibilityEvent(Mods, true, true)); } + var mod = modService.FindMod(SelectedMods, searchString, false, index + 1); if (mod != null && mod != SelectedMod) { @@ -1737,20 +1774,23 @@ await Task.Run(async () => { await MessageBus.PublishAsync(new EvalModAchievementsCompatibilityEvent(Mods, true, true)); } + var mod = modService.FindMod(SelectedMods, searchString, true, reverseIndex); if (mod != null && mod != SelectedMod) { SelectedMod = mod; } } + skipModSelectionSave = false; SaveState(); }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(s => s.ModNameSortOrder.IsActivated).Where(p => p == true).Subscribe(s => + // ReSharper disable once RedundantBoolCompare + this.WhenAnyValue(s => s.ModNameSortOrder.IsActivated).Where(p => p == true).Subscribe(_ => { - ModNameSortOrder.SortCommand.Subscribe(s => + ModNameSortOrder.SortCommand.Subscribe(_ => { ApplySort(); SaveState(); @@ -1767,9 +1807,11 @@ await Task.Run(async () => { item.IsSelected = false; } + SetSelectedModsState([]); SaveSelectedCollection(); } + skipModCollectionSave = false; }).DisposeWith(disposables); @@ -1808,13 +1850,21 @@ await Task.Run(async () => } }).DisposeWith(disposables); + CopyModPathCommand = ReactiveCommand.Create(() => + { + if (!string.IsNullOrWhiteSpace(ContextMenuMod?.FullPath)) + { + appAction.CopyAsync(ContextMenuMod.FullPath).ConfigureAwait(true); + } + }).DisposeWith(disposables); + this.WhenAnyValue(p => p.AllowModSelection).Subscribe(s => { ExportCollection.AllowModSelection = s; ModifyCollection.AllowModSelection = s; }).DisposeWith(disposables); - this.WhenAnyValue(p => p.CollectionJumpOnPositionChange).Subscribe(s => + this.WhenAnyValue(p => p.CollectionJumpOnPositionChange).Subscribe(_ => { SetAutoFocusLabel(); }).DisposeWith(disposables); @@ -1859,6 +1909,7 @@ await Task.Run(async () => mods.Add(item); } } + SetSelectedModsState(mods.OrderBy(p => modNames.IndexOf(p.Name)).ToObservableCollection()); AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); var state = appStateService.Get(); @@ -1871,7 +1922,8 @@ await Task.Run(async () => if (nonExistingMods.Any()) { var notExistingModTitle = localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Title); - var nonExistingModMessage = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Message), new { Environment.NewLine, Mods = string.Join(Environment.NewLine, nonExistingMods) }); + var nonExistingModMessage = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Message), + new { Environment.NewLine, Mods = string.Join(Environment.NewLine, nonExistingMods) }); await notificationAction.ShowPromptAsync(notExistingModTitle, notExistingModTitle, nonExistingModMessage, NotificationType.Warning, PromptType.OK); } } @@ -1888,12 +1940,12 @@ void registerReportHandlers(long id, bool useImportOverlay = false) if (useImportOverlay) { TriggerOverlay(id, true, localizationManager.GetResource(LocalizationResources.Collection_Mods.FileHash.ImportOverlay), - IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.FileHash.ProgressImport), new { Progress = s.Percentage.ToLocalizedPercentage(), Count = s.Step, TotalCount = 2 })); + IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.FileHash.ProgressImport), new { Progress = s.Percentage.ToLocalizedPercentage(), Count = s.Step, TotalCount = 2 })); } else { TriggerOverlay(id, true, localizationManager.GetResource(LocalizationResources.Collection_Mods.FileHash.ExportOverlay), - IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.FileHash.ProgressExport), new { Progress = s.Percentage.ToLocalizedPercentage() })); + IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.FileHash.ProgressExport), new { Progress = s.Percentage.ToLocalizedPercentage() })); } }).DisposeWith(disposables); } @@ -1914,11 +1966,13 @@ void registerReportHandlers(long id, bool useImportOverlay = false) { reports.AddRange(collectionReports); } + var gameReports = await Task.Run(() => gameService.ImportHashReportAsync(activeGame, rawReports.ToList())); if (gameReports != null && gameReports.Any()) { reports.AddRange(gameReports); } + if (reports.Count != 0) { await TriggerOverlayAsync(id, false); @@ -1930,6 +1984,7 @@ void registerReportHandlers(long id, bool useImportOverlay = false) notificationAction.ShowNotification(localizationManager.GetResource(LocalizationResources.Notifications.ReportValid.Title), localizationManager.GetResource(LocalizationResources.Notifications.ReportValid.Message), NotificationType.Success); } + reportDisposable?.Dispose(); } }).DisposeWith(disposables); @@ -1948,6 +2003,7 @@ void registerReportHandlers(long id, bool useImportOverlay = false) notificationAction.ShowNotification(localizationManager.GetResource(LocalizationResources.Notifications.ReportExported.Title), localizationManager.GetResource(LocalizationResources.Notifications.ReportExported.Message), NotificationType.Success); } + await TriggerOverlayAsync(id, false); reportDisposable?.Dispose(); } @@ -1967,22 +2023,17 @@ void registerReportHandlers(long id, bool useImportOverlay = false) notificationAction.ShowNotification(localizationManager.GetResource(LocalizationResources.Notifications.ReportExported.Title), localizationManager.GetResource(LocalizationResources.Notifications.ReportExported.Message), NotificationType.Success); } + await TriggerOverlayAsync(id, false); reportDisposable?.Dispose(); } }).DisposeWith(disposables); - UndoCommand = ReactiveCommand.Create(() => - { - PerformUndo(); - }).DisposeWith(disposables); + UndoCommand = ReactiveCommand.Create(PerformUndo).DisposeWith(disposables); - RedoCommand = ReactiveCommand.Create(() => - { - PerformRedo(); - }).DisposeWith(disposables); + RedoCommand = ReactiveCommand.Create(PerformRedo).DisposeWith(disposables); - this.WhenAnyValue(p => p.SelectedMod).Where(p => !skipModSelectionSave).Subscribe(s => + this.WhenAnyValue(p => p.SelectedMod).Where(_ => !skipModSelectionSave).Subscribe(_ => { var modCount = SelectedModCollection?.Mods.Count(); var selectedModsCount = SelectedMods?.Count; @@ -2024,16 +2075,14 @@ void registerReportHandlers(long id, bool useImportOverlay = false) break; case Enums.HotKeys.Ctrl_Z: - Dispatcher.UIThread.SafeInvoke(() => PerformUndo()); + await Dispatcher.UIThread.SafeInvokeAsync(PerformUndo); break; case Enums.HotKeys.Ctrl_Y: - Dispatcher.UIThread.SafeInvoke(() => PerformRedo()); - break; - - default: + await Dispatcher.UIThread.SafeInvokeAsync(PerformRedo); break; } + if (!(order < 1 || order > MaxOrder) && order != mod.Order) { // Check access because it's probably coming from a background thread @@ -2042,9 +2091,11 @@ void registerReportHandlers(long id, bool useImportOverlay = false) } }).DisposeWith(disposables); - this.WhenAnyValue(p => p.PatchMod.IsActivated).Where(p => p == true).Subscribe(state => + // ReSharper disable once RedundantBoolCompare + this.WhenAnyValue(p => p.PatchMod.IsActivated).Where(p => p == true).Subscribe(_ => { - this.WhenAnyValue(p => p.PatchMod.PatchDeleted).Where(p => p == true).Subscribe(state => + // ReSharper disable once RedundantBoolCompare + this.WhenAnyValue(p => p.PatchMod.PatchDeleted).Where(p => p == true).Subscribe(_ => { modPatchCollectionService.InvalidatePatchModState(PatchMod.CollectionName); modPatchCollectionService.ResetPatchStateCache(); @@ -2077,6 +2128,7 @@ protected virtual async Task PerformModReorderAsync(bool instant, CancellationTo { await Task.Delay(450, cancellationToken); } + if (!cancellationToken.IsCancellationRequested) { if (SelectedMods != null) @@ -2097,24 +2149,27 @@ protected virtual async Task PerformModReorderAsync(bool instant, CancellationTo mods.Remove(modItem); mods.Insert(index, modItem); index = mods.IndexOf(swapItem); - for (int i = 0; i <= index; i++) + for (var i = 0; i <= index; i++) { - if (!reorderQueue.Any(m => m == mods[i].Mod)) + if (reorderQueue.All(m => m != mods[i].Mod)) { mods[i].Order = i + 1; } } } } + SetSelectedModsState(mods.Select(p => p.Mod).ToList()); if (CollectionJumpOnPositionChange) { SelectedMod = reorderQueue.Last(); } + if (!string.IsNullOrWhiteSpace(SelectedModCollection?.Name)) { SaveSelectedCollection(); } + SaveState(); RecognizeSortOrder(SelectedModCollection); ModReordered?.Invoke(reorderQueue.Last(), instant); @@ -2122,7 +2177,6 @@ protected virtual async Task PerformModReorderAsync(bool instant, CancellationTo reorderQueue.Clear(); scrollState.SetState(true); } - mutex.Dispose(); } } } @@ -2136,6 +2190,7 @@ protected virtual void PerformRedo() { return; } + undoStack.Push(SelectedMods.Select(p => p.DescriptorFile).ToList()); PerformRedoUndoOrdering(redoStack.Pop()); } @@ -2157,6 +2212,7 @@ protected virtual void PerformRedoUndoOrdering(IEnumerable descriptors) { item.IsSelected = false; } + var mods = new List(); foreach (var item in descriptors) { @@ -2167,11 +2223,13 @@ protected virtual void PerformRedoUndoOrdering(IEnumerable descriptors) mods.Add(mod); } } + SetSelectedModsState(mods, ignoreStack: true); if (!string.IsNullOrWhiteSpace(SelectedModCollection?.Name)) { SaveSelectedCollection(); } + SaveState(); RecognizeSortOrder(SelectedModCollection); AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); @@ -2190,6 +2248,7 @@ protected virtual void PerformUndo() { return; } + redoStack.Push(SelectedMods.Select(p => p.DescriptorFile).ToList()); PerformRedoUndoOrdering(undoStack.Pop()); } @@ -2206,7 +2265,7 @@ protected virtual void RecognizeSortOrder(IModCollection modCollection) var mods = new List(); var colMods = modCollection.Mods.ToList(); var colPaths = modCollection.ModPaths != null ? modCollection.ModPaths.ToList() : []; - for (int i = 0; i < colMods.Count; i++) + for (var i = 0; i < colMods.Count; i++) { var item = colMods[i]; var mod = Mods.FirstOrDefault(p => p.DescriptorFile.Equals(item, StringComparison.OrdinalIgnoreCase)); @@ -2215,11 +2274,13 @@ protected virtual void RecognizeSortOrder(IModCollection modCollection) item = colPaths[i]; mod = Mods.FirstOrDefault(p => p.FullPath.Equals(item, StringComparison.OrdinalIgnoreCase)); } + if (mod != null) { mods.Add(mod); } } + if (mods.Count != 0) { var ascending = mods.OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase).Select(p => p.DescriptorFile); @@ -2263,6 +2324,7 @@ protected async Task RemoveCollectionAsync(string collectionName) localizationManager.GetResource(LocalizationResources.Collection_Mods.DeleteMerge.DeleteHeader), localizationManager.GetResource(LocalizationResources.Collection_Mods.DeleteMerge.DeleteMessage), NotificationType.Info); } + if (modCollectionService.Delete(collectionName)) { modPatchCollectionService.InvalidatePatchModState(collectionName); @@ -2271,7 +2333,7 @@ await Task.Run(async () => await modPatchCollectionService.CleanPatchCollectionAsync(collectionName).ConfigureAwait(false); if (deleteMergedMod) { - await modService.PurgeModDirectoryAsync(collection.MergedFolderName).ConfigureAwait(false); + await modService.PurgeModDirectoryAsync(collection!.MergedFolderName).ConfigureAwait(false); } }).ConfigureAwait(false); LoadModCollections(); @@ -2290,6 +2352,7 @@ await Task.Run(async () => notificationAction.ShowNotification(notificationTitle, notificationMessage, NotificationType.Success); } } + await TriggerOverlayAsync(id, false); } } @@ -2309,6 +2372,7 @@ protected virtual void SaveSelectedCollection() { return; } + collection.Game = game; collection.Name = SelectedModCollection.Name; var selectedMods = SelectedMods != null ? SelectedMods.Where(p => p.IsSelected) : []; @@ -2353,6 +2417,7 @@ protected virtual async Task ScheduleToReorderQueueAsync(IMod mod) { reorderQueue.Add(mod); } + reorderToken?.Cancel(); reorderToken = new CancellationTokenSource(); await PerformModReorderAsync(false, reorderToken.Token); @@ -2381,7 +2446,8 @@ protected virtual void SetSelectedModsState(IList selectedMods, bool canSh { skipReorder = true; } - int counter = 0; + + var counter = 0; if (selectedMods != null) { foreach (var item in selectedMods) @@ -2390,6 +2456,7 @@ protected virtual void SetSelectedModsState(IList selectedMods, bool canSh item.Order = counter; } } + SelectedMods = selectedMods; if (SelectedModCollection != null) { @@ -2398,20 +2465,23 @@ protected virtual void SetSelectedModsState(IList selectedMods, bool canSh { oldMods.AddRange(SelectedMods); } + previousValidatedMods.TryGetValue(SelectedModCollection.Name, out var prevMods); if (SelectedMods?.Count > 0 && (prevMods == null || - !(prevMods.Count() == SelectedMods.Count && !prevMods.Select(p => p.DescriptorFile).Except(SelectedMods.Select(p => p.DescriptorFile)).Any()) - || !prevMods.Select(p => p.DescriptorFile).SequenceEqual(SelectedMods.Select(p => p.DescriptorFile)))) + !(prevMods.Count() == SelectedMods.Count && !prevMods.Select(p => p.DescriptorFile).Except(SelectedMods.Select(p => p.DescriptorFile)).Any()) + || !prevMods.Select(p => p.DescriptorFile).SequenceEqual(SelectedMods.Select(p => p.DescriptorFile)))) { modPatchCollectionService.InvalidatePatchModState(SelectedModCollection.Name); } - previousValidatedMods.AddOrUpdate(SelectedModCollection.Name, oldMods, (k, v) => oldMods); + + previousValidatedMods.AddOrUpdate(SelectedModCollection.Name, oldMods, (_, _) => oldMods); if (!ignoreStack && !prevMods.ListsSame(selectedMods)) { undoStack.Push((prevMods ?? []).Select(p => p.DescriptorFile).ToList()); redoStack.Clear(); } } + ModifyCollection.SelectedMods = selectedMods; HandleCollectionPatchStateAsync(SelectedModCollection?.Name).ConfigureAwait(false); var order = 1; @@ -2419,6 +2489,7 @@ protected virtual void SetSelectedModsState(IList selectedMods, bool canSh { order = SelectedMods.Count; } + MaxOrder = order; if (canShutdownReorder) { @@ -2457,6 +2528,7 @@ protected virtual void SubscribeToMods() { SaveState(); } + needsSave = true; } } @@ -2468,10 +2540,12 @@ protected virtual void SubscribeToMods() needsSave = true; } } + if (!string.IsNullOrWhiteSpace(SelectedModCollection?.Name) && needsSave) { SaveSelectedCollection(); } + Dispatcher.UIThread.SafeInvoke(() => { SetSelectedModsState(selectedMods, false); @@ -2479,9 +2553,11 @@ protected virtual void SubscribeToMods() { InstantReorderSelectedItems(s.Sender, s.Sender.Order); } + RecognizeSortOrder(SelectedModCollection); }); } + AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); skipReorder = false; }).DisposeWith(Disposables); @@ -2508,7 +2584,7 @@ private async Task ExportModsAsync(bool fullMetadata = false) var sb = new StringBuilder(); foreach (var item in SelectedMods) { - var entries = new List() { item.Name }; + var entries = new List { item.Name }; if (fullMetadata) { var version = item.Version; @@ -2516,14 +2592,17 @@ private async Task ExportModsAsync(bool fullMetadata = false) { entries.Add(version); } + var url = modService.BuildModUrl(item); if (!string.IsNullOrWhiteSpace(url)) { entries.Add(url); } } + sb.AppendLine(string.Join(ClipboardSeparator, entries)); } + await appAction.CopyAsync(sb.ToString()); } } diff --git a/src/IronyModManager/ViewModels/Controls/InstalledModsControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/InstalledModsControlViewModel.cs index f14e4963..9c59b2d3 100644 --- a/src/IronyModManager/ViewModels/Controls/InstalledModsControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/InstalledModsControlViewModel.cs @@ -4,7 +4,7 @@ // Created : 02-29-2020 // // Last Modified By : Mario -// Last Modified On : 01-11-2023 +// Last Modified On : 06-06-2024 // *********************************************************************** // // Mario @@ -117,7 +117,7 @@ public class InstalledModsControlViewModel : BaseViewModel /// /// The checking state /// - private bool checkingState = false; + private bool checkingState; /// /// The eval mod token @@ -132,12 +132,12 @@ public class InstalledModsControlViewModel : BaseViewModel /// /// The showing invalid notification /// - private bool showingInvalidNotification = false; + private bool showingInvalidNotification; /// /// The showing prompt /// - private bool showingPrompt = false; + private bool showingPrompt; /// /// The sort orders @@ -237,6 +237,19 @@ public InstalledModsControlViewModel(EvalModAchievementCompatibilityHandler eval /// The context menu mod. public virtual IMod ContextMenuMod { get; set; } + /// + /// Gets or sets a value representing the copy mod path. + /// + /// The copy mod path. + [StaticLocalization(LocalizationResources.Mod_App_Actions.CopyPath)] + public virtual string CopyModPath { get; protected set; } + + /// + /// Gets or sets a value representing the copy mod path command. + /// + /// The copy mod path command. + public virtual ReactiveCommand CopyModPathCommand { get; protected set; } + /// /// Gets or sets the copy URL. /// @@ -511,6 +524,7 @@ public virtual string GetContextMenuModModSteamUrl() var url = modService.BuildSteamUrl(ContextMenuMod); return url; } + return string.Empty; } @@ -525,6 +539,7 @@ public virtual string GetContextMenuModModUrl() var url = modService.BuildModUrl(ContextMenuMod); return url; } + return string.Empty; } @@ -564,6 +579,7 @@ public virtual async Task RefreshModsAsync(bool skipOverlay = false) } } } + RefreshingMods = false; } @@ -596,9 +612,6 @@ protected virtual void ApplySort(string sortBy) case ModVersionKey: SortFunction(x => x.VersionData, sortModel.Key); break; - - default: - break; } } @@ -627,6 +640,7 @@ protected virtual async Task BindAsync(IGame game = null, bool skipOverlay = fal { await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Installed_Mods.LoadingMods)); } + game ??= gameService.GetSelected(); ActiveGame = game; if (game != null) @@ -635,6 +649,7 @@ protected virtual async Task BindAsync(IGame game = null, bool skipOverlay = fal { GameChangedRefresh = true; } + var mods = await Task.Run(async () => await modService.GetInstalledModsAsync(game)); await Task.Delay(100); Mods = mods.ToObservableCollection(); @@ -647,11 +662,13 @@ await Dispatcher.UIThread.SafeInvokeAsync(async () => await RemoveInvalidModsPromptAsync(invalidMods).ConfigureAwait(false); }); } + var searchString = FilterMods.Text ?? string.Empty; if (Mods != null && Mods.Any(p => p.AchievementStatus == AchievementStatus.NotEvaluated) && modService.QueryContainsAchievements(searchString)) { await MessageBus.PublishAsync(new EvalModAchievementsCompatibilityEvent(Mods, skipOverlay, true)); } + FilteredMods = modService.FilterMods(Mods, searchString).ToObservableCollection(); AllModsEnabled = FilteredMods.Where(p => p.IsValid).Any() && FilteredMods.Where(p => p.IsValid).All(p => p.IsSelected); @@ -659,7 +676,7 @@ await Dispatcher.UIThread.SafeInvokeAsync(async () => { modChanged?.Dispose(); modChanged = null; - modChanged = Mods.ToSourceList().Connect().WhenPropertyChanged(s => s.IsSelected).Subscribe(s => + modChanged = Mods.ToSourceList().Connect().WhenPropertyChanged(s => s.IsSelected).Subscribe(_ => { if (!checkingState) { @@ -675,13 +692,10 @@ await Dispatcher.UIThread.SafeInvokeAsync(async () => } else { - if (raiseGameChanged) - { - GameChangedRefresh = true; - } Mods = FilteredMods = new System.Collections.ObjectModel.ObservableCollection(); AllMods = Mods.ToHashSet(); } + if (!skipOverlay) { await TriggerOverlayAsync(id, false); @@ -707,12 +721,13 @@ protected virtual async Task CheckModEnabledStateAsync() protected virtual async Task CheckNewModsAsync() { var id = idGenerator.GetNextId(); - await TriggerOverlayAsync(id, true, message: localizationManager.GetResource(LocalizationResources.Installed_Mods.RefreshingModList)); + await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Installed_Mods.RefreshingModList)); var result = await modService.InstallModsAsync(Mods); if (result != null && result.Any(p => p.Invalid)) { await ShowInvalidModsNotificationAsync(result.Where(p => p.Invalid).ToList()); } + await RefreshModsAsync(); var title = localizationManager.GetResource(LocalizationResources.Notifications.NewDescriptorsChecked.Title); var message = localizationManager.GetResource(LocalizationResources.Notifications.NewDescriptorsChecked.Message); @@ -730,13 +745,14 @@ protected virtual async Task DeleteDescriptorAsync(IEnumerable mods) if (mods?.Count() > 0) { var id = idGenerator.GetNextId(); - await TriggerOverlayAsync(id, true, message: localizationManager.GetResource(LocalizationResources.Installed_Mods.RefreshingModList)); + await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Installed_Mods.RefreshingModList)); await modService.DeleteDescriptorsAsync(mods); var result = await modService.InstallModsAsync(Mods); if (result != null && result.Any(p => p.Invalid)) { await ShowInvalidModsNotificationAsync(result.Where(p => p.Invalid).ToList()); } + await RefreshModsAsync(); var title = localizationManager.GetResource(LocalizationResources.Notifications.DescriptorsRefreshed.Title); var message = localizationManager.GetResource(LocalizationResources.Notifications.DescriptorsRefreshed.Message); @@ -759,6 +775,7 @@ async Task performEval(CancellationToken token) { await Task.Delay(100, token); } + if (!token.IsCancellationRequested || hasPriority) { if (evalModsQueue.Any()) @@ -770,6 +787,7 @@ async Task performEval(CancellationToken token) } } } + evalModToken?.Cancel(); evalModToken = new CancellationTokenSource(); if (mods.Any()) @@ -779,6 +797,7 @@ async Task performEval(CancellationToken token) { mutex = await evalLock.LockAsync(); } + mods.ToList().ForEach(m => evalModsQueue.Add(m)); try { @@ -787,6 +806,7 @@ async Task performEval(CancellationToken token) catch (OperationCanceledException) { } + mutex?.Dispose(); } } @@ -817,6 +837,7 @@ protected virtual void InitDefaultSortOrder(string dictKey, SortOrderControlView { vm.SortOrder = defaultOrder; } + vm.Text = text; sortOrders[dictKey] = vm; } @@ -865,15 +886,15 @@ protected override void OnActivated(CompositeDisposable disposables) Bind(); this.WhenAnyValue(v => v.ModNameSortOrder.IsActivated, v => v.ModVersionSortOrder.IsActivated, v => v.ModSelectedSortOrder.IsActivated).Where(s => s.Item1 && s.Item2 && s.Item3) - .Subscribe(s => - { - Observable.Merge(ModNameSortOrder.SortCommand.Select(_ => ModNameKey), - ModVersionSortOrder.SortCommand.Select(_ => ModVersionKey), - ModSelectedSortOrder.SortCommand.Select(_ => ModSelectedKey)).Subscribe(s => - { - ApplySort(s); - }).DisposeWith(disposables); - }).DisposeWith(disposables); + .Subscribe(_ => + { + Observable.Merge(ModNameSortOrder.SortCommand.Select(_ => ModNameKey), + ModVersionSortOrder.SortCommand.Select(_ => ModVersionKey), + ModSelectedSortOrder.SortCommand.Select(_ => ModSelectedKey)).Subscribe(s => + { + ApplySort(s); + }).DisposeWith(disposables); + }).DisposeWith(disposables); OpenUrlCommand = ReactiveCommand.CreateFromTask(async () => { @@ -910,13 +931,22 @@ protected override void OnActivated(CompositeDisposable disposables) } }).DisposeWith(disposables); - this.WhenAnyValue(s => s.FilterMods.Text).Subscribe(async s => + CopyModPathCommand = ReactiveCommand.Create(() => + { + if (!string.IsNullOrWhiteSpace(ContextMenuMod?.FullPath)) + { + appAction.CopyAsync(ContextMenuMod.FullPath).ConfigureAwait(true); + } + }).DisposeWith(disposables); + + this.WhenAnyValue(s => s.FilterMods.Text).Subscribe(async _ => { var searchString = FilterMods.Text ?? string.Empty; if (Mods != null && Mods.Any(p => p.AchievementStatus == AchievementStatus.NotEvaluated) && modService.QueryContainsAchievements(searchString)) { await MessageBus.PublishAsync(new EvalModAchievementsCompatibilityEvent(Mods, true, true)); } + FilteredMods = modService.FilterMods(Mods, searchString); AllModsEnabled = FilteredMods != null && FilteredMods.Where(p => p.IsValid).Any() && FilteredMods.Where(p => p.IsValid).All(p => p.IsSelected); ApplyDefaultSort(); @@ -928,11 +958,12 @@ protected override void OnActivated(CompositeDisposable disposables) if (FilteredMods?.Count() > 0) { PerformingEnableAll = true; - bool enabled = FilteredMods.Where(p => p.IsValid).All(p => p.IsSelected); + var enabled = FilteredMods.Where(p => p.IsValid).All(p => p.IsSelected); foreach (var item in FilteredMods) { item.IsSelected = !enabled; } + AllModsEnabled = FilteredMods.Where(p => p.IsValid).Any() && FilteredMods.Where(p => p.IsValid).All(p => p.IsSelected); PerformingEnableAll = false; } @@ -942,7 +973,7 @@ protected override void OnActivated(CompositeDisposable disposables) { if (ContextMenuMod != null) { - await DeleteDescriptorAsync(new List() { ContextMenuMod }).ConfigureAwait(true); + await DeleteDescriptorAsync(new List { ContextMenuMod }).ConfigureAwait(true); } }).DisposeWith(disposables); @@ -958,7 +989,7 @@ protected override void OnActivated(CompositeDisposable disposables) { if (ContextMenuMod != null) { - await LockDescriptorAsync(new List() { ContextMenuMod }, true).ConfigureAwait(true); + await LockDescriptorAsync(new List { ContextMenuMod }, true).ConfigureAwait(true); } }).DisposeWith(disposables); @@ -974,7 +1005,7 @@ protected override void OnActivated(CompositeDisposable disposables) { if (ContextMenuMod != null) { - await LockDescriptorAsync(new List() { ContextMenuMod }, false).ConfigureAwait(true); + await LockDescriptorAsync(new List { ContextMenuMod }, false).ConfigureAwait(true); } }).DisposeWith(disposables); @@ -998,6 +1029,7 @@ protected override void OnActivated(CompositeDisposable disposables) { await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Installed_Mods.LoadingMods)); } + await EvalModAchievementAsync(s.Mods, s.HasPriority).ConfigureAwait(false); if (s.ShowOverlay) { @@ -1030,22 +1062,21 @@ protected virtual async Task RemoveInvalidModsPromptAsync(IEnumerable mods { return; } + showingPrompt = true; var messages = new List(); foreach (var item in mods) { messages.Add($"{item.Name} ({item.DescriptorFile})"); } + var title = localizationManager.GetResource(LocalizationResources.Installed_Mods.InvalidMods.Title); - var message = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Installed_Mods.InvalidMods.Message), new - { - Mods = string.Join(Environment.NewLine, messages), - Environment.NewLine - }); + var message = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Installed_Mods.InvalidMods.Message), new { Mods = string.Join(Environment.NewLine, messages), Environment.NewLine }); if (await notificationAction.ShowPromptAsync(title, title, message, NotificationType.Warning)) { await DeleteDescriptorAsync(mods); } + showingPrompt = false; } @@ -1061,6 +1092,7 @@ protected virtual IComparer ResolveComparer(string dictKey) { return (IComparer)StringComparer.OrdinalIgnoreCase; } + return null; } @@ -1106,7 +1138,7 @@ protected virtual void SortFunction(Func sortProp, string dictKey) if (FilteredMods != null) { var sortOrder = sortOrders[dictKey]; - IComparer comparer = ResolveComparer(dictKey); + var comparer = ResolveComparer(dictKey); switch (sortOrder.SortOrder) { case Implementation.SortOrder.Asc: @@ -1120,6 +1152,7 @@ protected virtual void SortFunction(Func sortProp, string dictKey) FilteredMods = FilteredMods.OrderBy(sortProp).ToObservableCollection(); AllMods = AllMods.OrderBy(sortProp).ToHashSet(); } + SelectedMod = null; break; @@ -1134,16 +1167,16 @@ protected virtual void SortFunction(Func sortProp, string dictKey) FilteredMods = FilteredMods.OrderByDescending(sortProp).ToObservableCollection(); AllMods = AllMods.OrderByDescending(sortProp).ToHashSet(); } - SelectedMod = null; - break; - default: + SelectedMod = null; break; } + foreach (var sort in sortOrders.Where(p => p.Value != sortOrder)) { sort.Value.SetSortOrder(Implementation.SortOrder.None); } + SaveState(); } } diff --git a/src/IronyModManager/Views/Controls/CollectionModsControlView.xaml.cs b/src/IronyModManager/Views/Controls/CollectionModsControlView.xaml.cs index d4c1727c..b7e0d154 100644 --- a/src/IronyModManager/Views/Controls/CollectionModsControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/CollectionModsControlView.xaml.cs @@ -1,22 +1,21 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 03-03-2020 // // Last Modified By : Mario -// Last Modified On : 12-05-2023 +// Last Modified On : 06-06-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; -using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.LogicalTree; @@ -34,7 +33,6 @@ namespace IronyModManager.Views.Controls { - /// /// Class CollectionModsControlView. /// Implements the @@ -118,7 +116,7 @@ protected virtual void HandleItemDragged() { var sourceMod = source as IMod; var destinationMod = destination as IMod; - ViewModel.InstantReorderSelectedItems(sourceMod, destinationMod.Order); + ViewModel!.InstantReorderSelectedItems(sourceMod, destinationMod!.Order); }; } @@ -127,13 +125,13 @@ protected virtual void HandleItemDragged() /// protected virtual void HandlePointerMoved() { - modList.PointerMoved += (sender, args) => + modList.PointerMoved += (_, _) => { var hoveredItem = modList.GetLogicalChildren().Cast().FirstOrDefault(p => p.IsPointerOver); if (hoveredItem != null) { - ViewModel.HoveredMod = hoveredItem.Content as IMod; - }; + ViewModel!.HoveredMod = hoveredItem.Content as IMod; + } }; } @@ -152,6 +150,7 @@ protected override void OnActivated(CompositeDisposable disposables) HandleItemDragged(); HandlePointerMoved(); } + base.OnActivated(disposables); } @@ -160,14 +159,15 @@ protected override void OnActivated(CompositeDisposable disposables) /// protected virtual void SetContextMenus() { - modList.ContextMenuOpening += (item) => + modList.ContextMenuOpening += item => { List menuItems = null; if (item != null) { - ViewModel.ContextMenuMod = item.Content as IMod; + ViewModel!.ContextMenuMod = item.Content as IMod; menuItems = GetMenuItems(); } + menuItems ??= GetStaticMenuItems(); modList.SetContextMenuItems(menuItems); }; @@ -179,12 +179,14 @@ protected virtual void SetContextMenus() protected virtual void SetUIParameters() { var performingUISet = false; + async Task setUIProperties() { while (performingUISet) { await Task.Delay(25); } + performingUISet = true; await Dispatcher.UIThread.SafeInvokeAsync(() => { @@ -194,7 +196,7 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => var grid = item.GetLogicalChildren().OfType().FirstOrDefault(); if (grid != null) { - for (int i = 0; i < grid.ColumnDefinitions.Count; i++) + for (var i = 0; i < grid.ColumnDefinitions.Count; i++) { var col = grid.ColumnDefinitions[i]; var width = header.ColumnDefinitions[i].ActualWidth; @@ -203,11 +205,12 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => col.Width = new GridLength(width); } } + var orderCtrl = grid.GetLogicalChildren().OfType().FirstOrDefault(p => p.Name == OrderName); if (orderCtrl != null) { orderCtrl.Minimum = 1; - orderCtrl.Maximum = ViewModel.MaxOrder; + orderCtrl.Maximum = ViewModel!.MaxOrder; } } } @@ -215,20 +218,20 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => performingUISet = false; } - ViewModel.ModReordered += (mod, instant) => + ViewModel!.ModReordered += (mod, instant) => { setUIProperties().ConfigureAwait(false); modList.Focus(); FocusListBoxItemAsync(mod, instant).ConfigureAwait(true); }; - this.WhenAnyValue(v => v.ViewModel.MaxOrder).Subscribe(max => + this.WhenAnyValue(v => v.ViewModel.MaxOrder).Subscribe(_ => { setUIProperties().ConfigureAwait(false); }).DisposeWith(Disposables); var mbus = DIResolver.Get(); - modList.LayoutUpdated += (sender, args) => + modList.LayoutUpdated += (_, _) => { var visibleItems = modList.ItemContainerGenerator.Containers.ToList(); if (visibleItems.Any()) @@ -243,25 +246,26 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => } } } + setUIProperties().ConfigureAwait(false); }; var modListAutoScrollValue = modList.AutoScrollToSelectedItem; int? modListFocusSideScrollItem = null; - int previousModListCount = 0; - int previousModListIndex = 0; - ViewModel.BeforeUndoRedo += (sender, args) => + var previousModListCount = 0; + var previousModListIndex = 0; + ViewModel.BeforeUndoRedo += (_, _) => { modList.AutoScrollToSelectedItem = false; previousModListIndex = modList.SelectedIndex; var visibleItems = modList.ItemContainerGenerator.Containers.ToList(); if (visibleItems.Any()) { - modListFocusSideScrollItem = visibleItems.FirstOrDefault().Index; + modListFocusSideScrollItem = visibleItems.FirstOrDefault()!.Index; previousModListCount = modList.ItemCount; } }; - ViewModel.AfterUndoRedo += (sender, args) => + ViewModel.AfterUndoRedo += (_, _) => { async Task delay() { @@ -274,6 +278,7 @@ async Task delay() { modListFocusSideScrollItem -= Math.Abs(previousModListCount - modList.ItemCount); } + if (modListFocusSideScrollItem.GetValueOrDefault() < 0) { modListFocusSideScrollItem = 0; @@ -282,10 +287,12 @@ async Task delay() { modListFocusSideScrollItem = modList.ItemCount - 1; } + modList.SelectedIndex = previousModListIndex; modList.ScrollIntoView(modListFocusSideScrollItem.GetValueOrDefault()); } } + Dispatcher.UIThread.SafeInvoke(() => delay().ConfigureAwait(false)); }; } @@ -298,34 +305,12 @@ private List GetMenuItems() { var menuItems = new List { - new() - { - Header = ViewModel.CollectionJumpOnPositionChangeLabel, - Command = ViewModel.CollectionJumpOnPositionChangeCommand - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.ExportCollectionToClipboard, - Command = ViewModel.ExportCollectionToClipboardCommand - }, - new() - { - Header = ViewModel.ExportCollectionToClipboardFull, - Command = ViewModel.ExportCollectionToClipboardFullCommand - }, - new() - { - Header = ViewModel.ImportCollectionFromClipboard, - Command = ViewModel.ImportCollectionFromClipboardCommand - }, - new() - { - Header = "-" - } + new() { Header = ViewModel!.CollectionJumpOnPositionChangeLabel, Command = ViewModel.CollectionJumpOnPositionChangeCommand }, + new() { Header = "-" }, + new() { Header = ViewModel.ExportCollectionToClipboard, Command = ViewModel.ExportCollectionToClipboardCommand }, + new() { Header = ViewModel.ExportCollectionToClipboardFull, Command = ViewModel.ExportCollectionToClipboardFullCommand }, + new() { Header = ViewModel.ImportCollectionFromClipboard, Command = ViewModel.ImportCollectionFromClipboardCommand }, + new() { Header = "-" } }; var counterOffset = 5; var redoAvailable = ViewModel.IsRedoAvailable(); @@ -335,26 +320,17 @@ private List GetMenuItems() var offset = 1; if (undoAvailable) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.Undo, - Command = ViewModel.UndoCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.Undo, Command = ViewModel.UndoCommand }); offset++; } + if (redoAvailable) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.Redo, - Command = ViewModel.RedoCommand, - }); + menuItems.Add(new MenuItem { Header = ViewModel.Redo, Command = ViewModel.RedoCommand }); offset++; } - menuItems.Add(new MenuItem() - { - Header = "-" - }); + + menuItems.Add(new MenuItem { Header = "-" }); counterOffset += offset; } @@ -365,53 +341,30 @@ private List GetMenuItems() var offset = 2; if (canExportMods) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.ExportCollectionReport, - Command = ViewModel.ExportCollectionReportCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.ExportCollectionReport, Command = ViewModel.ExportCollectionReportCommand }); offset++; } + if (canExportGame) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.ExportGameReport, - Command = ViewModel.ExportGameReportCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.ExportGameReport, Command = ViewModel.ExportGameReportCommand }); offset++; } - menuItems.Add(new MenuItem() - { - Header = ViewModel.ImportReport, - Command = ViewModel.ImportReportCommand - }); - menuItems.Add(new MenuItem() - { - Header = "-" - }); + + menuItems.Add(new MenuItem { Header = ViewModel.ImportReport, Command = ViewModel.ImportReportCommand }); + menuItems.Add(new MenuItem { Header = "-" }); counterOffset += offset; } + if (!string.IsNullOrEmpty(ViewModel.GetContextMenuModUrl())) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.OpenUrl, - Command = ViewModel.OpenUrlCommand - }); - menuItems.Add(new MenuItem() - { - Header = ViewModel.CopyUrl, - Command = ViewModel.CopyUrlCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.OpenUrl, Command = ViewModel.OpenUrlCommand }); + menuItems.Add(new MenuItem { Header = ViewModel.CopyUrl, Command = ViewModel.CopyUrlCommand }); } + if (!string.IsNullOrEmpty(ViewModel.GetContextMenuModSteamUrl())) { - var menuItem = new MenuItem() - { - Header = ViewModel.OpenInSteam, - Command = ViewModel.OpenInSteamCommand - }; + var menuItem = new MenuItem { Header = ViewModel.OpenInSteam, Command = ViewModel.OpenInSteamCommand }; if (menuItems.Count == counterOffset) { menuItems.Add(menuItem); @@ -421,13 +374,10 @@ private List GetMenuItems() menuItems.Insert(counterOffset + 1, menuItem); } } + if (!string.IsNullOrWhiteSpace(ViewModel.ContextMenuMod?.FullPath)) { - var menuItem = new MenuItem() - { - Header = ViewModel.OpenInAssociatedApp, - Command = ViewModel.OpenInAssociatedAppCommand - }; + var menuItem = new MenuItem { Header = ViewModel.OpenInAssociatedApp, Command = ViewModel.OpenInAssociatedAppCommand }; if (menuItems.Count == counterOffset) { menuItems.Add(menuItem); @@ -436,7 +386,11 @@ private List GetMenuItems() { menuItems.Insert(counterOffset, menuItem); } + + menuItem = new MenuItem { Header = ViewModel.CopyModPath, Command = ViewModel.CopyModPathCommand }; + menuItems.Add(menuItem); } + return menuItems; } @@ -448,86 +402,46 @@ private List GetStaticMenuItems() { var menuItems = new List { - new() - { - Header = ViewModel.CollectionJumpOnPositionChangeLabel, - Command = ViewModel.CollectionJumpOnPositionChangeCommand - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.ExportCollectionToClipboard, - Command = ViewModel.ExportCollectionToClipboardCommand - }, - new() - { - Header = ViewModel.ExportCollectionToClipboardFull, - Command = ViewModel.ExportCollectionToClipboardFullCommand - }, - new() - { - Header = ViewModel.ImportCollectionFromClipboard, - Command = ViewModel.ImportCollectionFromClipboardCommand - } + new() { Header = ViewModel!.CollectionJumpOnPositionChangeLabel, Command = ViewModel.CollectionJumpOnPositionChangeCommand }, + new() { Header = "-" }, + new() { Header = ViewModel.ExportCollectionToClipboard, Command = ViewModel.ExportCollectionToClipboardCommand }, + new() { Header = ViewModel.ExportCollectionToClipboardFull, Command = ViewModel.ExportCollectionToClipboardFullCommand }, + new() { Header = ViewModel.ImportCollectionFromClipboard, Command = ViewModel.ImportCollectionFromClipboardCommand } }; var redoAvailable = ViewModel.IsRedoAvailable(); var undoAvailable = ViewModel.IsUndoAvailable(); if (redoAvailable || undoAvailable) { - menuItems.Add(new MenuItem() - { - Header = "-" - }); + menuItems.Add(new MenuItem { Header = "-" }); if (undoAvailable) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.Undo, - Command = ViewModel.UndoCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.Undo, Command = ViewModel.UndoCommand }); } + if (redoAvailable) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.Redo, - Command = ViewModel.RedoCommand, - }); + menuItems.Add(new MenuItem { Header = ViewModel.Redo, Command = ViewModel.RedoCommand }); } } + var canExportGame = ViewModel.CanExportGame(); var canExportMods = ViewModel.CanExportMods(); if (canExportMods || canExportGame) { - menuItems.Add(new MenuItem() - { - Header = "-" - }); + menuItems.Add(new MenuItem { Header = "-" }); if (canExportMods) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.ExportCollectionReport, - Command = ViewModel.ExportCollectionReportCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.ExportCollectionReport, Command = ViewModel.ExportCollectionReportCommand }); } + if (canExportGame) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.ExportGameReport, - Command = ViewModel.ExportGameReportCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.ExportGameReport, Command = ViewModel.ExportGameReportCommand }); } - menuItems.Add(new MenuItem() - { - Header = ViewModel.ImportReport, - Command = ViewModel.ImportReportCommand - }); + + menuItems.Add(new MenuItem { Header = ViewModel.ImportReport, Command = ViewModel.ImportReportCommand }); } + return menuItems; } diff --git a/src/IronyModManager/Views/Controls/InstalledModsControlView.xaml.cs b/src/IronyModManager/Views/Controls/InstalledModsControlView.xaml.cs index b5c2e301..9d6d2d00 100644 --- a/src/IronyModManager/Views/Controls/InstalledModsControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/InstalledModsControlView.xaml.cs @@ -11,6 +11,7 @@ // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; @@ -68,6 +69,7 @@ async Task updateLayout() { await Task.Delay(25); } + performingLayoutUpdate = true; await Dispatcher.UIThread.SafeInvokeAsync(() => { @@ -77,7 +79,7 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => var grid = item.GetLogicalChildren().OfType().FirstOrDefault(); if (grid != null) { - for (int i = 0; i < grid.ColumnDefinitions.Count; i++) + for (var i = 0; i < grid.ColumnDefinitions.Count; i++) { var col = grid.ColumnDefinitions[i]; var width = header.ColumnDefinitions[i].ActualWidth; @@ -94,19 +96,20 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => if (modList != null && header != null) { - modList.ContextMenuOpening += (item) => + modList.ContextMenuOpening += item => { List menuItems = null; if (item != null) { - ViewModel.ContextMenuMod = item.Content as IMod; + ViewModel!.ContextMenuMod = item.Content as IMod; menuItems = !string.IsNullOrEmpty(ViewModel.GetContextMenuModModUrl()) || !string.IsNullOrEmpty(ViewModel.GetContextMenuModModSteamUrl()) ? GetAllMenuItems() : GetActionMenuItems(); } + menuItems ??= GetStaticMenuItems(); modList.SetContextMenuItems(menuItems); }; var mbus = DIResolver.Get(); - modList.LayoutUpdated += (sender, args) => + modList.LayoutUpdated += (_, _) => { var visibleItems = modList.ItemContainerGenerator.Containers.ToList(); if (visibleItems.Any()) @@ -121,9 +124,11 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => } } } + updateLayout().ConfigureAwait(false); }; } + base.OnActivated(disposables); } @@ -133,62 +138,25 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => /// List<MenuItem>. private List GetActionMenuItems() { - var menuItems = new List() + var menuItems = new List { - new MenuItem() - { - Header = ViewModel.CheckNewMods, - Command = ViewModel.CheckNewModsCommand - }, - new MenuItem() - { - Header = ViewModel.DeleteDescriptor, - Command = ViewModel.DeleteDescriptorCommand - }, - new MenuItem() - { - Header = ViewModel.DeleteAllDescriptors, - Command = ViewModel.DeleteAllDescriptorsCommand - }, - new MenuItem() - { - Header = "-" - }, - new MenuItem() - { - Header = ViewModel.LockDescriptor, - Command = ViewModel.LockDescriptorCommand - }, - new MenuItem() - { - Header = ViewModel.LockAllDescriptors, - Command = ViewModel.LockAllDescriptorsCommand - }, - new MenuItem() - { - Header = ViewModel.UnlockDescriptor, - Command = ViewModel.UnlockDescriptorCommand - }, - new MenuItem() - { - Header = ViewModel.UnlockAllDescriptors, - Command = ViewModel.UnlockAllDescriptorsCommand - } + new() { Header = ViewModel!.CheckNewMods, Command = ViewModel.CheckNewModsCommand }, + new() { Header = ViewModel.DeleteDescriptor, Command = ViewModel.DeleteDescriptorCommand }, + new() { Header = ViewModel.DeleteAllDescriptors, Command = ViewModel.DeleteAllDescriptorsCommand }, + new() { Header = "-" }, + new() { Header = ViewModel.LockDescriptor, Command = ViewModel.LockDescriptorCommand }, + new() { Header = ViewModel.LockAllDescriptors, Command = ViewModel.LockAllDescriptorsCommand }, + new() { Header = ViewModel.UnlockDescriptor, Command = ViewModel.UnlockDescriptorCommand }, + new() { Header = ViewModel.UnlockAllDescriptors, Command = ViewModel.UnlockAllDescriptorsCommand } }; if (!string.IsNullOrWhiteSpace(ViewModel.ContextMenuMod?.FullPath)) { - var menuItem = new MenuItem() - { - Header = ViewModel.OpenInAssociatedApp, - Command = ViewModel.OpenInAssociatedAppCommand - }; + var menuItem = new MenuItem { Header = ViewModel.OpenInAssociatedApp, Command = ViewModel.OpenInAssociatedAppCommand }; menuItems.Insert(0, menuItem); - menuItems.Insert(1, new MenuItem() - { - Header = "-" - }); + menuItems.Insert(1, new MenuItem { Header = "-" }); } + return menuItems; } @@ -199,26 +167,15 @@ private List GetActionMenuItems() private List GetAllMenuItems() { var menuItems = new List(); - if (!string.IsNullOrEmpty(ViewModel.GetContextMenuModModUrl())) + if (!string.IsNullOrEmpty(ViewModel!.GetContextMenuModModUrl())) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.OpenUrl, - Command = ViewModel.OpenUrlCommand - }); - menuItems.Add(new MenuItem() - { - Header = ViewModel.CopyUrl, - Command = ViewModel.CopyUrlCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.OpenUrl, Command = ViewModel.OpenUrlCommand }); + menuItems.Add(new MenuItem { Header = ViewModel.CopyUrl, Command = ViewModel.CopyUrlCommand }); } + if (!string.IsNullOrEmpty(ViewModel.GetContextMenuModModSteamUrl())) { - var menuItem = new MenuItem() - { - Header = ViewModel.OpenInSteam, - Command = ViewModel.OpenInSteamCommand - }; + var menuItem = new MenuItem { Header = ViewModel.OpenInSteam, Command = ViewModel.OpenInSteamCommand }; if (menuItems.Count == 0) { menuItems.Add(menuItem); @@ -227,14 +184,14 @@ private List GetAllMenuItems() { menuItems.Insert(1, menuItem); } + + menuItem = new MenuItem { Header = ViewModel.CopyModPath, Command = ViewModel.CopyModPathCommand }; + menuItems.Add(menuItem); } + if (!string.IsNullOrWhiteSpace(ViewModel.ContextMenuMod?.FullPath)) { - var menuItem = new MenuItem() - { - Header = ViewModel.OpenInAssociatedApp, - Command = ViewModel.OpenInAssociatedAppCommand - }; + var menuItem = new MenuItem { Header = ViewModel.OpenInAssociatedApp, Command = ViewModel.OpenInAssociatedAppCommand }; if (menuItems.Count == 0) { menuItems.Add(menuItem); @@ -244,51 +201,18 @@ private List GetAllMenuItems() menuItems.Insert(0, menuItem); } } - menuItems.AddRange(new List() + + menuItems.AddRange(new List { - new MenuItem() - { - Header = "-" - }, - new MenuItem() - { - Header = ViewModel.CheckNewMods, - Command = ViewModel.CheckNewModsCommand - }, - new MenuItem() - { - Header = ViewModel.DeleteDescriptor, - Command = ViewModel.DeleteDescriptorCommand - }, - new MenuItem() - { - Header = ViewModel.DeleteAllDescriptors, - Command = ViewModel.DeleteAllDescriptorsCommand - }, - new MenuItem() - { - Header = "-" - }, - new MenuItem() - { - Header = ViewModel.LockDescriptor, - Command = ViewModel.LockDescriptorCommand - }, - new MenuItem() - { - Header = ViewModel.LockAllDescriptors, - Command = ViewModel.LockAllDescriptorsCommand - }, - new MenuItem() - { - Header = ViewModel.UnlockDescriptor, - Command = ViewModel.UnlockDescriptorCommand - }, - new MenuItem() - { - Header = ViewModel.UnlockAllDescriptors, - Command = ViewModel.UnlockAllDescriptorsCommand - } + new() { Header = "-" }, + new() { Header = ViewModel.CheckNewMods, Command = ViewModel.CheckNewModsCommand }, + new() { Header = ViewModel.DeleteDescriptor, Command = ViewModel.DeleteDescriptorCommand }, + new() { Header = ViewModel.DeleteAllDescriptors, Command = ViewModel.DeleteAllDescriptorsCommand }, + new() { Header = "-" }, + new() { Header = ViewModel.LockDescriptor, Command = ViewModel.LockDescriptorCommand }, + new() { Header = ViewModel.LockAllDescriptors, Command = ViewModel.LockAllDescriptorsCommand }, + new() { Header = ViewModel.UnlockDescriptor, Command = ViewModel.UnlockDescriptorCommand }, + new() { Header = ViewModel.UnlockAllDescriptors, Command = ViewModel.UnlockAllDescriptorsCommand } }); return menuItems; } @@ -299,19 +223,7 @@ private List GetAllMenuItems() /// System.Collections.Generic.List<Avalonia.Controls.MenuItem>. private List GetStaticMenuItems() { - var menuItems = new List() - { - new MenuItem() - { - Header = ViewModel.CheckNewMods, - Command = ViewModel.CheckNewModsCommand - }, - new MenuItem() - { - Header = ViewModel.DeleteAllDescriptors, - Command = ViewModel.DeleteAllDescriptorsCommand - } - }; + var menuItems = new List { new() { Header = ViewModel!.CheckNewMods, Command = ViewModel.CheckNewModsCommand }, new() { Header = ViewModel.DeleteAllDescriptors, Command = ViewModel.DeleteAllDescriptorsCommand } }; return menuItems; }