mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
1376 lines
54 KiB
C#
1376 lines
54 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Numerics;
|
|
using System.Threading.Tasks;
|
|
|
|
using CheapLoc;
|
|
using Dalamud.Interface.Colors;
|
|
using Dalamud.Interface.Components;
|
|
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 ImGuiNET;
|
|
using ImGuiScene;
|
|
|
|
namespace Dalamud.Interface.Internal.Windows
|
|
{
|
|
/// <summary>
|
|
/// Class responsible for drawing the plugin installer.
|
|
/// </summary>
|
|
internal class PluginInstallerWindow : Window, IDisposable
|
|
{
|
|
private const int PluginImageWidth = 750;
|
|
private const int PluginImageHeight = 400;
|
|
|
|
private const int PluginIconWidth = 300;
|
|
private const int PluginIconHeight = 300;
|
|
|
|
private static readonly ModuleLog Log = new("PLUGINW");
|
|
|
|
private readonly Dalamud dalamud;
|
|
|
|
private bool errorModalDrawing = true;
|
|
private bool errorModalOnNextFrame = false;
|
|
private string errorModalMessage = string.Empty;
|
|
|
|
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 Dictionary<string, DownloadedPluginImages> pluginImagesMap = new();
|
|
|
|
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="dalamud">The Dalamud instance.</param>
|
|
public PluginInstallerWindow(Dalamud dalamud)
|
|
: base(
|
|
Locs.WindowTitle + (dalamud.Configuration.DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
|
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoScrollbar)
|
|
{
|
|
this.dalamud = dalamud;
|
|
this.IsOpen = true;
|
|
|
|
this.Size = new Vector2(810, 520);
|
|
this.SizeCondition = ImGuiCond.Always;
|
|
|
|
// For debugging
|
|
if (this.dalamud.PluginManager.PluginsReady)
|
|
this.OnInstalledPluginsChanged();
|
|
|
|
this.dalamud.PluginManager.OnAvailablePluginsChanged += this.OnAvailablePluginsChanged;
|
|
this.dalamud.PluginManager.OnInstalledPluginsChanged += this.OnInstalledPluginsChanged;
|
|
}
|
|
|
|
private enum OperationStatus
|
|
{
|
|
Idle,
|
|
InProgress,
|
|
Complete,
|
|
}
|
|
|
|
private enum PluginSortKind
|
|
{
|
|
Alphabetical,
|
|
DownloadCount,
|
|
LastUpdate,
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void Dispose()
|
|
{
|
|
this.dalamud.PluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged;
|
|
this.dalamud.PluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void OnOpen()
|
|
{
|
|
Task.Run(this.dalamud.PluginManager.ReloadPluginMasters);
|
|
|
|
this.updatePluginCount = 0;
|
|
this.updatedPlugins = null;
|
|
|
|
this.searchText = string.Empty;
|
|
this.sortKind = PluginSortKind.Alphabetical;
|
|
this.filterText = Locs.SortBy_Alphabetical;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void Draw()
|
|
{
|
|
this.DrawHeader();
|
|
this.DrawPluginTabBar();
|
|
this.DrawFooter();
|
|
this.DrawErrorModal();
|
|
}
|
|
|
|
private static Vector2 GetButtonSize(string text) => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2);
|
|
|
|
private void DrawHeader()
|
|
{
|
|
var style = ImGui.GetStyle();
|
|
var windowSize = ImGui.GetWindowContentRegionMax();
|
|
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale));
|
|
|
|
var searchInputWidth = 240 * 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),
|
|
};
|
|
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);
|
|
ImGui.SetNextItemWidth(searchInputWidth);
|
|
ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100);
|
|
|
|
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 windowSize = ImGui.GetWindowContentRegionMax();
|
|
var placeholderButtonSize = GetButtonSize("placeholder");
|
|
|
|
ImGui.Separator();
|
|
|
|
ImGui.SetCursorPosY(windowSize.Y - placeholderButtonSize.Y);
|
|
|
|
this.DrawUpdatePluginsButton();
|
|
|
|
ImGui.SameLine();
|
|
if (ImGui.Button(Locs.FooterButton_Settings))
|
|
{
|
|
this.dalamud.DalamudUi.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))
|
|
{
|
|
this.dalamud.PluginManager.ScanDevPlugins();
|
|
}
|
|
}
|
|
|
|
var closeText = Locs.FooterButton_Close;
|
|
var closeButtonSize = GetButtonSize(closeText);
|
|
|
|
ImGui.SameLine(windowSize.X - closeButtonSize.X);
|
|
if (ImGui.Button(closeText))
|
|
{
|
|
this.IsOpen = false;
|
|
this.dalamud.Configuration.Save();
|
|
}
|
|
}
|
|
|
|
private void DrawUpdatePluginsButton()
|
|
{
|
|
var ready = this.dalamud.PluginManager.PluginsReady && this.dalamud.PluginManager.ReposReady;
|
|
|
|
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(() => this.dalamud.PluginManager.UpdatePlugins())
|
|
.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)
|
|
{
|
|
this.dalamud.PluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
ImGui.OpenPopup(modalTitle);
|
|
this.errorModalOnNextFrame = false;
|
|
}
|
|
}
|
|
|
|
private void DrawPluginTabBar()
|
|
{
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale));
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3));
|
|
|
|
if (ImGui.BeginTabBar("PluginsTabBar", ImGuiTabBarFlags.NoTooltip))
|
|
{
|
|
this.DrawPluginTab(Locs.TabTitle_AvailablePlugins, this.DrawAvailablePluginList);
|
|
this.DrawPluginTab(Locs.TabTitle_InstalledPlugins, this.DrawInstalledPluginList);
|
|
|
|
if (this.hasDevPlugins)
|
|
{
|
|
this.DrawPluginTab(Locs.TabTitle_InstalledDevPlugins, this.DrawInstalledDevPluginList);
|
|
}
|
|
}
|
|
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
private void DrawPluginTab(string title, Action drawPluginList)
|
|
{
|
|
if (ImGui.BeginTabItem(title))
|
|
{
|
|
ImGui.BeginChild($"Scrolling{title}", ImGuiHelpers.ScaledVector2(0, 384), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground);
|
|
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
|
|
|
|
var ready = this.DrawPluginListLoading();
|
|
|
|
if (ready)
|
|
{
|
|
drawPluginList();
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
|
|
ImGui.EndTabItem();
|
|
}
|
|
}
|
|
|
|
private void DrawAvailablePluginList()
|
|
{
|
|
var pluginList = this.pluginListAvailable;
|
|
|
|
if (pluginList.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoCompatible);
|
|
return;
|
|
}
|
|
|
|
var filteredList = pluginList
|
|
.Where(rm => !this.IsManifestFiltered(rm))
|
|
.ToList();
|
|
|
|
if (filteredList.Count == 0)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching);
|
|
return;
|
|
}
|
|
|
|
var i = 0;
|
|
foreach (var manifest in filteredList)
|
|
{
|
|
var (isInstalled, plugin) = this.IsManifestInstalled(manifest);
|
|
|
|
ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}");
|
|
|
|
if (isInstalled)
|
|
{
|
|
this.DrawInstalledPlugin(plugin, i++, true);
|
|
}
|
|
else
|
|
{
|
|
this.DrawAvailablePlugin(manifest, 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 bool DrawPluginListLoading()
|
|
{
|
|
var ready = this.dalamud.PluginManager.PluginsReady && this.dalamud.PluginManager.ReposReady;
|
|
|
|
if (!ready)
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_LoadingPlugins);
|
|
}
|
|
|
|
var failedRepos = this.dalamud.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 void DrawAvailablePlugin(RemotePluginManifest manifest, int index)
|
|
{
|
|
var useTesting = this.dalamud.PluginManager.UseTesting(manifest);
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (ImGui.CollapsingHeader($"{label}###Header{index}{manifest.InternalName}"))
|
|
{
|
|
ImGui.Indent();
|
|
|
|
// Name
|
|
ImGui.Text(manifest.Name);
|
|
|
|
// 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);
|
|
|
|
// Installable from
|
|
if (manifest.SourceRepo.IsThirdParty)
|
|
{
|
|
var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.SourceRepo.PluginMasterUrl);
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText);
|
|
}
|
|
|
|
// Description
|
|
if (!string.IsNullOrWhiteSpace(manifest.Description))
|
|
{
|
|
ImGui.TextWrapped(manifest.Description);
|
|
}
|
|
|
|
// Controls
|
|
var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress;
|
|
|
|
var versionString = useTesting
|
|
? $"{manifest.TestingAssemblyVersion}"
|
|
: $"{manifest.AssemblyVersion}";
|
|
|
|
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(() => this.dalamud.PluginManager.InstallPlugin(manifest, useTesting, PluginLoadReason.Installer))
|
|
.ContinueWith(task =>
|
|
{
|
|
// There is no need to set as Complete for an individual plugin installation
|
|
this.installStatus = OperationStatus.Idle;
|
|
this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name));
|
|
});
|
|
}
|
|
}
|
|
|
|
this.DrawVisitRepoUrlButton(manifest.RepoUrl);
|
|
|
|
this.DrawPluginImages(manifest);
|
|
|
|
ImGui.Unindent();
|
|
}
|
|
|
|
if (ImGui.BeginPopupContextItem("ItemContextMenu"))
|
|
{
|
|
if (ImGui.Selectable(Locs.PluginContext_HidePlugin))
|
|
{
|
|
Log.Debug($"Adding {manifest.InternalName} to hidden plugins");
|
|
this.dalamud.Configuration.HiddenPluginInternalName.Add(manifest.InternalName);
|
|
this.dalamud.Configuration.Save();
|
|
this.dalamud.PluginManager.RefilterPluginMasters();
|
|
}
|
|
|
|
if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfig))
|
|
{
|
|
Log.Debug($"Deleting config for {manifest.InternalName}");
|
|
|
|
this.installStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(() =>
|
|
{
|
|
this.dalamud.PluginManager.PluginConfigs.Delete(manifest.InternalName);
|
|
|
|
var path = Path.Combine(this.dalamud.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)
|
|
{
|
|
// 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)
|
|
{
|
|
label += Locs.PluginTitleMod_Disabled;
|
|
}
|
|
|
|
// Load error
|
|
if (plugin.State == PluginState.LoadError)
|
|
{
|
|
label += Locs.PluginTitleMod_LoadError;
|
|
}
|
|
|
|
// Unload error
|
|
if (plugin.State == PluginState.UnloadError)
|
|
{
|
|
label += Locs.PluginTitleMod_UnloadError;
|
|
}
|
|
|
|
var availablePluginUpdate = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin);
|
|
// Update available
|
|
if (availablePluginUpdate != default)
|
|
{
|
|
label += Locs.PluginTitleMod_HasUpdate;
|
|
}
|
|
|
|
// Freshly updated
|
|
if (this.updatedPlugins != null && !plugin.IsDev)
|
|
{
|
|
var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName);
|
|
if (update != default)
|
|
{
|
|
if (update.WasUpdated)
|
|
{
|
|
label += Locs.PluginTitleMod_Updated;
|
|
}
|
|
else
|
|
{
|
|
label += Locs.PluginTitleMod_UpdateFailed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Outdated API level
|
|
if (plugin.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel)
|
|
{
|
|
label += Locs.PluginTitleMod_OutdatedError;
|
|
}
|
|
|
|
if (ImGui.CollapsingHeader($"{label}###Header{index}{plugin.Manifest.InternalName}"))
|
|
{
|
|
var manifest = plugin.Manifest;
|
|
|
|
ImGui.Indent();
|
|
|
|
// Name
|
|
ImGui.Text(manifest.Name);
|
|
|
|
// Download count
|
|
var downloadText = manifest.DownloadCount > 0
|
|
? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount)
|
|
: Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author);
|
|
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText);
|
|
|
|
// Installed from
|
|
if (!string.IsNullOrEmpty(manifest.InstalledFromUrl))
|
|
{
|
|
var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.InstalledFromUrl);
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText);
|
|
}
|
|
|
|
// Description
|
|
if (!string.IsNullOrWhiteSpace(manifest.Description))
|
|
{
|
|
ImGui.TextWrapped(manifest.Description);
|
|
}
|
|
|
|
if (plugin.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel)
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
|
ImGui.TextWrapped(Locs.PluginBody_Outdated);
|
|
ImGui.PopStyleColor();
|
|
}
|
|
|
|
// Available commands (if loaded)
|
|
if (plugin.IsLoaded)
|
|
{
|
|
var commands = this.dalamud.CommandManager.Commands.Where(cInfo => cInfo.Value.ShowInHelp && cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName);
|
|
if (commands.Any())
|
|
{
|
|
ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f));
|
|
foreach (var command in commands)
|
|
{
|
|
ImGui.TextWrapped($"{command.Key} → {command.Value.HelpMessage}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Controls
|
|
this.DrawPluginControlButton(plugin);
|
|
this.DrawDevPluginButtons(plugin);
|
|
this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl);
|
|
|
|
if (availablePluginUpdate != default)
|
|
this.DrawUpdateSinglePluginButton(availablePluginUpdate);
|
|
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.AssemblyVersion}");
|
|
|
|
if (plugin.IsDev)
|
|
{
|
|
ImGui.SameLine();
|
|
ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin);
|
|
}
|
|
|
|
ImGui.Unindent();
|
|
}
|
|
|
|
if (ImGui.BeginPopupContextItem("InstalledItemContextMenu"))
|
|
{
|
|
if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfigReload))
|
|
{
|
|
Log.Debug($"Deleting config for {plugin.Manifest.InternalName}");
|
|
|
|
this.installStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(() => this.dalamud.PluginManager.DeleteConfiguration(plugin))
|
|
.ContinueWith(task =>
|
|
{
|
|
this.installStatus = OperationStatus.Idle;
|
|
|
|
this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name));
|
|
});
|
|
}
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
|
|
private void DrawPluginControlButton(LocalPlugin plugin)
|
|
{
|
|
// 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.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !this.dalamud.Configuration.LoadAllApiLevels);
|
|
|
|
if (plugin.State == PluginState.InProgress)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_Working);
|
|
}
|
|
else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError)
|
|
{
|
|
if (disabled)
|
|
{
|
|
ImGuiComponents.DisabledButton(Locs.PluginButton_Disable);
|
|
}
|
|
else
|
|
{
|
|
if (ImGui.Button(Locs.PluginButton_Disable))
|
|
{
|
|
Task.Run(() =>
|
|
{
|
|
var unloadTask = Task.Run(() => plugin.Unload())
|
|
.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)
|
|
{
|
|
this.dalamud.PluginManager.RemovePlugin(plugin);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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.Load(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);
|
|
}
|
|
}
|
|
|
|
private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update)
|
|
{
|
|
ImGui.SameLine();
|
|
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Download))
|
|
{
|
|
this.installStatus = OperationStatus.InProgress;
|
|
|
|
Task.Run(() => this.dalamud.PluginManager.UpdateSinglePlugin(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())
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_UpdateSingle(update.UpdateManifest.AssemblyVersion.ToString()));
|
|
}
|
|
|
|
private void DrawOpenPluginSettingsButton(LocalPlugin plugin)
|
|
{
|
|
if (plugin.DalamudInterface?.UiBuilder?.HasConfigUi ?? false)
|
|
{
|
|
ImGui.SameLine();
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog))
|
|
{
|
|
try
|
|
{
|
|
plugin.DalamudInterface.UiBuilder.OpenConfigUi();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Error during OpenConfigUi: {plugin.Name}");
|
|
}
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_OpenConfiguration);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawDevPluginButtons(LocalPlugin localPlugin)
|
|
{
|
|
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;
|
|
this.dalamud.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;
|
|
this.dalamud.Configuration.Save();
|
|
}
|
|
|
|
ImGui.PopStyleColor(2);
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading);
|
|
}
|
|
|
|
// Delete
|
|
if (plugin.State == PluginState.Unloaded)
|
|
{
|
|
ImGui.SameLine();
|
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt))
|
|
{
|
|
try
|
|
{
|
|
plugin.DllFile.Delete();
|
|
this.dalamud.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.PluginBody_DeleteDevPlugin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 void DrawPluginImages(PluginManifest manifest)
|
|
{
|
|
if (!this.pluginImagesMap.TryGetValue(manifest.InternalName, out var images))
|
|
{
|
|
if (images == null)
|
|
return;
|
|
|
|
Task.Run(() => this.DownloadPluginImagesAsync(manifest));
|
|
return;
|
|
}
|
|
|
|
if (images.ImagesTex != null && images.ImagesTex.Length > 0)
|
|
ImGui.Image(images.ImagesTex[0].ImGuiHandle, new Vector2(images.ImagesTex[0].Width, images.ImagesTex[0].Height));
|
|
}
|
|
|
|
private bool IsManifestFiltered(PluginManifest manifest)
|
|
{
|
|
var searchString = this.searchText.ToLowerInvariant();
|
|
var hasSearchString = !string.IsNullOrWhiteSpace(searchString);
|
|
|
|
return hasSearchString && !(
|
|
manifest.Name.ToLowerInvariant().Contains(searchString) ||
|
|
manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase) ||
|
|
(manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase)));
|
|
}
|
|
|
|
private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(RemotePluginManifest manifest)
|
|
{
|
|
var plugin = this.pluginListInstalled.FirstOrDefault(plugin => plugin.Manifest.InternalName == manifest.InternalName);
|
|
var isInstalled = plugin != default;
|
|
|
|
return (isInstalled, plugin);
|
|
}
|
|
|
|
private void OnAvailablePluginsChanged()
|
|
{
|
|
// 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 = this.dalamud.PluginManager.AvailablePlugins
|
|
.Where(manifest => !this.IsManifestInstalled(manifest).IsInstalled)
|
|
.ToList();
|
|
this.pluginListUpdatable = this.dalamud.PluginManager.UpdatablePlugins.ToList();
|
|
this.ResortPlugins();
|
|
}
|
|
|
|
private void OnInstalledPluginsChanged()
|
|
{
|
|
this.pluginListInstalled = this.dalamud.PluginManager.InstalledPlugins.ToList();
|
|
this.pluginListUpdatable = this.dalamud.PluginManager.UpdatablePlugins.ToList();
|
|
this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev);
|
|
this.ResortPlugins();
|
|
}
|
|
|
|
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;
|
|
default:
|
|
throw new InvalidEnumArgumentException("Unknown plugin sort type.");
|
|
}
|
|
}
|
|
|
|
/// <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 async Task DownloadPluginImagesAsync(PluginManifest manifest)
|
|
{
|
|
Log.Verbose($"Downloading images for {manifest.InternalName}");
|
|
|
|
this.pluginImagesMap.Add(manifest.InternalName, null);
|
|
|
|
var pluginImages = new DownloadedPluginImages();
|
|
var client = new HttpClient();
|
|
|
|
if (manifest.IconUrl != null)
|
|
{
|
|
var data = await client.GetAsync(manifest.IconUrl);
|
|
data.EnsureSuccessStatusCode();
|
|
var icon = this.dalamud.InterfaceManager.LoadImage(await data.Content.ReadAsByteArrayAsync());
|
|
|
|
if (icon != null)
|
|
{
|
|
if (icon.Height != PluginIconHeight || icon.Width != PluginIconWidth)
|
|
{
|
|
Log.Error($"Icon at {manifest.IconUrl} was not of the correct resolution.");
|
|
return;
|
|
}
|
|
|
|
pluginImages.IconTex = icon;
|
|
}
|
|
}
|
|
|
|
pluginImages.ImagesTex = new TextureWrap[manifest.ImageUrls.Count];
|
|
for (var i = 0; i < manifest.ImageUrls.Count; i++)
|
|
{
|
|
var data = await client.GetAsync(manifest.ImageUrls[i]);
|
|
data.EnsureSuccessStatusCode();
|
|
var image = this.dalamud.InterfaceManager.LoadImage(await data.Content.ReadAsByteArrayAsync());
|
|
|
|
if (image != null)
|
|
{
|
|
if (image.Height != PluginImageHeight || image.Width != PluginImageWidth)
|
|
{
|
|
Log.Error($"Image at {manifest.ImageUrls[i]} was not of the correct resolution.");
|
|
|
|
return;
|
|
}
|
|
|
|
pluginImages.ImagesTex[i] = image;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.pluginImagesMap[manifest.InternalName] = pluginImages;
|
|
}
|
|
|
|
[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_Label => Loc.Localize("InstallerSortBy", "Sort By");
|
|
|
|
#endregion
|
|
|
|
#region Tabs
|
|
|
|
public static string TabTitle_AvailablePlugins => Loc.Localize("InstallerAvailablePlugins", "Available Plugins");
|
|
|
|
public static string TabTitle_InstalledPlugins => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins");
|
|
|
|
public static string TabTitle_InstalledDevPlugins => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins");
|
|
|
|
#endregion
|
|
|
|
#region Tab body
|
|
|
|
public static string TabBody_LoadingPlugins => Loc.Localize("InstallerLoading", "Loading plugins...");
|
|
|
|
public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed.");
|
|
|
|
#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 Available Plugins tab.");
|
|
|
|
#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)");
|
|
|
|
#endregion
|
|
|
|
#region Plugin context menu
|
|
|
|
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("InstallerDeletePluginConfig", "Reset plugin settings & reload");
|
|
|
|
#endregion
|
|
|
|
#region Plugin body
|
|
|
|
public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0}, {1} downloads").Format(author, count);
|
|
|
|
public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}, download count unavailable").Format(author);
|
|
|
|
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.");
|
|
|
|
#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");
|
|
|
|
#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_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL");
|
|
|
|
public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version);
|
|
|
|
#endregion
|
|
|
|
#region Footer
|
|
|
|
public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins");
|
|
|
|
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_InstallFail(string name) => Loc.Localize("InstallerInstallFail", "Failed to install plugin {0}.").Format(name);
|
|
|
|
public static string ErrorModal_SingleUpdateFail(string name) => Loc.Localize("InstallerSingleUpdateFail", "Failed to update plugin {0}.").Format(name);
|
|
|
|
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}.").Format(name);
|
|
|
|
public static string ErrorModal_DisableFail(string name) => Loc.Localize("InstallerDisableFail", "Failed to disable plugin {0}.").Format(name);
|
|
|
|
public static string ErrorModal_UnloadFail(string name) => Loc.Localize("InstallerUnloadFail", "Failed to unload plugin {0}.").Format(name);
|
|
|
|
public static string ErrorModal_LoadFail(string name) => Loc.Localize("InstallerLoadFail", "Failed to load plugin {0}.").Format(name);
|
|
|
|
public static string ErrorModal_DeleteFail(string name) => Loc.Localize("InstallerDeleteFail", "Failed to delete plugin {0}.").Format(name);
|
|
|
|
public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.");
|
|
|
|
public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.").Format(failCount);
|
|
|
|
public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.").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 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
|
|
}
|
|
|
|
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")]
|
|
private class DownloadedPluginImages
|
|
{
|
|
public TextureWrap IconTex { get; set; }
|
|
|
|
public TextureWrap[] ImagesTex { get; set; }
|
|
}
|
|
}
|
|
}
|