Files
se-launcher/CringePlugins/Ui/PluginListComponent.cs
zznty 8cddcab26c
All checks were successful
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 1m35s
Build / Build Nuget package (NuGet) (push) Successful in 2m2s
Build / Build Nuget package (CringePlugins) (push) Successful in 3m2s
Build / Build Nuget package (SharedCringe) (push) Successful in 2m12s
Build / Build Launcher (push) Successful in 3m27s
show actual plugins in installed tab
2024-11-03 02:54:47 +07:00

315 lines
10 KiB
C#

using System.Collections.Immutable;
using System.Text.Json;
using CringePlugins.Abstractions;
using CringePlugins.Config;
using CringePlugins.Loader;
using CringePlugins.Resolver;
using ImGuiNET;
using NLog;
using NuGet;
using NuGet.Models;
using NuGet.Versioning;
using Sandbox.Game.Gui;
using Sandbox.Graphics.GUI;
using SpaceEngineers.Game.GUI;
using static ImGuiNET.ImGui;
namespace CringePlugins.Ui;
internal class PluginListComponent : IRenderComponent
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private ImmutableDictionary<string, VersionRange> _packages;
private ImmutableDictionary<NuGetClient, SearchResult>? _searchResults;
private string _searchQuery = "";
private Task? _searchTask;
private bool _changed;
private bool _open = true;
private readonly PackagesConfig _packagesConfig;
private readonly PackageSourceMapping _sources;
private readonly string _configPath;
private readonly ImmutableArray<PluginInstance> _plugins;
private (SearchResultEntry entry, NuGetClient client)? _selected;
public PluginListComponent(PackagesConfig packagesConfig, PackageSourceMapping sources, string configPath,
ImmutableArray<PluginInstance> plugins)
{
_packagesConfig = packagesConfig;
_sources = sources;
_configPath = configPath;
_plugins = plugins;
_packages = packagesConfig.Packages.ToImmutableDictionary(b => b.Id, b => b.Range, StringComparer.OrdinalIgnoreCase);
MyGuiSandbox.GuiControlCreated += GuiControlCreated;
}
private void GuiControlCreated(object obj)
{
if (obj is MyGuiScreenMainMenu && MyGuiScreenGamePlay.Static is null)
_open = true;
}
public void OnFrame()
{
if (!_open) return;
SetNextWindowSize(new(700, 500), ImGuiCond.FirstUseEver);
if (!Begin("Plugin List", ref _open))
{
End();
return;
}
if (_changed)
TextDisabled("Changes would be applied on the next restart");
if (BeginTabBar("Main"))
{
// TODO support for opening plugin loader plugin config (reflection call to a specific method)
if (BeginTabItem("Installed Plugins"))
{
if (BeginTable("InstalledTable", 2, ImGuiTableFlags.ScrollY))
{
TableSetupColumn("Id");
TableSetupColumn("Version");
TableHeadersRow();
foreach (var plugin in _plugins)
{
TableNextRow();
TableNextColumn();
Text(plugin.Metadata.Name);
TableNextColumn();
Text(plugin.Metadata.Version.ToString());
}
EndTable();
}
EndTabItem();
}
if (BeginTabItem("Available Plugins"))
{
AvailablePluginsTab();
EndTabItem();
}
EndTabBar();
}
End();
}
// TODO sources editor
// TODO combobox with active sources (to limit search results to specific list of sources)
private unsafe void AvailablePluginsTab()
{
if (_searchResults is null && _searchTask is null)
{
_searchTask = RefreshAsync();
}
switch (_searchTask)
{
case { IsCompleted: false }:
TextDisabled("Loading...");
return;
case { IsCompletedSuccessfully: false }:
{
TextDisabled("Failed to load plugins list");
if (_searchTask.Exception is null) return;
foreach (var exception in _searchTask.Exception.InnerExceptions)
{
TextWrapped($"{exception.GetType()}: {exception.Message}");
}
return;
}
}
var searchResults = _searchResults ?? ImmutableDictionary<NuGetClient, SearchResult>.Empty;
InputText("", ref _searchQuery, 256);
SameLine();
if (Button("Search"))
{
_searchTask = RefreshAsync();
return;
}
if (searchResults.IsEmpty || searchResults.Values.All(b => b.Entries.IsEmpty))
{
TextDisabled("Nothing found");
return;
}
BeginChild("List", new(400, 0), ImGuiChildFlags.Border | ImGuiChildFlags.ResizeX);
{
if (BeginTable("AvailableTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp))
{
TableSetupColumn("Id", ImGuiTableColumnFlags.None, .5f);
TableSetupColumn("Version", ImGuiTableColumnFlags.None, .3f);
TableSetupColumn("Installed", ImGuiTableColumnFlags.None, .2f);
TableHeadersRow();
foreach (var (client, result) in searchResults)
{
foreach (var package in result.Entries.Take(100))
{
TableNextRow();
TableNextColumn();
var selected = _selected?.entry.Id.Equals(package.Id, StringComparison.OrdinalIgnoreCase) == true;
if (Selectable(package.Title ?? package.Id, ref selected, ImGuiSelectableFlags.SpanAllColumns))
{
_selected = selected ? (package, client) : null;
}
if (!string.IsNullOrEmpty(package.Summary) && IsItemHovered(ImGuiHoveredFlags.ForTooltip))
{
SetTooltip(package.Summary);
}
TableNextColumn();
Text(package.Version.ToString());
TableNextColumn();
var installed = _packages.ContainsKey(package.Id);
BeginDisabled();
if (Checkbox("", ref installed))
{
_packages = installed
? _packages.Add(package.Id, new(package.Version))
: _packages.Remove(package.Id);
Save();
}
EndDisabled();
}
}
EndTable();
}
EndChild();
}
SameLine();
BeginGroup();
BeginChild("Package View", new(0, -GetFrameHeightWithSpacing())); // Leave room for 1 line below us
if (_selected is not null)
{
var selected = _selected.Value.entry;
Text(selected.Title ?? selected.Id);
SameLine();
TextColored(*GetStyleColorVec4(ImGuiCol.TextLink), selected.Version.ToString());
Separator();
if (BeginTabBar("##PackageViewTabs"))
{
if (BeginTabItem("Description"))
{
TextWrapped(selected.Description ?? "Nothing.");
EndTabItem();
}
if (BeginTabItem("Details"))
{
Text("Pulled from");
SameLine();
var url = _selected.Value.client.ToString();
TextLinkOpenURL(url, url);
if (selected.Authors is not null)
{
Text("Authors:");
SameLine();
TextWrapped(selected.Authors.Author);
}
if (selected.TotalDownloads.HasValue)
{
Text("Total downloads:");
SameLine();
TextColored(*GetStyleColorVec4(ImGuiCol.TextLink), selected.TotalDownloads.ToString());
}
if (!string.IsNullOrEmpty(selected.ProjectUrl))
TextLinkOpenURL("Project URL", selected.ProjectUrl);
EndTabItem();
}
EndTabBar();
}
}
EndChild();
if (_selected is not null)
{
var selected = _selected.Value.entry;
var installed = _packages.ContainsKey(selected.Id);
if (Button(installed ? "Uninstall" : "Install"))
{
_packages = installed
? _packages.Remove(selected.Id)
: _packages.Add(selected.Id, new(selected.Version));
Save();
}
}
EndGroup();
}
private async Task RefreshAsync()
{
_searchResults = null;
var builder = ImmutableDictionary.CreateBuilder<NuGetClient, SearchResult>();
await foreach (var source in _sources)
{
try
{
var result = await source.SearchPackagesAsync(_searchQuery, take: 1000, packageType: "CringePlugin");
builder.Add(source, result);
}
catch (Exception e)
{
Log.Error(e, "Failed to get packages from source {Source}", source);
}
}
_searchResults = builder.ToImmutable();
}
private void Save()
{
_changed = true;
using var stream = File.Create(_configPath);
JsonSerializer.Serialize(stream, _packagesConfig with
{
Packages = [.._packages.Select(b => new PackageReference(b.Key, b.Value))]
}, NuGetClient.SerializerOptions);
}
}