From 7c428e6b72422aca79cbcfc3f60cab0f3d47c615 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 13 Aug 2023 22:42:59 +0200 Subject: [PATCH] feat: allow individual toggling of plugins in a single custom collection from the installed plugins page --- .../PluginInstaller/PluginInstallerWindow.cs | 109 ++++++++++-------- Dalamud/Plugin/Internal/Profiles/Profile.cs | 3 + 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 4cc7e35c3..b648a8204 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -2366,6 +2366,10 @@ internal class PluginInstallerWindow : Window, IDisposable var config = Service.Get(); var applicableForProfiles = plugin.Manifest.SupportsProfiles && !plugin.IsDev; + var profilesThatWantThisPlugin = profileManager.Profiles + .Where(x => x.WantsPlugin(plugin.InternalName) != null) + .ToArray(); + var isInSingleProfile = profilesThatWantThisPlugin.Length == 1; var isDefaultPlugin = profileManager.IsInDefaultProfile(plugin.Manifest.InternalName); // Disable everything if the updater is running or another plugin is operating @@ -2449,6 +2453,10 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.EndPopup(); } + var inMultipleProfiles = !isDefaultPlugin && !isInSingleProfile; + var inSingleNonDefaultProfileWhichIsDisabled = + isInSingleProfile && !profilesThatWantThisPlugin.First().IsEnabled; + if (plugin.State is PluginState.UnloadError or PluginState.LoadError or PluginState.DependencyResolutionFailed && !plugin.IsDev) { ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); @@ -2456,80 +2464,77 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGui.IsItemHovered()) ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); } - else if (disabled || !isDefaultPlugin) + else if (disabled || inMultipleProfiles || inSingleNonDefaultProfileWhichIsDisabled) { ImGuiComponents.DisabledToggleButton(toggleId, isLoadedAndUnloadable); - if (!isDefaultPlugin && ImGui.IsItemHovered()) - ImGui.SetTooltip(Locs.PluginButtonToolTip_NeedsToBeInDefault); + if (inMultipleProfiles && ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_NeedsToBeInSingleProfile); + else if (inSingleNonDefaultProfileWhichIsDisabled && ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_SingleProfileDisabled(profilesThatWantThisPlugin.First().Name)); } else { if (ImGuiComponents.ToggleButton(toggleId, ref isLoadedAndUnloadable)) { - // TODO: We can technically let profile manager take care of unloading/loading the plugin, but we should figure out error handling first. + var applicableProfile = profilesThatWantThisPlugin.First(); + Log.Verbose("Switching {InternalName} in {Profile} to {State}", + plugin.InternalName, applicableProfile, isLoadedAndUnloadable); + + try + { + // Reload the devPlugin manifest if it's a dev plugin + // The plugin might rely on changed values in the manifest + if (plugin.IsDev) + { + plugin.ReloadManifest(); + } + } + catch (Exception ex) + { + Log.Error(ex, "Could not reload DevPlugin manifest"); + } + + // NOTE: We don't use the profile manager to actually handle loading/unloading here, + // because that might cause us to show an error if a plugin we don't actually care about + // fails to load/unload. Instead, we just do it ourselves and then update the profile. + // There is probably a smarter way to handle this, but it's probably more code. if (!isLoadedAndUnloadable) { this.enableDisableStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.DisablingSingle; - Task.Run(() => + Task.Run(async () => { - if (plugin.IsDev) - { - plugin.ReloadManifest(); - } - - var unloadTask = Task.Run(() => plugin.UnloadAsync()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); - - unloadTask.Wait(); - if (!unloadTask.Result) - { - this.enableDisableStatus = OperationStatus.Complete; - return; - } - - // TODO: Work this out - Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, false, false)) - .GetAwaiter().GetResult(); - this.enableDisableStatus = OperationStatus.Complete; + await plugin.UnloadAsync(); + await applicableProfile.AddOrUpdateAsync( + plugin.Manifest.InternalName, false, false); notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); + }).ContinueWith(t => + { + this.enableDisableStatus = OperationStatus.Complete; + this.DisplayErrorContinuation(t, Locs.ErrorModal_UnloadFail(plugin.Name)); }); } else { - var enabler = new Task(() => + async Task Enabler() { this.enableDisableStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.EnablingSingle; - if (plugin.IsDev) - { - plugin.ReloadManifest(); - } + await applicableProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, true, false); + await plugin.LoadAsync(PluginLoadReason.Installer); - // TODO: Work this out - Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, true, false)) - .GetAwaiter().GetResult(); + notifications.AddNotification(Locs.Notifications_PluginEnabled(plugin.Manifest.Name), Locs.Notifications_PluginEnabledTitle, NotificationType.Success); + } - var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer)) - .ContinueWith( - this.DisplayErrorContinuation, - Locs.ErrorModal_LoadFail(plugin.Name)); - - loadTask.Wait(); + var continuation = (Task t) => + { this.enableDisableStatus = OperationStatus.Complete; - - if (!loadTask.Result) - return; - - notifications.AddNotification( - Locs.Notifications_PluginEnabled(plugin.Manifest.Name), - Locs.Notifications_PluginEnabledTitle, - NotificationType.Success); - }); + this.DisplayErrorContinuation(t, Locs.ErrorModal_LoadFail(plugin.Name)); + }; if (availableUpdate != default && !availableUpdate.InstalledPlugin.IsDev) { @@ -2539,17 +2544,19 @@ internal class PluginInstallerWindow : Window, IDisposable if (shouldUpdate) { + // We need to update the profile right here, because PM will not enable the plugin otherwise + await applicableProfile.AddOrUpdateAsync(plugin.InternalName, true, false); await this.UpdateSinglePlugin(availableUpdate); } else { - enabler.Start(); + _ = Task.Run(Enabler).ContinueWith(continuation); } }); } else { - enabler.Start(); + _ = Task.Run(Enabler).ContinueWith(continuation); } } } @@ -3259,6 +3266,10 @@ internal class PluginInstallerWindow : Window, IDisposable public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerLoadUnloadFailedTooltip", "Plugin load/unload failed, please restart your game and try again."); public static string PluginButtonToolTip_NeedsToBeInDefault => Loc.Localize("InstallerUnloadNeedsToBeInDefault", "This plugin is in one or more collections. If you want to enable or disable it, please do so by enabling or disabling the collections it is in.\nIf you want to manage it manually, remove it from all collections."); + + public static string PluginButtonToolTip_NeedsToBeInSingleProfile => Loc.Localize("InstallerUnloadNeedsToBeInSingleProfile", "This plugin is in more than one collection. If you want to enable or disable it, please do so by enabling or disabling the collections it is in.\nIf you want to manage it here, make sure it is only in a single collection."); + + public static string PluginButtonToolTip_SingleProfileDisabled(string name) => Loc.Localize("InstallerSingleProfileDisabled", "The collection '{0}' which contains this plugin is disabled.\nPlease enable it in the collections manager to toggle the plugin individually.").Format(name); #endregion diff --git a/Dalamud/Plugin/Internal/Profiles/Profile.cs b/Dalamud/Plugin/Internal/Profiles/Profile.cs index 61d521e89..ac46d9153 100644 --- a/Dalamud/Plugin/Internal/Profiles/Profile.cs +++ b/Dalamud/Plugin/Internal/Profiles/Profile.cs @@ -232,4 +232,7 @@ internal class Profile if (apply) await this.manager.ApplyAllWantStatesAsync(); } + + /// + public override string ToString() => $"{this.Guid} ({this.Name})"; }