plugin ui
All checks were successful
Build / Compute Version (push) Successful in 17s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 3m5s
Build / Build Nuget package (NuGet) (push) Successful in 2m34s
Build / Build Nuget package (CringePlugins) (push) Successful in 2m56s
Build / Build Nuget package (SharedCringe) (push) Successful in 1m52s
Build / Build Launcher (push) Successful in 3m52s
All checks were successful
Build / Compute Version (push) Successful in 17s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 3m5s
Build / Build Nuget package (NuGet) (push) Successful in 2m34s
Build / Build Nuget package (CringePlugins) (push) Successful in 2m56s
Build / Build Nuget package (SharedCringe) (push) Successful in 1m52s
Build / Build Launcher (push) Successful in 3m52s
This commit is contained in:
310
CringePlugins/Ui/PluginListComponent.cs
Normal file
310
CringePlugins/Ui/PluginListComponent.cs
Normal file
@@ -0,0 +1,310 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using CringePlugins.Abstractions;
|
||||
using CringePlugins.Config;
|
||||
using CringePlugins.Resolver;
|
||||
using ImGuiNET;
|
||||
using NLog;
|
||||
using NuGet;
|
||||
using NuGet.Models;
|
||||
using NuGet.Versioning;
|
||||
using Sandbox.Graphics.GUI;
|
||||
using SpaceEngineers.Game.GUI;
|
||||
using static ImGuiNET.ImGui;
|
||||
|
||||
namespace CringePlugins.Ui;
|
||||
|
||||
public 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 (SearchResultEntry entry, NuGetClient client)? _selected;
|
||||
|
||||
public PluginListComponent(PackagesConfig packagesConfig, PackageSourceMapping sources, string configPath)
|
||||
{
|
||||
_packagesConfig = packagesConfig;
|
||||
_sources = sources;
|
||||
_configPath = configPath;
|
||||
_packages = packagesConfig.Packages.ToImmutableDictionary(b => b.Id, b => b.Range, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
MyGuiSandbox.GuiControlCreated += GuiControlCreated;
|
||||
}
|
||||
|
||||
private void GuiControlCreated(object obj)
|
||||
{
|
||||
if (obj is MyGuiScreenMainMenu)
|
||||
_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 (id, versionRange) in _packages)
|
||||
{
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
Text(id);
|
||||
TableNextColumn();
|
||||
Text(versionRange.MinVersion?.ToString() ?? versionRange.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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user