mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
2512 lines
104 KiB
C#
2512 lines
104 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Threading.Tasks;
|
|
|
|
using CheapLoc;
|
|
using Dalamud.Configuration.Internal;
|
|
using Dalamud.Game.Command;
|
|
using Dalamud.Interface.Colors;
|
|
using Dalamud.Interface.Components;
|
|
using Dalamud.Interface.Internal.Notifications;
|
|
using Dalamud.Interface.Windowing;
|
|
using Dalamud.Logging.Internal;
|
|
using Dalamud.Plugin;
|
|
using Dalamud.Plugin.Internal;
|
|
using Dalamud.Plugin.Internal.Exceptions;
|
|
using Dalamud.Plugin.Internal.Types;
|
|
using Dalamud.Support;
|
|
using Dalamud.Utility;
|
|
using ImGuiNET;
|
|
using ImGuiScene;
|
|
|
|
namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
|
{
|
|
/// <summary>
|
|
/// Class responsible for drawing the plugin installer.
|
|
/// </summary>
|
|
internal class PluginInstallerWindow : Window, IDisposable
|
|
{
|
|
private static readonly ModuleLog Log = new("PLUGINW");
|
|
|
|
private readonly Vector4 changelogBgColor = new(0.114f, 0.584f, 0.192f, 0.678f);
|
|
private readonly Vector4 changelogTextColor = new(0.812f, 1.000f, 0.816f, 1.000f);
|
|
|
|
private readonly PluginImageCache imageCache;
|
|
private readonly PluginCategoryManager categoryManager = new();
|
|
private readonly DalamudChangelogManager dalamudChangelogManager = new();
|
|
|
|
private readonly List<int> openPluginCollapsibles = new();
|
|
|
|
#region Image Tester State
|
|
|
|
private string[] testerImagePaths = new string[5];
|
|
private string testerIconPath = string.Empty;
|
|
|
|
private TextureWrap?[] testerImages;
|
|
private TextureWrap? testerIcon;
|
|
|
|
private bool testerError = false;
|
|
private bool testerUpdateAvailable = false;
|
|
|
|
#endregion
|
|
|
|
private bool errorModalDrawing = true;
|
|
private bool errorModalOnNextFrame = false;
|
|
private string errorModalMessage = string.Empty;
|
|
|
|
private bool feedbackModalDrawing = true;
|
|
private bool feedbackModalOnNextFrame = false;
|
|
private string feedbackModalBody = string.Empty;
|
|
private string feedbackModalContact = string.Empty;
|
|
private bool feedbackModalIncludeException = false;
|
|
private PluginManifest? feedbackPlugin = null;
|
|
private bool feedbackIsTesting = false;
|
|
|
|
private int updatePluginCount = 0;
|
|
private List<PluginUpdateStatus>? updatedPlugins;
|
|
|
|
private List<RemotePluginManifest> pluginListAvailable = new();
|
|
private List<LocalPlugin> pluginListInstalled = new();
|
|
private List<AvailablePluginUpdate> pluginListUpdatable = new();
|
|
private bool hasDevPlugins = false;
|
|
|
|
private string searchText = string.Empty;
|
|
|
|
private PluginSortKind sortKind = PluginSortKind.Alphabetical;
|
|
private string filterText = Locs.SortBy_Alphabetical;
|
|
|
|
private OperationStatus installStatus = OperationStatus.Idle;
|
|
private OperationStatus updateStatus = OperationStatus.Idle;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PluginInstallerWindow"/> class.
|
|
/// </summary>
|
|
/// <param name="imageCache">An instance of <see cref="PluginImageCache"/> class.</param>
|
|
public PluginInstallerWindow(PluginImageCache imageCache)
|
|
: base(
|
|
Locs.WindowTitle + (Service<DalamudConfiguration>.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
|
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
|
|
{
|
|
this.IsOpen = true;
|
|
this.imageCache = imageCache;
|
|
|
|
this.Size = new Vector2(830, 570);
|
|
this.SizeCondition = ImGuiCond.FirstUseEver;
|
|
|
|
this.SizeConstraints = new WindowSizeConstraints
|
|
{
|
|
MinimumSize = this.Size.Value,
|
|
MaximumSize = new Vector2(5000, 5000),
|
|
};
|
|
|
|
Service<PluginManager>.GetAsync().ContinueWith(pluginManagerTask =>
|
|
{
|
|
var pluginManager = pluginManagerTask.Result;
|
|
|
|
// For debugging
|
|
if (pluginManager.PluginsReady)
|
|
this.OnInstalledPluginsChanged();
|
|
|
|
pluginManager.OnAvailablePluginsChanged += this.OnAvailablePluginsChanged;
|
|
pluginManager.OnInstalledPluginsChanged += this.OnInstalledPluginsChanged;
|
|
|
|
for (var i = 0; i < this.testerImagePaths.Length; i++)
|
|
{
|
|
this.testerImagePaths[i] = string.Empty;
|
|
}
|
|
});
|
|
}
|
|
|
|
private enum OperationStatus
|
|
{
|
|
Idle,
|
|
InProgress,
|
|
Complete,
|
|
}
|
|
|
|
private enum PluginSortKind
|
|
{
|
|
Alphabetical,
|
|
DownloadCount,
|
|
LastUpdate,
|
|
NewOrNot,
|
|
NotInstalled,
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void Dispose()
|
|
{
|
|
var pluginManager = Service<PluginManager>.GetNullable();
|
|
if (pluginManager != null)
|
|
{
|
|
pluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged;
|
|
pluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void OnOpen()
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
_ = pluginManager.ReloadPluginMastersAsync();
|
|
_ = this.dalamudChangelogManager.ReloadChangelogAsync();
|
|
|
|
this.searchText = string.Empty;
|
|
this.sortKind = PluginSortKind.Alphabetical;
|
|
this.filterText = Locs.SortBy_Alphabetical;
|
|
|
|
if (this.updateStatus == OperationStatus.Complete || this.updateStatus == OperationStatus.Idle)
|
|
{
|
|
this.updateStatus = OperationStatus.Idle;
|
|
this.updatePluginCount = 0;
|
|
this.updatedPlugins = null;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void OnClose()
|
|
{
|
|
Service<DalamudConfiguration>.Get().Save();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void Draw()
|
|
{
|
|
this.DrawHeader();
|
|
this.DrawPluginCategories();
|
|
this.DrawFooter();
|
|
this.DrawErrorModal();
|
|
this.DrawFeedbackModal();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the icon and image caches, forcing a fresh download.
|
|
/// </summary>
|
|
public void ClearIconCache()
|
|
{
|
|
this.imageCache.ClearIconCache();
|
|
}
|
|
|
|
private void DrawHeader()
|
|
{
|
|
var style = ImGui.GetStyle();
|
|
var windowSize = ImGui.GetWindowContentRegionMax();
|
|
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale));
|
|
|
|
var searchInputWidth = 240 * ImGuiHelpers.GlobalScale;
|
|
var searchClearButtonWidth = 40 * ImGuiHelpers.GlobalScale;
|
|
|
|
var sortByText = Locs.SortBy_Label;
|
|
var sortByTextWidth = ImGui.CalcTextSize(sortByText).X;
|
|
var sortSelectables = new (string Localization, PluginSortKind SortKind)[]
|
|
{
|
|
(Locs.SortBy_Alphabetical, PluginSortKind.Alphabetical),
|
|
(Locs.SortBy_DownloadCounts, PluginSortKind.DownloadCount),
|
|
(Locs.SortBy_LastUpdate, PluginSortKind.LastUpdate),
|
|
(Locs.SortBy_NewOrNot, PluginSortKind.NewOrNot),
|
|
(Locs.SortBy_NotInstalled, PluginSortKind.NotInstalled),
|
|
};
|
|
var longestSelectableWidth = sortSelectables.Select(t => ImGui.CalcTextSize(t.Localization).X).Max();
|
|
var selectableWidth = longestSelectableWidth + (style.FramePadding.X * 2); // This does not include the label
|
|
var sortSelectWidth = selectableWidth + sortByTextWidth + style.ItemInnerSpacing.X; // Item spacing between the selectable and the label
|
|
|
|
var headerText = Locs.Header_Hint;
|
|
var headerTextSize = ImGui.CalcTextSize(headerText);
|
|
ImGui.Text(headerText);
|
|
|
|
ImGui.SameLine();
|
|
|
|
// Shift down a little to align with the middle of the header text
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2);
|
|
|
|
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth - searchClearButtonWidth);
|
|
|
|
var searchTextChanged = false;
|
|
ImGui.SetNextItemWidth(searchInputWidth);
|
|
searchTextChanged |= ImGui.InputTextWithHint(
|
|
"###XlPluginInstaller_Search",
|
|
Locs.Header_SearchPlaceholder,
|
|
ref this.searchText,
|
|
100);
|
|
|
|
ImGui.SameLine();
|
|
|
|
ImGui.SetNextItemWidth(searchClearButtonWidth);
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
|
|
{
|
|
this.searchText = string.Empty;
|
|
searchTextChanged = true;
|
|
}
|
|
|
|
if (searchTextChanged)
|
|
this.UpdateCategoriesOnSearchChange();
|
|
|
|
ImGui.SameLine();
|
|
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth);
|
|
ImGui.SetNextItemWidth(selectableWidth);
|
|
if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton))
|
|
{
|
|
foreach (var selectable in sortSelectables)
|
|
{
|
|
if (ImGui.Selectable(selectable.Localization))
|
|
{
|
|
this.sortKind = selectable.SortKind;
|
|
this.filterText = selectable.Localization;
|
|
|
|
this.ResortPlugins();
|
|
}
|
|
}
|
|
|
|
ImGui.EndCombo();
|
|
}
|
|
}
|
|
|
|
private void DrawFooter()
|
|
{
|
|
var configuration = Service<DalamudConfiguration>.Get();
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
var windowSize = ImGui.GetWindowContentRegionMax();
|
|
var placeholderButtonSize = ImGuiHelpers.GetButtonSize("placeholder");
|
|
|
|
ImGui.Separator();
|
|
|
|
ImGui.SetCursorPosY(windowSize.Y - placeholderButtonSize.Y);
|
|
|
|
this.DrawUpdatePluginsButton();
|
|
|
|
ImGui.SameLine();
|
|
if (ImGui.Button(Locs.FooterButton_Settings))
|
|
{
|
|
Service<DalamudInterface>.Get().OpenSettings();
|
|
}
|
|
|
|
// If any dev plugins are installed, allow a shortcut for the /xldev menu item
|
|
if (this.hasDevPlugins)
|
|
{
|
|
ImGui.SameLine();
|
|
if (ImGui.Button(Locs.FooterButton_ScanDevPlugins))
|
|
{
|
|
pluginManager.ScanDevPlugins();
|
|
}
|
|
}
|
|
|
|
var closeText = Locs.FooterButton_Close;
|
|
var closeButtonSize = ImGuiHelpers.GetButtonSize(closeText);
|
|
|
|
ImGui.SameLine(windowSize.X - closeButtonSize.X - 20);
|
|
if (ImGui.Button(closeText))
|
|
{
|
|
this.IsOpen = false;
|
|
configuration.Save();
|
|
}
|
|
}
|
|
|
|
private void DrawUpdatePluginsButton()
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
var notifications = Service<NotificationManager>.Get();
|
|
|
|
var ready = pluginManager.PluginsReady && pluginManager.ReposReady;
|
|
|
|
if (pluginManager.SafeMode)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.FooterButton_UpdateSafeMode);
|
|
}
|
|
else if (!ready || this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.FooterButton_UpdatePlugins);
|
|
}
|
|
else if (this.updateStatus == OperationStatus.Complete)
|
|
{
|
|
ImGui.Button(this.updatePluginCount > 0
|
|
? Locs.FooterButton_UpdateComplete(this.updatePluginCount)
|
|
: Locs.FooterButton_NoUpdates);
|
|
}
|
|
else
|
|
{
|
|
if (ImGui.Button(Locs.FooterButton_UpdatePlugins))
|
|
{
|
|
this.updateStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(() => pluginManager.UpdatePluginsAsync())
|
|
.ContinueWith(task =>
|
|
{
|
|
this.updateStatus = OperationStatus.Complete;
|
|
|
|
if (task.IsFaulted)
|
|
{
|
|
this.updatePluginCount = 0;
|
|
this.updatedPlugins = null;
|
|
this.DisplayErrorContinuation(task, Locs.ErrorModal_UpdaterFatal);
|
|
}
|
|
else
|
|
{
|
|
this.updatedPlugins = task.Result.Where(res => res.WasUpdated).ToList();
|
|
this.updatePluginCount = this.updatedPlugins.Count;
|
|
|
|
var errorPlugins = task.Result.Where(res => !res.WasUpdated).ToList();
|
|
var errorPluginCount = errorPlugins.Count;
|
|
|
|
if (errorPluginCount > 0)
|
|
{
|
|
var errorMessage = this.updatePluginCount > 0
|
|
? Locs.ErrorModal_UpdaterFailPartial(this.updatePluginCount, errorPluginCount)
|
|
: Locs.ErrorModal_UpdaterFail(errorPluginCount);
|
|
|
|
var hintInsert = errorPlugins
|
|
.Aggregate(string.Empty, (current, pluginUpdateStatus) => $"{current}* {pluginUpdateStatus.InternalName}\n")
|
|
.TrimEnd();
|
|
errorMessage += Locs.ErrorModal_HintBlame(hintInsert);
|
|
|
|
this.DisplayErrorContinuation(task, errorMessage);
|
|
}
|
|
|
|
if (this.updatePluginCount > 0)
|
|
{
|
|
PluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox);
|
|
notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success);
|
|
|
|
var installedGroupIdx = this.categoryManager.GroupList.TakeWhile(
|
|
x => x.GroupKind != PluginCategoryManager.GroupKind.Installed).Count();
|
|
this.categoryManager.CurrentGroupIdx = installedGroupIdx;
|
|
}
|
|
else if (this.updatePluginCount == 0)
|
|
{
|
|
notifications.AddNotification(Locs.Notifications_NoUpdatesFound, Locs.Notifications_NoUpdatesFoundTitle, NotificationType.Info);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawErrorModal()
|
|
{
|
|
var modalTitle = Locs.ErrorModal_Title;
|
|
|
|
if (ImGui.BeginPopupModal(modalTitle, ref this.errorModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar))
|
|
{
|
|
ImGui.Text(this.errorModalMessage);
|
|
ImGui.Spacing();
|
|
|
|
var buttonWidth = 120f;
|
|
ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2);
|
|
|
|
if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40)))
|
|
{
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
if (this.errorModalOnNextFrame)
|
|
{
|
|
// NOTE(goat): ImGui cannot open a modal if no window is focused, at the moment.
|
|
// If people click out of the installer into the game while a plugin is installing, we won't be able to show a modal if we don't grab focus.
|
|
ImGui.SetWindowFocus(this.WindowName);
|
|
|
|
ImGui.OpenPopup(modalTitle);
|
|
this.errorModalOnNextFrame = false;
|
|
this.errorModalDrawing = true;
|
|
}
|
|
}
|
|
|
|
private void DrawFeedbackModal()
|
|
{
|
|
var modalTitle = Locs.FeedbackModal_Title;
|
|
|
|
if (ImGui.BeginPopupModal(modalTitle, ref this.feedbackModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar))
|
|
{
|
|
ImGui.TextUnformatted(Locs.FeedbackModal_Text(this.feedbackPlugin.Name));
|
|
|
|
if (this.feedbackPlugin?.FeedbackMessage != null)
|
|
{
|
|
ImGuiHelpers.SafeTextWrapped(this.feedbackPlugin.FeedbackMessage);
|
|
}
|
|
|
|
if (this.pluginListUpdatable.Any(
|
|
up => up.InstalledPlugin.Manifest.InternalName == this.feedbackPlugin?.InternalName))
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudRed, Locs.FeedbackModal_HasUpdate);
|
|
}
|
|
|
|
ImGui.Spacing();
|
|
|
|
ImGui.InputTextMultiline("###FeedbackContent", ref this.feedbackModalBody, 1000, new Vector2(400, 200));
|
|
|
|
ImGui.Spacing();
|
|
|
|
ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100);
|
|
|
|
ImGui.Checkbox(Locs.FeedbackModal_IncludeLastError, ref this.feedbackModalIncludeException);
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_IncludeLastErrorHint);
|
|
|
|
ImGui.Spacing();
|
|
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_Hint);
|
|
|
|
var buttonWidth = 120f;
|
|
ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2);
|
|
|
|
if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40)))
|
|
{
|
|
if (this.feedbackPlugin != null)
|
|
{
|
|
Task.Run(async () => await BugBait.SendFeedback(this.feedbackPlugin, this.feedbackIsTesting, this.feedbackModalBody, this.feedbackModalContact, this.feedbackModalIncludeException))
|
|
.ContinueWith(
|
|
t =>
|
|
{
|
|
var notif = Service<NotificationManager>.Get();
|
|
if (t.IsCanceled || t.IsFaulted)
|
|
notif.AddNotification(Locs.FeedbackModal_NotificationError, Locs.FeedbackModal_Title, NotificationType.Error);
|
|
else
|
|
notif.AddNotification(Locs.FeedbackModal_NotificationSuccess, Locs.FeedbackModal_Title, NotificationType.Success);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Log.Error("FeedbackPlugin was null.");
|
|
}
|
|
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
if (this.feedbackModalOnNextFrame)
|
|
{
|
|
ImGui.OpenPopup(modalTitle);
|
|
this.feedbackModalOnNextFrame = false;
|
|
this.feedbackModalDrawing = true;
|
|
this.feedbackModalBody = string.Empty;
|
|
this.feedbackModalContact = string.Empty;
|
|
this.feedbackModalIncludeException = false;
|
|
}
|
|
}
|
|
|
|
private void DrawChangelogList(bool displayDalamud, bool displayPlugins)
|
|
{
|
|
if (this.pluginListInstalled.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled);
|
|
return;
|
|
}
|
|
|
|
var pluginChangelogs = this.pluginListInstalled
|
|
.Where(plugin => !this.IsManifestFiltered(plugin.Manifest)
|
|
&& !plugin.Manifest.Changelog.IsNullOrEmpty())
|
|
.Select(x =>
|
|
{
|
|
var changelog = new PluginChangelogEntry(x);
|
|
return (IChangelogEntry)changelog;
|
|
});
|
|
|
|
IEnumerable<IChangelogEntry> changelogs = null;
|
|
if (displayDalamud && displayPlugins && this.dalamudChangelogManager.Changelogs != null)
|
|
{
|
|
changelogs = pluginChangelogs
|
|
.Concat(this.dalamudChangelogManager.Changelogs.Select(
|
|
x => new DalamudChangelogEntry(x)));
|
|
}
|
|
else if (displayDalamud && this.dalamudChangelogManager.Changelogs != null)
|
|
{
|
|
changelogs = this.dalamudChangelogManager.Changelogs.Select(
|
|
x => new DalamudChangelogEntry(x));
|
|
}
|
|
else if (displayPlugins)
|
|
{
|
|
changelogs = pluginChangelogs;
|
|
}
|
|
|
|
var sortedChangelogs = changelogs?.OrderByDescending(x => x.Date).ToList();
|
|
|
|
if (sortedChangelogs == null || !sortedChangelogs.Any())
|
|
{
|
|
ImGui.TextColored(
|
|
ImGuiColors.DalamudGrey2,
|
|
this.pluginListInstalled.Any(plugin => !plugin.Manifest.Changelog.IsNullOrEmpty())
|
|
? Locs.TabBody_SearchNoMatching
|
|
: Locs.TabBody_ChangelogNone);
|
|
|
|
return;
|
|
}
|
|
|
|
foreach (var logEntry in sortedChangelogs)
|
|
{
|
|
this.DrawChangelog(logEntry);
|
|
}
|
|
}
|
|
|
|
private void DrawAvailablePluginList()
|
|
{
|
|
var pluginList = this.pluginListAvailable;
|
|
|
|
if (pluginList.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoCompatible);
|
|
return;
|
|
}
|
|
|
|
var filteredManifests = pluginList
|
|
.Where(rm => !this.IsManifestFiltered(rm))
|
|
.ToList();
|
|
|
|
if (filteredManifests.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching);
|
|
return;
|
|
}
|
|
|
|
// get list to show and reset category dirty flag
|
|
var categoryManifestsList = this.categoryManager.GetCurrentCategoryContent(filteredManifests);
|
|
|
|
var i = 0;
|
|
foreach (var manifest in categoryManifestsList)
|
|
{
|
|
if (manifest is not RemotePluginManifest remoteManifest)
|
|
continue;
|
|
var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest);
|
|
|
|
ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}");
|
|
if (isInstalled)
|
|
{
|
|
this.DrawInstalledPlugin(plugin, i++, true);
|
|
}
|
|
else
|
|
{
|
|
this.DrawAvailablePlugin(remoteManifest, i++);
|
|
}
|
|
|
|
ImGui.PopID();
|
|
}
|
|
}
|
|
|
|
private void DrawInstalledPluginList()
|
|
{
|
|
var pluginList = this.pluginListInstalled;
|
|
|
|
if (pluginList.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled);
|
|
return;
|
|
}
|
|
|
|
var filteredList = pluginList
|
|
.Where(plugin => !this.IsManifestFiltered(plugin.Manifest))
|
|
.ToList();
|
|
|
|
if (filteredList.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching);
|
|
return;
|
|
}
|
|
|
|
var i = 0;
|
|
foreach (var plugin in filteredList)
|
|
{
|
|
this.DrawInstalledPlugin(plugin, i++);
|
|
}
|
|
}
|
|
|
|
private void DrawInstalledDevPluginList()
|
|
{
|
|
var pluginList = this.pluginListInstalled
|
|
.Where(plugin => plugin.IsDev)
|
|
.ToList();
|
|
|
|
if (pluginList.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled);
|
|
return;
|
|
}
|
|
|
|
var filteredList = pluginList
|
|
.Where(plugin => !this.IsManifestFiltered(plugin.Manifest))
|
|
.ToList();
|
|
|
|
if (filteredList.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching);
|
|
return;
|
|
}
|
|
|
|
var i = 0;
|
|
foreach (var plugin in filteredList)
|
|
{
|
|
this.DrawInstalledPlugin(plugin, i++);
|
|
}
|
|
}
|
|
|
|
private void DrawPluginCategories()
|
|
{
|
|
var useContentHeight = -40f; // button height + spacing
|
|
var useMenuWidth = 180f; // works fine as static value, table can be resized by user
|
|
|
|
var useContentWidth = ImGui.GetContentRegionAvail().X;
|
|
|
|
if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale)))
|
|
{
|
|
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0));
|
|
if (ImGui.BeginTable("##InstallerCategoriesCont", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV))
|
|
{
|
|
ImGui.TableSetupColumn("##InstallerCategoriesSelector", ImGuiTableColumnFlags.WidthFixed, useMenuWidth * ImGuiHelpers.GlobalScale);
|
|
ImGui.TableSetupColumn("##InstallerCategoriesBody", ImGuiTableColumnFlags.WidthStretch);
|
|
ImGui.TableNextRow();
|
|
|
|
ImGui.TableNextColumn();
|
|
this.DrawPluginCategorySelectors();
|
|
|
|
ImGui.TableNextColumn();
|
|
if (ImGui.BeginChild("ScrollingPlugins", new Vector2(-1, 0), false, ImGuiWindowFlags.NoBackground))
|
|
{
|
|
this.DrawPluginCategoryContent();
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
ImGui.EndTable();
|
|
}
|
|
|
|
ImGui.PopStyleVar();
|
|
ImGui.EndChild();
|
|
}
|
|
}
|
|
|
|
private void DrawPluginCategorySelectors()
|
|
{
|
|
var colorSearchHighlight = Vector4.One;
|
|
unsafe
|
|
{
|
|
var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight);
|
|
if (colorPtr != null)
|
|
{
|
|
colorSearchHighlight = *colorPtr;
|
|
}
|
|
}
|
|
|
|
for (var groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++)
|
|
{
|
|
var groupInfo = this.categoryManager.GroupList[groupIdx];
|
|
var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins;
|
|
if (!canShowGroup)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx);
|
|
if (ImGui.CollapsingHeader(groupInfo.Name, groupIdx == this.categoryManager.CurrentGroupIdx ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None))
|
|
{
|
|
if (this.categoryManager.CurrentGroupIdx != groupIdx)
|
|
{
|
|
this.categoryManager.CurrentGroupIdx = groupIdx;
|
|
}
|
|
|
|
ImGui.Indent();
|
|
var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight());
|
|
for (var categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++)
|
|
{
|
|
var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]);
|
|
|
|
var hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryId);
|
|
if (hasSearchHighlight)
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight);
|
|
}
|
|
|
|
if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize))
|
|
{
|
|
this.categoryManager.CurrentCategoryIdx = categoryIdx;
|
|
}
|
|
|
|
if (hasSearchHighlight)
|
|
{
|
|
ImGui.PopStyleColor();
|
|
}
|
|
}
|
|
|
|
ImGui.Unindent();
|
|
|
|
if (groupIdx != this.categoryManager.GroupList.Length - 1)
|
|
{
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawPluginCategoryContent()
|
|
{
|
|
var ready = this.DrawPluginListLoading();
|
|
if (!this.categoryManager.IsSelectionValid || !ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3));
|
|
|
|
var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx];
|
|
if (this.categoryManager.IsContentDirty)
|
|
{
|
|
// reset opened list of collapsibles when switching between categories
|
|
this.openPluginCollapsibles.Clear();
|
|
|
|
// do NOT reset dirty flag when Available group is selected, it will be handled by DrawAvailablePluginList()
|
|
if (groupInfo.GroupKind != PluginCategoryManager.GroupKind.Available)
|
|
{
|
|
this.categoryManager.ResetContentDirty();
|
|
}
|
|
}
|
|
|
|
switch (groupInfo.GroupKind)
|
|
{
|
|
case PluginCategoryManager.GroupKind.DevTools:
|
|
// this one is never sorted and remains in hardcoded order from group ctor
|
|
switch (this.categoryManager.CurrentCategoryIdx)
|
|
{
|
|
case 0:
|
|
this.DrawInstalledDevPluginList();
|
|
break;
|
|
|
|
case 1:
|
|
this.DrawImageTester();
|
|
break;
|
|
|
|
default:
|
|
// umm, there's nothing else, keep handled set and just skip drawing...
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case PluginCategoryManager.GroupKind.Installed:
|
|
this.DrawInstalledPluginList();
|
|
break;
|
|
case PluginCategoryManager.GroupKind.Changelog:
|
|
switch (this.categoryManager.CurrentCategoryIdx)
|
|
{
|
|
case 0:
|
|
this.DrawChangelogList(true, true);
|
|
break;
|
|
|
|
case 1:
|
|
this.DrawChangelogList(true, false);
|
|
break;
|
|
|
|
case 2:
|
|
this.DrawChangelogList(false, true);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
this.DrawAvailablePluginList();
|
|
break;
|
|
}
|
|
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
private void DrawImageTester()
|
|
{
|
|
var sectionSize = ImGuiHelpers.GlobalScale * 66;
|
|
var startCursor = ImGui.GetCursorPos();
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Button, true ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero);
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f));
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f));
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
|
|
|
|
ImGui.Button($"###pluginTesterCollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize));
|
|
|
|
ImGui.PopStyleVar();
|
|
|
|
ImGui.PopStyleColor(3);
|
|
|
|
ImGui.SetCursorPos(startCursor);
|
|
|
|
var hasIcon = this.testerIcon != null;
|
|
|
|
var iconTex = this.imageCache.DefaultIcon;
|
|
if (hasIcon) iconTex = this.testerIcon;
|
|
|
|
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
|
|
|
var cursorBeforeImage = ImGui.GetCursorPos();
|
|
ImGui.Image(iconTex.ImGuiHandle, iconSize);
|
|
ImGui.SameLine();
|
|
|
|
if (this.testerError)
|
|
{
|
|
ImGui.SetCursorPos(cursorBeforeImage);
|
|
ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize);
|
|
ImGui.SameLine();
|
|
}
|
|
else if (this.testerUpdateAvailable)
|
|
{
|
|
ImGui.SetCursorPos(cursorBeforeImage);
|
|
ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize);
|
|
ImGui.SameLine();
|
|
}
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
ImGui.SameLine();
|
|
|
|
var cursor = ImGui.GetCursorPos();
|
|
// Name
|
|
ImGui.Text("My Cool Plugin");
|
|
|
|
// Download count
|
|
var downloadCountText = Locs.PluginBody_AuthorWithDownloadCount("Plugin Enjoyer", 69420);
|
|
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText);
|
|
|
|
cursor.Y += ImGui.GetTextLineHeightWithSpacing();
|
|
ImGui.SetCursorPos(cursor);
|
|
|
|
// Description
|
|
ImGui.TextWrapped("This plugin does very many great things.");
|
|
|
|
startCursor.Y += sectionSize;
|
|
ImGui.SetCursorPos(startCursor);
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
ImGui.Indent();
|
|
|
|
// Description
|
|
ImGui.TextWrapped("This is a description.\nIt has multiple lines.\nTruly descriptive.");
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
// Controls
|
|
var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress;
|
|
|
|
var versionString = "1.0.0.0";
|
|
|
|
if (disabled)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString));
|
|
}
|
|
else
|
|
{
|
|
var buttonText = Locs.PluginButton_InstallVersion(versionString);
|
|
ImGui.Button($"{buttonText}##{buttonText}testing");
|
|
}
|
|
|
|
this.DrawVisitRepoUrlButton("https://google.com");
|
|
|
|
if (this.testerImages != null)
|
|
{
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
const float thumbFactor = 2.7f;
|
|
|
|
var scrollBarSize = 15;
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, scrollBarSize);
|
|
ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Vector4.Zero);
|
|
|
|
var width = ImGui.GetWindowWidth();
|
|
|
|
if (ImGui.BeginChild(
|
|
"pluginTestingImageScrolling",
|
|
new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize),
|
|
false,
|
|
ImGuiWindowFlags.HorizontalScrollbar |
|
|
ImGuiWindowFlags.NoScrollWithMouse |
|
|
ImGuiWindowFlags.NoBackground))
|
|
{
|
|
if (this.testerImages != null && this.testerImages is { Length: > 0 })
|
|
{
|
|
for (var i = 0; i < this.testerImages.Length; i++)
|
|
{
|
|
var popupId = $"pluginTestingImage{i}";
|
|
var image = this.testerImages[i];
|
|
if (image == null)
|
|
continue;
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0);
|
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
|
|
|
if (ImGui.BeginPopup(popupId))
|
|
{
|
|
if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height)))
|
|
ImGui.CloseCurrentPopup();
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
ImGui.PopStyleVar(3);
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
|
|
|
float xAct = image.Width;
|
|
float yAct = image.Height;
|
|
float xMax = PluginImageCache.PluginImageWidth;
|
|
float yMax = PluginImageCache.PluginImageHeight;
|
|
|
|
// scale image if undersized
|
|
if (xAct < xMax && yAct < yMax)
|
|
{
|
|
var scale = Math.Min(xMax / xAct, yMax / yAct);
|
|
xAct *= scale;
|
|
yAct *= scale;
|
|
}
|
|
|
|
var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor);
|
|
if (ImGui.ImageButton(image.ImGuiHandle, size))
|
|
ImGui.OpenPopup(popupId);
|
|
|
|
ImGui.PopStyleVar();
|
|
|
|
if (i < this.testerImages.Length - 1)
|
|
{
|
|
ImGui.SameLine();
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
ImGui.SameLine();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
|
|
ImGui.PopStyleVar();
|
|
ImGui.PopStyleColor();
|
|
|
|
ImGui.Unindent();
|
|
}
|
|
|
|
ImGuiHelpers.ScaledDummy(20);
|
|
|
|
ImGui.InputText("Icon Path", ref this.testerIconPath, 1000);
|
|
ImGui.InputText("Image 1 Path", ref this.testerImagePaths[0], 1000);
|
|
ImGui.InputText("Image 2 Path", ref this.testerImagePaths[1], 1000);
|
|
ImGui.InputText("Image 3 Path", ref this.testerImagePaths[2], 1000);
|
|
ImGui.InputText("Image 4 Path", ref this.testerImagePaths[3], 1000);
|
|
ImGui.InputText("Image 5 Path", ref this.testerImagePaths[4], 1000);
|
|
|
|
var im = Service<InterfaceManager>.Get();
|
|
if (ImGui.Button("Load"))
|
|
{
|
|
try
|
|
{
|
|
if (this.testerIcon != null)
|
|
{
|
|
this.testerIcon.Dispose();
|
|
this.testerIcon = null;
|
|
}
|
|
|
|
if (!this.testerIconPath.IsNullOrEmpty())
|
|
{
|
|
this.testerIcon = im.LoadImage(this.testerIconPath);
|
|
}
|
|
|
|
this.testerImages = new TextureWrap[this.testerImagePaths.Length];
|
|
|
|
for (var i = 0; i < this.testerImagePaths.Length; i++)
|
|
{
|
|
if (this.testerImagePaths[i].IsNullOrEmpty())
|
|
continue;
|
|
|
|
if (this.testerImages[i] != null)
|
|
{
|
|
this.testerImages[i].Dispose();
|
|
this.testerImages[i] = null;
|
|
}
|
|
|
|
this.testerImages[i] = im.LoadImage(this.testerImagePaths[i]);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Could not load plugin images for testing.");
|
|
}
|
|
}
|
|
|
|
ImGui.Checkbox("Failed", ref this.testerError);
|
|
ImGui.Checkbox("Has Update", ref this.testerUpdateAvailable);
|
|
}
|
|
|
|
private bool DrawPluginListLoading()
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
var ready = pluginManager.PluginsReady && pluginManager.ReposReady;
|
|
|
|
if (!ready)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_LoadingPlugins);
|
|
}
|
|
|
|
var failedRepos = pluginManager.Repos
|
|
.Where(repo => repo.State == PluginRepositoryState.Fail)
|
|
.ToArray();
|
|
|
|
if (failedRepos.Length > 0)
|
|
{
|
|
var failText = Locs.TabBody_DownloadFailed;
|
|
var aggFailText = failedRepos
|
|
.Select(repo => $"{failText} ({repo.PluginMasterUrl})")
|
|
.Aggregate((s1, s2) => $"{s1}\n{s2}");
|
|
|
|
ImGui.TextColored(ImGuiColors.DalamudRed, aggFailText);
|
|
}
|
|
|
|
return ready;
|
|
}
|
|
|
|
private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, Action drawContextMenuAction, int index)
|
|
{
|
|
ImGui.Separator();
|
|
|
|
var isOpen = this.openPluginCollapsibles.Contains(index);
|
|
|
|
var sectionSize = ImGuiHelpers.GlobalScale * 66;
|
|
var startCursor = ImGui.GetCursorPos();
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Button, isOpen ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero);
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f));
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f));
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
|
|
|
|
if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize)))
|
|
{
|
|
if (isOpen)
|
|
{
|
|
this.openPluginCollapsibles.Remove(index);
|
|
}
|
|
else
|
|
{
|
|
this.openPluginCollapsibles.Add(index);
|
|
}
|
|
|
|
isOpen = !isOpen;
|
|
}
|
|
|
|
drawContextMenuAction?.Invoke();
|
|
|
|
ImGui.PopStyleVar();
|
|
|
|
ImGui.PopStyleColor(3);
|
|
|
|
ImGui.SetCursorPos(startCursor);
|
|
|
|
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
|
var cursorBeforeImage = ImGui.GetCursorPos();
|
|
var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos();
|
|
if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize))
|
|
{
|
|
var iconTex = this.imageCache.DefaultIcon;
|
|
var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex);
|
|
if (hasIcon && cachedIconTex != null)
|
|
{
|
|
iconTex = cachedIconTex;
|
|
}
|
|
|
|
ImGui.Image(iconTex.ImGuiHandle, iconSize);
|
|
ImGui.SameLine();
|
|
ImGui.SetCursorPos(cursorBeforeImage);
|
|
}
|
|
|
|
var isLoaded = plugin is { IsLoaded: true };
|
|
|
|
if (updateAvailable)
|
|
ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize);
|
|
else if (trouble)
|
|
ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize);
|
|
else if (isLoaded && isThirdParty)
|
|
ImGui.Image(this.imageCache.ThirdInstalledIcon.ImGuiHandle, iconSize);
|
|
else if (isThirdParty)
|
|
ImGui.Image(this.imageCache.ThirdIcon.ImGuiHandle, iconSize);
|
|
else if (isLoaded)
|
|
ImGui.Image(this.imageCache.InstalledIcon.ImGuiHandle, iconSize);
|
|
else
|
|
ImGui.Dummy(iconSize);
|
|
ImGui.SameLine();
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
ImGui.SameLine();
|
|
|
|
var cursor = ImGui.GetCursorPos();
|
|
|
|
// Name
|
|
ImGui.TextUnformatted(label);
|
|
|
|
// Download count
|
|
var downloadCountText = manifest.DownloadCount > 0
|
|
? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount)
|
|
: Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author);
|
|
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText);
|
|
|
|
if (isNew)
|
|
{
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.TankBlue, Locs.PluginTitleMod_New);
|
|
}
|
|
|
|
cursor.Y += ImGui.GetTextLineHeightWithSpacing();
|
|
ImGui.SetCursorPos(cursor);
|
|
|
|
// Outdated warning
|
|
if (plugin is { IsOutdated: true, IsBanned: false })
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
|
ImGui.TextWrapped(Locs.PluginBody_Outdated);
|
|
ImGui.PopStyleColor();
|
|
}
|
|
else if (plugin is { IsBanned: true }) // Banned warning
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
|
ImGuiHelpers.SafeTextWrapped(plugin.BanReason.IsNullOrEmpty()
|
|
? Locs.PluginBody_Banned
|
|
: Locs.PluginBody_BannedReason(plugin.BanReason));
|
|
|
|
ImGui.PopStyleColor();
|
|
}
|
|
else if (plugin is { IsOrphaned: true })
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
|
ImGui.TextWrapped(Locs.PluginBody_Orphaned);
|
|
ImGui.PopStyleColor();
|
|
}
|
|
else if (plugin != null && !plugin.CheckPolicy())
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
|
ImGui.TextWrapped(Locs.PluginBody_Policy);
|
|
ImGui.PopStyleColor();
|
|
}
|
|
else if (plugin is { State: PluginState.LoadError or PluginState.DependencyResolutionFailed }) // Load failed warning
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
|
ImGui.TextWrapped(Locs.PluginBody_LoadFailed);
|
|
ImGui.PopStyleColor();
|
|
}
|
|
|
|
ImGui.SetCursorPosX(cursor.X);
|
|
|
|
// Description
|
|
if (plugin is null or { IsOutdated: false, IsBanned: false })
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(manifest.Punchline))
|
|
{
|
|
ImGuiHelpers.SafeTextWrapped(manifest.Punchline);
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(manifest.Description))
|
|
{
|
|
const int punchlineLen = 200;
|
|
var firstLine = manifest.Description.Split(new[] { '\r', '\n' })[0];
|
|
|
|
ImGuiHelpers.SafeTextWrapped(firstLine.Length < punchlineLen
|
|
? firstLine
|
|
: firstLine[..punchlineLen]);
|
|
}
|
|
}
|
|
|
|
startCursor.Y += sectionSize;
|
|
ImGui.SetCursorPos(startCursor);
|
|
|
|
return isOpen;
|
|
}
|
|
|
|
private void DrawChangelog(IChangelogEntry log)
|
|
{
|
|
ImGui.Separator();
|
|
|
|
var startCursor = ImGui.GetCursorPos();
|
|
|
|
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
|
var cursorBeforeImage = ImGui.GetCursorPos();
|
|
var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos();
|
|
if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize))
|
|
{
|
|
TextureWrap icon;
|
|
if (log is PluginChangelogEntry pluginLog)
|
|
{
|
|
icon = this.imageCache.DefaultIcon;
|
|
var hasIcon = this.imageCache.TryGetIcon(pluginLog.Plugin, pluginLog.Plugin.Manifest, pluginLog.Plugin.Manifest.IsThirdParty, out var cachedIconTex);
|
|
if (hasIcon && cachedIconTex != null)
|
|
{
|
|
icon = cachedIconTex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
icon = this.imageCache.CorePluginIcon;
|
|
}
|
|
|
|
ImGui.Image(icon.ImGuiHandle, iconSize);
|
|
}
|
|
else
|
|
{
|
|
ImGui.Dummy(iconSize);
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
ImGui.SameLine();
|
|
var cursor = ImGui.GetCursorPos();
|
|
ImGui.TextUnformatted(log.Title);
|
|
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{log.Version}");
|
|
|
|
cursor.Y += ImGui.GetTextLineHeightWithSpacing();
|
|
ImGui.SetCursorPos(cursor);
|
|
|
|
ImGuiHelpers.SafeTextWrapped(log.Text);
|
|
|
|
var endCursor = ImGui.GetCursorPos();
|
|
|
|
var sectionSize = Math.Max(
|
|
66 * ImGuiHelpers.GlobalScale, // min size due to icons
|
|
endCursor.Y - startCursor.Y);
|
|
|
|
startCursor.Y += sectionSize;
|
|
ImGui.SetCursorPos(startCursor);
|
|
}
|
|
|
|
private void DrawAvailablePlugin(RemotePluginManifest manifest, int index)
|
|
{
|
|
var configuration = Service<DalamudConfiguration>.Get();
|
|
var notifications = Service<NotificationManager>.Get();
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
var useTesting = PluginManager.UseTesting(manifest);
|
|
var wasSeen = this.WasPluginSeen(manifest.InternalName);
|
|
|
|
// Check for valid versions
|
|
if ((useTesting && manifest.TestingAssemblyVersion == null) || manifest.AssemblyVersion == null)
|
|
{
|
|
// Without a valid version, quit
|
|
return;
|
|
}
|
|
|
|
// Name
|
|
var label = manifest.Name;
|
|
|
|
// Testing
|
|
if (useTesting)
|
|
{
|
|
label += Locs.PluginTitleMod_TestingVersion;
|
|
}
|
|
|
|
ImGui.PushID($"available{index}{manifest.InternalName}");
|
|
|
|
var isThirdParty = manifest.SourceRepo.IsThirdParty;
|
|
if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, () => this.DrawAvailablePluginContextMenu(manifest), index))
|
|
{
|
|
if (!wasSeen)
|
|
configuration.SeenPluginInternalName.Add(manifest.InternalName);
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
ImGui.Indent();
|
|
|
|
// Installable from
|
|
if (manifest.SourceRepo.IsThirdParty)
|
|
{
|
|
var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.SourceRepo.PluginMasterUrl);
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText);
|
|
|
|
ImGuiHelpers.ScaledDummy(2);
|
|
}
|
|
|
|
// Description
|
|
if (!string.IsNullOrWhiteSpace(manifest.Description))
|
|
{
|
|
ImGuiHelpers.SafeTextWrapped(manifest.Description);
|
|
}
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
// Controls
|
|
var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress;
|
|
|
|
var versionString = useTesting
|
|
? $"{manifest.TestingAssemblyVersion}"
|
|
: $"{manifest.AssemblyVersion}";
|
|
|
|
if (pluginManager.SafeMode)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_SafeMode);
|
|
}
|
|
else if (disabled)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString));
|
|
}
|
|
else
|
|
{
|
|
var buttonText = Locs.PluginButton_InstallVersion(versionString);
|
|
if (ImGui.Button($"{buttonText}##{buttonText}{index}"))
|
|
{
|
|
this.installStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting, PluginLoadReason.Installer))
|
|
.ContinueWith(task =>
|
|
{
|
|
// There is no need to set as Complete for an individual plugin installation
|
|
this.installStatus = OperationStatus.Idle;
|
|
if (this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name)))
|
|
{
|
|
if (task.Result.State == PluginState.Loaded)
|
|
{
|
|
notifications.AddNotification(Locs.Notifications_PluginInstalled(manifest.Name), Locs.Notifications_PluginInstalledTitle, NotificationType.Success);
|
|
}
|
|
else
|
|
{
|
|
notifications.AddNotification(Locs.Notifications_PluginNotInstalled(manifest.Name), Locs.Notifications_PluginNotInstalledTitle, NotificationType.Error);
|
|
this.ShowErrorModal(Locs.ErrorModal_InstallFail(manifest.Name));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
this.DrawVisitRepoUrlButton(manifest.RepoUrl);
|
|
|
|
if (!manifest.SourceRepo.IsThirdParty && manifest.AcceptsFeedback)
|
|
{
|
|
this.DrawSendFeedbackButton(manifest, false);
|
|
}
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
if (this.DrawPluginImages(null, manifest, isThirdParty, index))
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
ImGui.Unindent();
|
|
}
|
|
|
|
ImGui.PopID();
|
|
}
|
|
|
|
private void DrawAvailablePluginContextMenu(PluginManifest manifest)
|
|
{
|
|
var configuration = Service<DalamudConfiguration>.Get();
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
var startInfo = Service<DalamudStartInfo>.Get();
|
|
|
|
if (ImGui.BeginPopupContextItem("ItemContextMenu"))
|
|
{
|
|
if (ImGui.Selectable(Locs.PluginContext_MarkAllSeen))
|
|
{
|
|
configuration.SeenPluginInternalName.AddRange(this.pluginListAvailable.Select(x => x.InternalName));
|
|
configuration.Save();
|
|
pluginManager.RefilterPluginMasters();
|
|
}
|
|
|
|
if (ImGui.Selectable(Locs.PluginContext_HidePlugin))
|
|
{
|
|
Log.Debug($"Adding {manifest.InternalName} to hidden plugins");
|
|
configuration.HiddenPluginInternalName.Add(manifest.InternalName);
|
|
configuration.Save();
|
|
pluginManager.RefilterPluginMasters();
|
|
}
|
|
|
|
if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfig))
|
|
{
|
|
Log.Debug($"Deleting config for {manifest.InternalName}");
|
|
|
|
this.installStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(() =>
|
|
{
|
|
pluginManager.PluginConfigs.Delete(manifest.InternalName);
|
|
|
|
var path = Path.Combine(startInfo.PluginDirectory, manifest.InternalName);
|
|
if (Directory.Exists(path))
|
|
Directory.Delete(path, true);
|
|
})
|
|
.ContinueWith(task =>
|
|
{
|
|
this.installStatus = OperationStatus.Idle;
|
|
|
|
this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(manifest.InternalName));
|
|
});
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
|
|
private void DrawInstalledPlugin(LocalPlugin plugin, int index, bool showInstalled = false)
|
|
{
|
|
var configuration = Service<DalamudConfiguration>.Get();
|
|
var commandManager = Service<CommandManager>.Get();
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
var startInfo = Service<DalamudStartInfo>.Get();
|
|
|
|
var trouble = false;
|
|
|
|
// Name
|
|
var label = plugin.Manifest.Name;
|
|
|
|
// Testing
|
|
if (plugin.Manifest.Testing)
|
|
{
|
|
label += Locs.PluginTitleMod_TestingVersion;
|
|
}
|
|
|
|
// Freshly installed
|
|
if (showInstalled)
|
|
{
|
|
label += Locs.PluginTitleMod_Installed;
|
|
}
|
|
|
|
// Disabled
|
|
if (plugin.IsDisabled || !plugin.CheckPolicy())
|
|
{
|
|
label += Locs.PluginTitleMod_Disabled;
|
|
trouble = true;
|
|
}
|
|
|
|
// Load error
|
|
if (plugin.State is PluginState.LoadError or PluginState.DependencyResolutionFailed && plugin.CheckPolicy())
|
|
{
|
|
label += Locs.PluginTitleMod_LoadError;
|
|
trouble = true;
|
|
}
|
|
|
|
// Unload error
|
|
if (plugin.State == PluginState.UnloadError)
|
|
{
|
|
label += Locs.PluginTitleMod_UnloadError;
|
|
trouble = true;
|
|
}
|
|
|
|
var availablePluginUpdate = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin);
|
|
// Update available
|
|
if (availablePluginUpdate != default)
|
|
{
|
|
label += Locs.PluginTitleMod_HasUpdate;
|
|
}
|
|
|
|
// Freshly updated
|
|
var thisWasUpdated = false;
|
|
if (this.updatedPlugins != null && !plugin.IsDev)
|
|
{
|
|
var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName);
|
|
if (update != default)
|
|
{
|
|
if (update.WasUpdated)
|
|
{
|
|
thisWasUpdated = true;
|
|
label += Locs.PluginTitleMod_Updated;
|
|
}
|
|
else
|
|
{
|
|
label += Locs.PluginTitleMod_UpdateFailed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Outdated API level
|
|
if (plugin.IsOutdated)
|
|
{
|
|
label += Locs.PluginTitleMod_OutdatedError;
|
|
trouble = true;
|
|
}
|
|
|
|
// Banned
|
|
if (plugin.IsBanned)
|
|
{
|
|
label += Locs.PluginTitleMod_BannedError;
|
|
trouble = true;
|
|
}
|
|
|
|
// Orphaned
|
|
if (plugin.IsOrphaned)
|
|
{
|
|
label += Locs.PluginTitleMod_OrphanedError;
|
|
trouble = true;
|
|
}
|
|
|
|
ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}");
|
|
var hasChangelog = !plugin.Manifest.Changelog.IsNullOrEmpty();
|
|
|
|
if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, () => this.DrawInstalledPluginContextMenu(plugin), index))
|
|
{
|
|
if (!this.WasPluginSeen(plugin.Manifest.InternalName))
|
|
configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName);
|
|
|
|
var manifest = plugin.Manifest;
|
|
|
|
ImGui.Indent();
|
|
|
|
// Name
|
|
ImGui.TextUnformatted(manifest.Name);
|
|
|
|
// Download count
|
|
var downloadText = plugin.IsDev
|
|
? Locs.PluginBody_AuthorWithoutDownloadCount(manifest.Author)
|
|
: manifest.DownloadCount > 0
|
|
? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount)
|
|
: Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author);
|
|
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText);
|
|
|
|
var isThirdParty = manifest.IsThirdParty;
|
|
var canFeedback = !isThirdParty && !plugin.IsDev && plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel && plugin.Manifest.AcceptsFeedback && availablePluginUpdate == default;
|
|
|
|
// Installed from
|
|
if (plugin.IsDev)
|
|
{
|
|
var fileText = Locs.PluginBody_DevPluginPath(plugin.DllFile.FullName);
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, fileText);
|
|
}
|
|
else if (isThirdParty)
|
|
{
|
|
var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.InstalledFromUrl);
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText);
|
|
}
|
|
|
|
// Description
|
|
if (!string.IsNullOrWhiteSpace(manifest.Description))
|
|
{
|
|
ImGuiHelpers.SafeTextWrapped(manifest.Description);
|
|
}
|
|
|
|
// Available commands (if loaded)
|
|
if (plugin.IsLoaded)
|
|
{
|
|
var commands = commandManager.Commands
|
|
.Where(cInfo => cInfo.Value.ShowInHelp && cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName)
|
|
.ToArray();
|
|
|
|
if (commands.Any())
|
|
{
|
|
ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f));
|
|
foreach (var command in commands)
|
|
{
|
|
ImGuiHelpers.SafeTextWrapped($"{command.Key} → {command.Value.HelpMessage}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Controls
|
|
this.DrawPluginControlButton(plugin);
|
|
this.DrawDevPluginButtons(plugin);
|
|
this.DrawDeletePluginButton(plugin);
|
|
this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl);
|
|
|
|
if (canFeedback)
|
|
{
|
|
this.DrawSendFeedbackButton(plugin.Manifest, plugin.IsTesting);
|
|
}
|
|
|
|
if (availablePluginUpdate != default)
|
|
this.DrawUpdateSinglePluginButton(availablePluginUpdate);
|
|
|
|
ImGui.SameLine();
|
|
var version = plugin.AssemblyName?.Version;
|
|
version ??= plugin.Manifest.Testing
|
|
? plugin.Manifest.TestingAssemblyVersion
|
|
: plugin.Manifest.AssemblyVersion;
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{version}");
|
|
|
|
if (plugin.IsDev)
|
|
{
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin);
|
|
}
|
|
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
if (this.DrawPluginImages(plugin, manifest, isThirdParty, index))
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
ImGui.Unindent();
|
|
|
|
if (hasChangelog)
|
|
{
|
|
if (ImGui.TreeNode($"Changelog (v{plugin.Manifest.AssemblyVersion})"))
|
|
{
|
|
this.DrawInstalledPluginChangelog(plugin.Manifest);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (thisWasUpdated && hasChangelog)
|
|
{
|
|
this.DrawInstalledPluginChangelog(plugin.Manifest);
|
|
}
|
|
|
|
ImGui.PopID();
|
|
}
|
|
|
|
private void DrawInstalledPluginChangelog(PluginManifest manifest)
|
|
{
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.ChildBg, this.changelogBgColor);
|
|
ImGui.PushStyleColor(ImGuiCol.Text, this.changelogTextColor);
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(7, 5));
|
|
|
|
if (ImGui.BeginChild("##changelog", new Vector2(-1, 100), true, ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.AlwaysAutoResize))
|
|
{
|
|
ImGui.Text("Changelog:");
|
|
ImGuiHelpers.ScaledDummy(2);
|
|
ImGuiHelpers.SafeTextWrapped(manifest.Changelog);
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
|
|
ImGui.PopStyleVar();
|
|
ImGui.PopStyleColor(2);
|
|
}
|
|
|
|
private void DrawInstalledPluginContextMenu(LocalPlugin plugin)
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
if (ImGui.BeginPopupContextItem("InstalledItemContextMenu"))
|
|
{
|
|
if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfigReload))
|
|
{
|
|
Log.Debug($"Deleting config for {plugin.Manifest.InternalName}");
|
|
|
|
this.installStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(() => pluginManager.DeleteConfigurationAsync(plugin))
|
|
.ContinueWith(task =>
|
|
{
|
|
this.installStatus = OperationStatus.Idle;
|
|
|
|
this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name));
|
|
});
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
|
|
private void DrawPluginControlButton(LocalPlugin plugin)
|
|
{
|
|
var configuration = Service<DalamudConfiguration>.Get();
|
|
var notifications = Service<NotificationManager>.Get();
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
var startInfo = Service<DalamudStartInfo>.Get();
|
|
|
|
// Disable everything if the updater is running or another plugin is operating
|
|
var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress;
|
|
|
|
// Disable everything if the plugin is outdated
|
|
disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned;
|
|
|
|
// Disable everything if the plugin is orphaned
|
|
disabled = disabled || plugin.IsOrphaned;
|
|
|
|
// Disable everything if the plugin failed to load
|
|
disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
|
|
|
|
if (plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_Working);
|
|
}
|
|
else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed)
|
|
{
|
|
if (pluginManager.SafeMode)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_SafeMode);
|
|
}
|
|
else if (disabled)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_Disable);
|
|
}
|
|
else
|
|
{
|
|
if (ImGui.Button(Locs.PluginButton_Disable))
|
|
{
|
|
Task.Run(() =>
|
|
{
|
|
var unloadTask = Task.Run(() => plugin.UnloadAsync())
|
|
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name));
|
|
|
|
unloadTask.Wait();
|
|
if (!unloadTask.Result)
|
|
return;
|
|
|
|
var disableTask = Task.Run(() => plugin.Disable())
|
|
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name));
|
|
|
|
disableTask.Wait();
|
|
if (!disableTask.Result)
|
|
return;
|
|
|
|
if (!plugin.IsDev)
|
|
{
|
|
pluginManager.RemovePlugin(plugin);
|
|
}
|
|
|
|
notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success);
|
|
});
|
|
}
|
|
}
|
|
|
|
if (plugin.State == PluginState.Loaded)
|
|
{
|
|
// Only if the plugin isn't broken.
|
|
this.DrawOpenPluginSettingsButton(plugin);
|
|
}
|
|
}
|
|
else if (plugin.State == PluginState.Unloaded)
|
|
{
|
|
if (disabled)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_Load);
|
|
}
|
|
else
|
|
{
|
|
if (ImGui.Button(Locs.PluginButton_Load))
|
|
{
|
|
Task.Run(() =>
|
|
{
|
|
var enableTask = Task.Run(() => plugin.Enable())
|
|
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name));
|
|
|
|
enableTask.Wait();
|
|
if (!enableTask.Result)
|
|
return;
|
|
|
|
var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer))
|
|
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name));
|
|
|
|
loadTask.Wait();
|
|
if (!loadTask.Result)
|
|
return;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
else if (plugin.State == PluginState.UnloadError)
|
|
{
|
|
ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown);
|
|
|
|
if (ImGui.IsItemHovered())
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed);
|
|
}
|
|
}
|
|
|
|
private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update)
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
ImGui.SameLine();
|
|
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Download))
|
|
{
|
|
this.installStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(async () => await pluginManager.UpdateSinglePluginAsync(update, true, false))
|
|
.ContinueWith(task =>
|
|
{
|
|
// There is no need to set as Complete for an individual plugin installation
|
|
this.installStatus = OperationStatus.Idle;
|
|
|
|
var errorMessage = Locs.ErrorModal_SingleUpdateFail(update.UpdateManifest.Name);
|
|
this.DisplayErrorContinuation(task, errorMessage);
|
|
|
|
if (!task.Result.WasUpdated)
|
|
{
|
|
this.ShowErrorModal(errorMessage);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
var updateVersion = update.UseTesting
|
|
? update.UpdateManifest.TestingAssemblyVersion
|
|
: update.UpdateManifest.AssemblyVersion;
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_UpdateSingle(updateVersion.ToString()));
|
|
}
|
|
}
|
|
|
|
private void DrawOpenPluginSettingsButton(LocalPlugin plugin)
|
|
{
|
|
if (plugin.DalamudInterface?.UiBuilder?.HasConfigUi ?? false)
|
|
{
|
|
ImGui.SameLine();
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog))
|
|
{
|
|
try
|
|
{
|
|
plugin.DalamudInterface.UiBuilder.OpenConfig();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Error during OpenConfigUi: {plugin.Name}");
|
|
}
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_OpenConfiguration);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawSendFeedbackButton(PluginManifest manifest, bool isTesting)
|
|
{
|
|
ImGui.SameLine();
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Comment))
|
|
{
|
|
this.feedbackPlugin = manifest;
|
|
this.feedbackModalOnNextFrame = true;
|
|
this.feedbackIsTesting = isTesting;
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.FeedbackModal_Title);
|
|
}
|
|
}
|
|
|
|
private void DrawDevPluginButtons(LocalPlugin localPlugin)
|
|
{
|
|
var configuration = Service<DalamudConfiguration>.Get();
|
|
|
|
if (localPlugin is LocalDevPlugin plugin)
|
|
{
|
|
// https://colorswall.com/palette/2868/
|
|
var greenColor = new Vector4(0x5C, 0xB8, 0x5C, 0xFF) / 0xFF;
|
|
var redColor = new Vector4(0xD9, 0x53, 0x4F, 0xFF) / 0xFF;
|
|
|
|
// Load on boot
|
|
ImGui.PushStyleColor(ImGuiCol.Button, plugin.StartOnBoot ? greenColor : redColor);
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.StartOnBoot ? greenColor : redColor);
|
|
|
|
ImGui.SameLine();
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.PowerOff))
|
|
{
|
|
plugin.StartOnBoot ^= true;
|
|
configuration.Save();
|
|
}
|
|
|
|
ImGui.PopStyleColor(2);
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_StartOnBoot);
|
|
}
|
|
|
|
// Automatic reload
|
|
ImGui.PushStyleColor(ImGuiCol.Button, plugin.AutomaticReload ? greenColor : redColor);
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.AutomaticReload ? greenColor : redColor);
|
|
|
|
ImGui.SameLine();
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.SyncAlt))
|
|
{
|
|
plugin.AutomaticReload ^= true;
|
|
configuration.Save();
|
|
}
|
|
|
|
ImGui.PopStyleColor(2);
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawDeletePluginButton(LocalPlugin plugin)
|
|
{
|
|
var unloaded = plugin.State == PluginState.Unloaded || plugin.State == PluginState.LoadError;
|
|
|
|
// When policy check fails, the plugin is never loaded
|
|
var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned || plugin.IsOrphaned || !plugin.CheckPolicy());
|
|
|
|
if (!showButton)
|
|
return;
|
|
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
ImGui.SameLine();
|
|
if (plugin.HasEverStartedLoad)
|
|
{
|
|
ImGui.PushFont(InterfaceManager.IconFont);
|
|
ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString());
|
|
ImGui.PopFont();
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePluginRestricted);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt))
|
|
{
|
|
try
|
|
{
|
|
plugin.DllFile.Delete();
|
|
pluginManager.RemovePlugin(plugin);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}");
|
|
|
|
this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name);
|
|
this.errorModalDrawing = true;
|
|
this.errorModalOnNextFrame = true;
|
|
}
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawVisitRepoUrlButton(string? repoUrl)
|
|
{
|
|
if (!string.IsNullOrEmpty(repoUrl) && repoUrl.StartsWith("https://"))
|
|
{
|
|
ImGui.SameLine();
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Globe))
|
|
{
|
|
try
|
|
{
|
|
_ = Process.Start(new ProcessStartInfo()
|
|
{
|
|
FileName = repoUrl,
|
|
UseShellExecute = true,
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Could not open repoUrl: {repoUrl}");
|
|
}
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_VisitPluginUrl);
|
|
}
|
|
}
|
|
|
|
private bool DrawPluginImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, int index)
|
|
{
|
|
var hasImages = this.imageCache.TryGetImages(plugin, manifest, isThirdParty, out var imageTextures);
|
|
if (!hasImages || imageTextures.All(x => x == null))
|
|
return false;
|
|
|
|
const float thumbFactor = 2.7f;
|
|
|
|
var scrollBarSize = 15;
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, scrollBarSize);
|
|
ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Vector4.Zero);
|
|
|
|
var width = ImGui.GetWindowWidth();
|
|
|
|
if (ImGui.BeginChild($"plugin{index}ImageScrolling", new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground))
|
|
{
|
|
for (var i = 0; i < imageTextures.Length; i++)
|
|
{
|
|
var image = imageTextures[i];
|
|
if (image == null)
|
|
continue;
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0);
|
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
|
|
|
var popupId = $"plugin{index}image{i}";
|
|
if (ImGui.BeginPopup(popupId))
|
|
{
|
|
if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height)))
|
|
ImGui.CloseCurrentPopup();
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
ImGui.PopStyleVar(3);
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
|
|
|
float xAct = image.Width;
|
|
float yAct = image.Height;
|
|
float xMax = PluginImageCache.PluginImageWidth;
|
|
float yMax = PluginImageCache.PluginImageHeight;
|
|
|
|
// scale image if undersized
|
|
if (xAct < xMax && yAct < yMax)
|
|
{
|
|
var scale = Math.Min(xMax / xAct, yMax / yAct);
|
|
xAct *= scale;
|
|
yAct *= scale;
|
|
}
|
|
|
|
var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor);
|
|
if (ImGui.ImageButton(image.ImGuiHandle, size))
|
|
ImGui.OpenPopup(popupId);
|
|
|
|
ImGui.PopStyleVar();
|
|
|
|
if (i < imageTextures.Length - 1)
|
|
{
|
|
ImGui.SameLine();
|
|
ImGuiHelpers.ScaledDummy(5);
|
|
ImGui.SameLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
|
|
ImGui.PopStyleVar();
|
|
ImGui.PopStyleColor();
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool IsManifestFiltered(PluginManifest manifest)
|
|
{
|
|
var searchString = this.searchText.ToLowerInvariant();
|
|
var hasSearchString = !string.IsNullOrWhiteSpace(searchString);
|
|
|
|
return hasSearchString && !(
|
|
manifest.Name.ToLowerInvariant().Contains(searchString) ||
|
|
(!manifest.Author.IsNullOrEmpty() && manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase)) ||
|
|
(!manifest.Punchline.IsNullOrEmpty() && manifest.Punchline.ToLowerInvariant().Contains(searchString)) ||
|
|
(manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase)));
|
|
}
|
|
|
|
private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(RemotePluginManifest? manifest)
|
|
{
|
|
if (manifest == null) return (false, default);
|
|
|
|
var plugin = this.pluginListInstalled.FirstOrDefault(plugin => plugin.Manifest.InternalName == manifest.InternalName);
|
|
var isInstalled = plugin != default;
|
|
|
|
return (isInstalled, plugin);
|
|
}
|
|
|
|
private void OnAvailablePluginsChanged()
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
// By removing installed plugins only when the available plugin list changes (basically when the window is
|
|
// opened), plugins that have been newly installed remain in the available plugin list as installed.
|
|
this.pluginListAvailable = pluginManager.AvailablePlugins.ToList();
|
|
this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList();
|
|
this.ResortPlugins();
|
|
|
|
this.UpdateCategoriesOnPluginsChange();
|
|
}
|
|
|
|
private void OnInstalledPluginsChanged()
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
this.pluginListInstalled = pluginManager.InstalledPlugins.ToList();
|
|
this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList();
|
|
this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev);
|
|
this.ResortPlugins();
|
|
|
|
this.UpdateCategoriesOnPluginsChange();
|
|
}
|
|
|
|
private void ResortPlugins()
|
|
{
|
|
switch (this.sortKind)
|
|
{
|
|
case PluginSortKind.Alphabetical:
|
|
this.pluginListAvailable.Sort((p1, p2) => p1.Name.CompareTo(p2.Name));
|
|
this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name));
|
|
break;
|
|
case PluginSortKind.DownloadCount:
|
|
this.pluginListAvailable.Sort((p1, p2) => p2.DownloadCount.CompareTo(p1.DownloadCount));
|
|
this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.DownloadCount.CompareTo(p1.Manifest.DownloadCount));
|
|
break;
|
|
case PluginSortKind.LastUpdate:
|
|
this.pluginListAvailable.Sort((p1, p2) => p2.LastUpdate.CompareTo(p1.LastUpdate));
|
|
this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.LastUpdate.CompareTo(p1.Manifest.LastUpdate));
|
|
break;
|
|
case PluginSortKind.NewOrNot:
|
|
this.pluginListAvailable.Sort((p1, p2) => this.WasPluginSeen(p1.InternalName)
|
|
.CompareTo(this.WasPluginSeen(p2.InternalName)));
|
|
this.pluginListInstalled.Sort((p1, p2) => this.WasPluginSeen(p1.Manifest.InternalName)
|
|
.CompareTo(this.WasPluginSeen(p2.Manifest.InternalName)));
|
|
break;
|
|
case PluginSortKind.NotInstalled:
|
|
this.pluginListAvailable.Sort((p1, p2) => this.pluginListInstalled.Any(x => x.Manifest.InternalName == p1.InternalName)
|
|
.CompareTo(this.pluginListInstalled.Any(x => x.Manifest.InternalName == p2.InternalName)));
|
|
this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name)); // Makes no sense for installed plugins
|
|
break;
|
|
default:
|
|
throw new InvalidEnumArgumentException("Unknown plugin sort type.");
|
|
}
|
|
}
|
|
|
|
private bool WasPluginSeen(string internalName) =>
|
|
Service<DalamudConfiguration>.Get().SeenPluginInternalName.Contains(internalName);
|
|
|
|
/// <summary>
|
|
/// A continuation task that displays any errors received into the error modal.
|
|
/// </summary>
|
|
/// <param name="task">The previous task.</param>
|
|
/// <param name="state">An error message to be displayed.</param>
|
|
/// <returns>A value indicating whether to continue with the next task.</returns>
|
|
private bool DisplayErrorContinuation(Task task, object state)
|
|
{
|
|
if (task.IsFaulted)
|
|
{
|
|
var errorModalMessage = state as string;
|
|
|
|
foreach (var ex in task.Exception.InnerExceptions)
|
|
{
|
|
if (ex is PluginException)
|
|
{
|
|
Log.Error(ex, "Plugin installer threw an error");
|
|
#if DEBUG
|
|
if (!string.IsNullOrEmpty(ex.Message))
|
|
errorModalMessage += $"\n\n{ex.Message}";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
Log.Error(ex, "Plugin installer threw an unexpected error");
|
|
#if DEBUG
|
|
if (!string.IsNullOrEmpty(ex.Message))
|
|
errorModalMessage += $"\n\n{ex.Message}";
|
|
#endif
|
|
}
|
|
}
|
|
|
|
this.ShowErrorModal(errorModalMessage);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void ShowErrorModal(string message)
|
|
{
|
|
this.errorModalMessage = message;
|
|
this.errorModalDrawing = true;
|
|
this.errorModalOnNextFrame = true;
|
|
}
|
|
|
|
private void UpdateCategoriesOnSearchChange()
|
|
{
|
|
if (string.IsNullOrEmpty(this.searchText))
|
|
{
|
|
this.categoryManager.SetCategoryHighlightsForPlugins(null);
|
|
}
|
|
else
|
|
{
|
|
var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm));
|
|
this.categoryManager.SetCategoryHighlightsForPlugins(pluginsMatchingSearch);
|
|
}
|
|
}
|
|
|
|
private void UpdateCategoriesOnPluginsChange()
|
|
{
|
|
this.categoryManager.BuildCategories(this.pluginListAvailable);
|
|
this.UpdateCategoriesOnSearchChange();
|
|
}
|
|
|
|
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")]
|
|
private static class Locs
|
|
{
|
|
#region Window Title
|
|
|
|
public static string WindowTitle => Loc.Localize("InstallerHeader", "Plugin Installer");
|
|
|
|
public static string WindowTitleMod_Testing => Loc.Localize("InstallerHeaderTesting", " (TESTING)");
|
|
|
|
#endregion
|
|
|
|
#region Header
|
|
|
|
public static string Header_Hint => Loc.Localize("InstallerHint", "This window allows you to install and remove in-game plugins.\nThey are made by third-party developers.");
|
|
|
|
public static string Header_SearchPlaceholder => Loc.Localize("InstallerSearch", "Search");
|
|
|
|
#endregion
|
|
|
|
#region SortBy
|
|
|
|
public static string SortBy_Alphabetical => Loc.Localize("InstallerAlphabetical", "Alphabetical");
|
|
|
|
public static string SortBy_DownloadCounts => Loc.Localize("InstallerDownloadCount", "Download Count");
|
|
|
|
public static string SortBy_LastUpdate => Loc.Localize("InstallerLastUpdate", "Last Update");
|
|
|
|
public static string SortBy_NewOrNot => Loc.Localize("InstallerNewOrNot", "New or not");
|
|
|
|
public static string SortBy_NotInstalled => Loc.Localize("InstallerNotInstalled", "Not Installed");
|
|
|
|
public static string SortBy_Label => Loc.Localize("InstallerSortBy", "Sort By");
|
|
|
|
#endregion
|
|
|
|
#region Tab body
|
|
|
|
public static string TabBody_LoadingPlugins => Loc.Localize("InstallerLoading", "Loading plugins...");
|
|
|
|
public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed.");
|
|
|
|
public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins.");
|
|
|
|
#endregion
|
|
|
|
#region Search text
|
|
|
|
public static string TabBody_SearchNoMatching => Loc.Localize("InstallerNoMatching", "No plugins were found matching your search.");
|
|
|
|
public static string TabBody_SearchNoCompatible => Loc.Localize("InstallerNoCompatible", "No compatible plugins were found :( Please restart your game and try again.");
|
|
|
|
public static string TabBody_SearchNoInstalled => Loc.Localize("InstallerNoInstalled", "No plugins are currently installed. You can install them from the \"All Plugins\" tab.");
|
|
|
|
public static string TabBody_ChangelogNone => Loc.Localize("InstallerNoChangelog", "None of your installed plugins have a changelog.");
|
|
|
|
#endregion
|
|
|
|
#region Plugin title text
|
|
|
|
public static string PluginTitleMod_Installed => Loc.Localize("InstallerInstalled", " (installed)");
|
|
|
|
public static string PluginTitleMod_Disabled => Loc.Localize("InstallerDisabled", " (disabled)");
|
|
|
|
public static string PluginTitleMod_Unloaded => Loc.Localize("InstallerUnloaded", " (unloaded)");
|
|
|
|
public static string PluginTitleMod_HasUpdate => Loc.Localize("InstallerHasUpdate", " (has update)");
|
|
|
|
public static string PluginTitleMod_Updated => Loc.Localize("InstallerUpdated", " (updated)");
|
|
|
|
public static string PluginTitleMod_TestingVersion => Loc.Localize("InstallerTestingVersion", " (testing version)");
|
|
|
|
public static string PluginTitleMod_UpdateFailed => Loc.Localize("InstallerUpdateFailed", " (update failed)");
|
|
|
|
public static string PluginTitleMod_LoadError => Loc.Localize("InstallerLoadError", " (load error)");
|
|
|
|
public static string PluginTitleMod_UnloadError => Loc.Localize("InstallerUnloadError", " (unload error)");
|
|
|
|
public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)");
|
|
|
|
public static string PluginTitleMod_BannedError => Loc.Localize("InstallerBannedError", " (automatically disabled)");
|
|
|
|
public static string PluginTitleMod_OrphanedError => Loc.Localize("InstallerOrphanedError", " (unknown repository)");
|
|
|
|
public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!");
|
|
|
|
#endregion
|
|
|
|
#region Plugin context menu
|
|
|
|
public static string PluginContext_MarkAllSeen => Loc.Localize("InstallerMarkAllSeen", "Mark all as seen");
|
|
|
|
public static string PluginContext_HidePlugin => Loc.Localize("InstallerHidePlugin", "Hide from installer");
|
|
|
|
public static string PluginContext_DeletePluginConfig => Loc.Localize("InstallerDeletePluginConfig", "Reset plugin");
|
|
|
|
public static string PluginContext_DeletePluginConfigReload => Loc.Localize("InstallerDeletePluginConfigReload", "Reset plugin settings & reload");
|
|
|
|
#endregion
|
|
|
|
#region Plugin body
|
|
|
|
public static string PluginBody_AuthorWithoutDownloadCount(string author) => Loc.Localize("InstallerAuthorWithoutDownloadCount", " by {0}").Format(author);
|
|
|
|
public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0} ({1} downloads)").Format(author, count.ToString("N0"));
|
|
|
|
public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}").Format(author);
|
|
|
|
public static string PluginBody_DevPluginPath(string path) => Loc.Localize("InstallerDevPluginPath", "From {0}").Format(path);
|
|
|
|
public static string PluginBody_Plugin3rdPartyRepo(string url) => Loc.Localize("InstallerPlugin3rdPartyRepo", "From custom plugin repository {0}").Format(url);
|
|
|
|
public static string PluginBody_AvailableDevPlugin => Loc.Localize("InstallerDevPlugin", " This plugin is available in one of your repos, please remove it from the devPlugins folder.");
|
|
|
|
public static string PluginBody_DeleteDevPlugin => Loc.Localize("InstallerDeleteDevPlugin ", " To delete this plugin, please remove it from the devPlugins folder.");
|
|
|
|
public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author.");
|
|
|
|
public static string PluginBody_Orphaned => Loc.Localize("InstallerOrphanedPluginBody ", "This plugin's source repository is no longer available. You may need to reinstall it from its repository, or re-add the repository.");
|
|
|
|
public static string PluginBody_LoadFailed => Loc.Localize("InstallerLoadFailedPluginBody ", "This plugin failed to load. Please contact the author for more information.");
|
|
|
|
public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin was automatically disabled due to incompatibilities and is not available at the moment. Please wait for it to be updated by its author.");
|
|
|
|
public static string PluginBody_Policy => Loc.Localize("InstallerPolicyPluginBody ", "Plugin loads for this type of plugin were manually disabled.");
|
|
|
|
public static string PluginBody_BannedReason(string message) =>
|
|
Loc.Localize("InstallerBannedPluginBodyReason ", "This plugin was automatically disabled: {0}").Format(message);
|
|
|
|
#endregion
|
|
|
|
#region Plugin buttons
|
|
|
|
public static string PluginButton_InstallVersion(string version) => Loc.Localize("InstallerInstall", "Install v{0}").Format(version);
|
|
|
|
public static string PluginButton_Working => Loc.Localize("InstallerWorking", "Working");
|
|
|
|
public static string PluginButton_Disable => Loc.Localize("InstallerDisable", "Disable");
|
|
|
|
public static string PluginButton_Load => Loc.Localize("InstallerLoad", "Load");
|
|
|
|
public static string PluginButton_Unload => Loc.Localize("InstallerUnload", "Unload");
|
|
|
|
public static string PluginButton_SafeMode => Loc.Localize("InstallerSafeModeButton", "Can't change in safe mode");
|
|
|
|
#endregion
|
|
|
|
#region Plugin button tooltips
|
|
|
|
public static string PluginButtonToolTip_OpenConfiguration => Loc.Localize("InstallerOpenConfig", "Open Configuration");
|
|
|
|
public static string PluginButtonToolTip_StartOnBoot => Loc.Localize("InstallerStartOnBoot", "Start on boot");
|
|
|
|
public static string PluginButtonToolTip_AutomaticReloading => Loc.Localize("InstallerAutomaticReloading", "Automatic reloading");
|
|
|
|
public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin");
|
|
|
|
public static string PluginButtonToolTip_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete - please try restarting the game.");
|
|
|
|
public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL");
|
|
|
|
public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version);
|
|
|
|
public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerUnloadFailedTooltip", "Plugin unload failed, please restart your game and try again.");
|
|
|
|
#endregion
|
|
|
|
#region Notifications
|
|
|
|
public static string Notifications_PluginInstalledTitle => Loc.Localize("NotificationsPluginInstalledTitle", "Plugin installed!");
|
|
|
|
public static string Notifications_PluginInstalled(string name) => Loc.Localize("NotificationsPluginInstalled", "'{0}' was successfully installed.").Format(name);
|
|
|
|
public static string Notifications_PluginNotInstalledTitle => Loc.Localize("NotificationsPluginNotInstalledTitle", "Plugin not installed!");
|
|
|
|
public static string Notifications_PluginNotInstalled(string name) => Loc.Localize("NotificationsPluginNotInstalled", "'{0}' failed to install.").Format(name);
|
|
|
|
public static string Notifications_NoUpdatesFoundTitle => Loc.Localize("NotificationsNoUpdatesFoundTitle", "No updates found!");
|
|
|
|
public static string Notifications_NoUpdatesFound => Loc.Localize("NotificationsNoUpdatesFound", "No updates were found.");
|
|
|
|
public static string Notifications_UpdatesInstalledTitle => Loc.Localize("NotificationsUpdatesInstalledTitle", "Updates installed!");
|
|
|
|
public static string Notifications_UpdatesInstalled(int count) => Loc.Localize("NotificationsUpdatesInstalled", "Updates for {0} of your plugins were installed.").Format(count);
|
|
|
|
public static string Notifications_PluginDisabledTitle => Loc.Localize("NotificationsPluginDisabledTitle", "Plugin disabled!");
|
|
|
|
public static string Notifications_PluginDisabled(string name) => Loc.Localize("NotificationsPluginDisabled", "'{0}' was disabled.").Format(name);
|
|
|
|
#endregion
|
|
|
|
#region Footer
|
|
|
|
public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins");
|
|
|
|
public static string FooterButton_UpdateSafeMode => Loc.Localize("InstallerUpdateSafeMode", "Can't update in safe mode");
|
|
|
|
public static string FooterButton_InProgress => Loc.Localize("InstallerInProgress", "Install in progress...");
|
|
|
|
public static string FooterButton_NoUpdates => Loc.Localize("InstallerNoUpdates", "No updates found!");
|
|
|
|
public static string FooterButton_UpdateComplete(int count) => Loc.Localize("InstallerUpdateComplete", "{0} plugins updated!").Format(count);
|
|
|
|
public static string FooterButton_Settings => Loc.Localize("InstallerSettings", "Settings");
|
|
|
|
public static string FooterButton_ScanDevPlugins => Loc.Localize("InstallerScanDevPlugins", "Scan Dev Plugins");
|
|
|
|
public static string FooterButton_Close => Loc.Localize("InstallerClose", "Close");
|
|
|
|
#endregion
|
|
|
|
#region Error modal
|
|
|
|
public static string ErrorModal_Title => Loc.Localize("InstallerError", "Installer Error");
|
|
|
|
public static string ErrorModal_InstallContactAuthor => Loc.Localize(
|
|
"InstallerContactAuthor",
|
|
"Please restart your game and try again. If this error occurs again, please contact the plugin author.");
|
|
|
|
public static string ErrorModal_InstallFail(string name) => Loc.Localize("InstallerInstallFail", "Failed to install plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor);
|
|
|
|
public static string ErrorModal_SingleUpdateFail(string name) => Loc.Localize("InstallerSingleUpdateFail", "Failed to update plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor);
|
|
|
|
public static string ErrorModal_DeleteConfigFail(string name) => Loc.Localize("InstallerDeleteConfigFail", "Failed to reset the plugin {0}.\n\nThe plugin may not support this action. You can try deleting the configuration manually while the game is shut down - please see the FAQ.").Format(name);
|
|
|
|
public static string ErrorModal_EnableFail(string name) => Loc.Localize("InstallerEnableFail", "Failed to enable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor);
|
|
|
|
public static string ErrorModal_DisableFail(string name) => Loc.Localize("InstallerDisableFail", "Failed to disable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor);
|
|
|
|
public static string ErrorModal_UnloadFail(string name) => Loc.Localize("InstallerUnloadFail", "Failed to unload plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor);
|
|
|
|
public static string ErrorModal_LoadFail(string name) => Loc.Localize("InstallerLoadFail", "Failed to load plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor);
|
|
|
|
public static string ErrorModal_DeleteFail(string name) => Loc.Localize("InstallerDeleteFail", "Failed to delete plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor);
|
|
|
|
public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.\nPlease restart your game and try again. If this error occurs again, please complain.");
|
|
|
|
public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(failCount);
|
|
|
|
public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(successCount, failCount);
|
|
|
|
public static string ErrorModal_HintBlame(string plugins) => Loc.Localize("InstallerErrorPluginInfo", "\n\nThe following plugins caused these issues:\n\n{0}\nYou may try removing these plugins manually and reinstalling them.").Format(plugins);
|
|
|
|
// public static string ErrorModal_Hint => Loc.Localize("InstallerErrorHint", "The plugin installer ran into an issue or the plugin is incompatible.\nPlease restart the game and report this error on our discord.");
|
|
|
|
#endregion
|
|
|
|
#region Feedback Modal
|
|
|
|
public static string FeedbackModal_Title => Loc.Localize("InstallerFeedback", "Send Feedback");
|
|
|
|
public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.\nYou can include your Discord tag or email address if you wish to give them the opportunity to answer.\n\nIf you put your Discord name into the contact information field, please join the XIVLauncher discord server so we can get in touch.").Format(pluginName);
|
|
|
|
public static string FeedbackModal_HasUpdate => Loc.Localize("InstallerFeedbackHasUpdate", "A new version of this plugin is available, please update before reporting bugs.");
|
|
|
|
public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information");
|
|
|
|
public static string FeedbackModal_IncludeLastError => Loc.Localize("InstallerFeedbackIncludeLastError", "Include last error message");
|
|
|
|
public static string FeedbackModal_IncludeLastErrorHint => Loc.Localize("InstallerFeedbackIncludeLastErrorHint", "This option can give the plugin developer useful feedback on what exactly went wrong.");
|
|
|
|
public static string FeedbackModal_Hint => Loc.Localize("InstallerFeedbackHint", "All plugin developers will be able to see your feedback.\nPlease never include any personal or revealing information.\nIf you chose to include the last error message, information like your Windows username may be included.\n\nThe collected feedback is not stored on our end and immediately relayed to Discord.");
|
|
|
|
public static string FeedbackModal_NotificationSuccess => Loc.Localize("InstallerFeedbackNotificationSuccess", "Your feedback was sent successfully!");
|
|
|
|
public static string FeedbackModal_NotificationError => Loc.Localize("InstallerFeedbackNotificationError", "Your feedback could not be sent.");
|
|
|
|
#endregion
|
|
|
|
#region Plugin Update chatbox
|
|
|
|
public static string PluginUpdateHeader_Chatbox => Loc.Localize("DalamudPluginUpdates", "Updates:");
|
|
|
|
#endregion
|
|
|
|
#region Error modal buttons
|
|
|
|
public static string ErrorModalButton_Ok => Loc.Localize("OK", "OK");
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|