Add support for plugin profiles
All checks were successful
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (NuGet) (push) Successful in 3m59s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 4m5s
Build / Build Nuget package (SharedCringe) (push) Successful in 4m2s
Build / Build Nuget package (CringePlugins) (push) Successful in 4m20s
Build / Build Launcher (push) Successful in 5m11s
All checks were successful
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (NuGet) (push) Successful in 3m59s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 4m5s
Build / Build Nuget package (SharedCringe) (push) Successful in 4m2s
Build / Build Nuget package (CringePlugins) (push) Successful in 4m20s
Build / Build Launcher (push) Successful in 5m11s
Some minor cleanup
This commit is contained in:
parent
05556c7841
commit
bc88f0c28a
@ -1,96 +0,0 @@
|
||||
#if false
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.Loader;
|
||||
using CringeBootstrap.Abstractions;
|
||||
using CringeLauncher.Loader;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.World;
|
||||
using VRage.Collections;
|
||||
using VRage.Scripting;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class ModAssemblyLoadContextPatches //todo: use ModScriptCompilerPatch
|
||||
{
|
||||
private static ModAssemblyLoadContext? _currentSessionContext;
|
||||
private static readonly MyConcurrentHashSet<string> AssemblyNames = [];
|
||||
|
||||
[HarmonyPatch(typeof(MyScriptCompiler), nameof(MyScriptCompiler.Compile), MethodType.Async)]
|
||||
[HarmonyTranspiler]
|
||||
private static IEnumerable<CodeInstruction> CompilerTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
|
||||
{
|
||||
var matcher = new CodeMatcher(instructions);
|
||||
|
||||
var load1Method = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.Load), [typeof(byte[]), typeof(byte[])]);
|
||||
var load2Method = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.Load), [typeof(byte[])]);
|
||||
|
||||
return matcher.SearchForward(i => i.Calls(load1Method))
|
||||
.InsertAndAdvance(new(OpCodes.Ldarg_0), CodeInstruction.LoadField(original.DeclaringType, "target"))
|
||||
.SetInstruction(CodeInstruction.CallClosure((byte[] assembly, byte[] symbols, MyApiTarget target) =>
|
||||
{
|
||||
//if (target is not MyApiTarget.Mod) return Assembly.Load(assembly, symbols);
|
||||
ArgumentNullException.ThrowIfNull(_currentSessionContext, "No session context");
|
||||
return _currentSessionContext.LoadFromStream(new MemoryStream(assembly), new MemoryStream(symbols));
|
||||
}))
|
||||
.Start()
|
||||
.SearchForward(i => i.Calls(load2Method))
|
||||
.InsertAndAdvance(new(OpCodes.Ldarg_0), CodeInstruction.LoadField(original.DeclaringType, "target"))
|
||||
.SetInstruction(CodeInstruction.CallClosure((byte[] assembly, MyApiTarget target) =>
|
||||
{
|
||||
//if (target is not MyApiTarget.Mod) return Assembly.Load(assembly);
|
||||
ArgumentNullException.ThrowIfNull(_currentSessionContext, "No session context");
|
||||
return _currentSessionContext.LoadFromStream(new MemoryStream(assembly));
|
||||
}))
|
||||
.Instructions();
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MyScriptManager), "Compile")]
|
||||
[HarmonyPrefix]
|
||||
private static bool CompilePrefix(string assemblyName)
|
||||
{
|
||||
if (!AssemblyNames.Add(assemblyName))
|
||||
{
|
||||
Debug.WriteLine($"Duplicate assembly: {assemblyName}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MySession), nameof(MySession.Unload))]
|
||||
[HarmonyPostfix]
|
||||
private static void UnloadPostfix()
|
||||
{
|
||||
AssemblyNames.Clear();
|
||||
if (_currentSessionContext is null) return;
|
||||
|
||||
_currentSessionContext.Unload();
|
||||
_currentSessionContext = null;
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
private static class LoadPrefixes
|
||||
{
|
||||
[HarmonyTargetMethods]
|
||||
private static IEnumerable<MethodInfo> TargetMethods()
|
||||
{
|
||||
yield return AccessTools.Method(typeof(MySession), nameof(MySession.Load));
|
||||
yield return AccessTools.Method(typeof(MySession), "LoadMultiplayer");
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
private static void Prefix()
|
||||
{
|
||||
if (_currentSessionContext is not null)
|
||||
throw new InvalidOperationException("Previous session context was not disposed");
|
||||
|
||||
if (AssemblyLoadContext.GetLoadContext(typeof(MySession).Assembly) is not ICoreLoadContext coreContext)
|
||||
throw new NotSupportedException("Mod loading is not supported in this context");
|
||||
|
||||
_currentSessionContext = new ModAssemblyLoadContext(coreContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -10,7 +10,7 @@ internal sealed class MissingUsingRewriter : ProtoTagRewriter //use existing rew
|
||||
private readonly SemanticModel _semanticModel;
|
||||
private MissingUsingRewriter(CSharpCompilation compilation, SyntaxTree tree) : base(compilation, tree) => _semanticModel = compilation.GetSemanticModel(tree);
|
||||
|
||||
public static SyntaxTree Rewrite(CSharpCompilation compilation, SyntaxTree tree)
|
||||
public static new SyntaxTree Rewrite(CSharpCompilation compilation, SyntaxTree tree)
|
||||
{
|
||||
SyntaxNode syntaxNode = new MissingUsingRewriter(compilation, tree).Visit(tree.GetRoot());
|
||||
return tree.WithRootAndOptions(syntaxNode, tree.Options);
|
||||
|
@ -43,7 +43,7 @@ public class PluginLoaderConfig
|
||||
pluginsBuilder.Add(package);
|
||||
}
|
||||
|
||||
var profiles = new Dictionary<string, ImmutableArray<PackageReference>>();
|
||||
var profiles = ImmutableArray.CreateBuilder<Profile>();
|
||||
foreach (var profile in Profiles)
|
||||
{
|
||||
var builder = ImmutableArray.CreateBuilder<PackageReference>();
|
||||
@ -62,13 +62,13 @@ public class PluginLoaderConfig
|
||||
builder.Add(package);
|
||||
}
|
||||
|
||||
profiles[profile.Name] = builder.ToImmutable();
|
||||
profiles.Add(new(profile.Name, builder.ToImmutable()));
|
||||
}
|
||||
|
||||
return old with
|
||||
{
|
||||
Packages = pluginsBuilder.ToImmutable(),
|
||||
Profiles = profiles,
|
||||
Profiles = profiles.ToImmutable(),
|
||||
Sources = sources.ToImmutable()
|
||||
};
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ namespace CringePlugins.Config;
|
||||
public sealed class ConfigHandler
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
|
||||
private readonly DirectoryInfo _configDirectory;
|
||||
private readonly JsonSerializerOptions _serializerOptions = new(NuGetClient.SerializerOptions)
|
||||
{
|
||||
@ -35,7 +35,7 @@ public sealed class ConfigHandler
|
||||
public ConfigReference<T> RegisterConfig<T>(string name, T? defaultInstance = null) where T : class
|
||||
{
|
||||
var spec = IConfigurationSpecProvider.FromType(typeof(T));
|
||||
|
||||
|
||||
var path = Path.Join(_configDirectory.FullName, $"{name}.json");
|
||||
var backupPath = path + $".bak.{DateTimeOffset.Now.ToUnixTimeSeconds()}";
|
||||
|
||||
@ -63,10 +63,20 @@ public sealed class ConfigHandler
|
||||
RegisterChange(name, defaultInstance);
|
||||
return reference;
|
||||
}
|
||||
|
||||
var instance = jsonNode.Deserialize<T>(_serializerOptions)!;
|
||||
|
||||
T instance;
|
||||
try
|
||||
{
|
||||
instance = jsonNode.Deserialize<T>(_serializerOptions)!;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Log.Warn(e, "Failed to load config {Name}", name);
|
||||
|
||||
instance = defaultInstance ?? Activator.CreateInstance<T>();
|
||||
}
|
||||
ConfigReloaded?.Invoke(this, new ConfigValue<T>(name, instance));
|
||||
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
@ -78,16 +88,16 @@ public sealed class ConfigHandler
|
||||
|
||||
if (spec != null && !TryValidate(name, spec, jsonNode))
|
||||
throw new JsonException($"Supplied config value for {name} is invalid");
|
||||
|
||||
|
||||
var path = Path.Join(_configDirectory.FullName, $"{name}.json");
|
||||
|
||||
|
||||
using var stream = File.Create(path);
|
||||
using var writer = new Utf8JsonWriter(stream, new()
|
||||
{
|
||||
Indented = true
|
||||
});
|
||||
jsonNode.WriteTo(writer, _serializerOptions);
|
||||
|
||||
|
||||
ConfigReloaded?.Invoke(this, new ConfigValue<T>(name, newValue));
|
||||
}
|
||||
|
||||
@ -97,7 +107,7 @@ public sealed class ConfigHandler
|
||||
|
||||
if (results.IsValid)
|
||||
return true;
|
||||
|
||||
|
||||
Log.Error("Config {Name} is invalid:", name);
|
||||
foreach (var detail in results.Details)
|
||||
{
|
||||
@ -107,7 +117,7 @@ public sealed class ConfigHandler
|
||||
Log.Error("\t- {Error}", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -145,6 +155,6 @@ public sealed class ConfigReference<T> : IDisposable
|
||||
{
|
||||
_instance.ConfigReloaded -= InstanceOnConfigReloaded;
|
||||
}
|
||||
|
||||
|
||||
public static implicit operator T(ConfigReference<T> reference) => reference.Value;
|
||||
}
|
@ -4,7 +4,7 @@ using NuGet;
|
||||
|
||||
namespace CringePlugins.Config;
|
||||
|
||||
public record PackagesConfig(ImmutableArray<PackageSource> Sources, ImmutableArray<PackageReference> Packages, Dictionary<string, ImmutableArray<PackageReference>> Profiles)
|
||||
public record PackagesConfig(ImmutableArray<PackageSource> Sources, ImmutableArray<PackageReference> Packages, ImmutableArray<Profile> Profiles)
|
||||
{
|
||||
public static PackagesConfig Default { get; } = new([
|
||||
new("zznty", @"^SpaceEngineersDedicated\.ReferenceAssemblies$|^ImGui\.NET\.DirectX$|^NuGet$|^Cringe.+$|^SharedCringe$|^Plugin.+$", "https://ng.zznty.ru/v3/index.json"),
|
||||
@ -13,5 +13,6 @@ public record PackagesConfig(ImmutableArray<PackageSource> Sources, ImmutableArr
|
||||
[
|
||||
new PackageReference("Plugin.ClientModLoader", new(new(0,0,0)))
|
||||
],
|
||||
[]); //todo: default profile with recommended plugins?
|
||||
}
|
||||
[]);
|
||||
}
|
||||
public record Profile(string Id, ImmutableArray<PackageReference> Plugins);
|
@ -1,8 +1,4 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Xml.Serialization;
|
||||
using CringePlugins.Abstractions;
|
||||
using CringePlugins.Abstractions;
|
||||
using CringePlugins.Compatability;
|
||||
using CringePlugins.Config;
|
||||
using CringePlugins.Loader;
|
||||
@ -18,6 +14,11 @@ using NuGet.Versioning;
|
||||
using Sandbox.Game.Gui;
|
||||
using Sandbox.Graphics.GUI;
|
||||
using SpaceEngineers.Game.GUI;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using System.Xml.Serialization;
|
||||
using static ImGuiNET.ImGui;
|
||||
|
||||
namespace CringePlugins.Ui;
|
||||
@ -32,6 +33,12 @@ internal class PluginListComponent : IRenderComponent
|
||||
private string _searchQuery = "";
|
||||
private Task? _searchTask;
|
||||
|
||||
private string _profileSearch = "";
|
||||
private string _newProfileName = "";
|
||||
private int _selectedProfile = -1;
|
||||
private ImmutableArray<Profile> _profiles;
|
||||
|
||||
|
||||
private bool _changed;
|
||||
private bool _open = true;
|
||||
private readonly ConfigReference<PackagesConfig> _packagesConfig;
|
||||
@ -53,6 +60,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
_plugins = plugins;
|
||||
_packages = packagesConfig.Value.Packages.ToImmutableDictionary(b => b.Id, b => b.Range,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
_profiles = packagesConfig.Value.Profiles;
|
||||
|
||||
MyScreenManager.ScreenAdded += ScreenChanged;
|
||||
MyScreenManager.ScreenRemoved += ScreenChanged;
|
||||
@ -92,9 +100,9 @@ internal class PluginListComponent : IRenderComponent
|
||||
{
|
||||
if (BeginTable("InstalledTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.Sortable))
|
||||
{
|
||||
TableSetupColumn("Id", ImGuiTableColumnFlags.None, .5f, (uint)Columns.Id);
|
||||
TableSetupColumn("Version", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Version);
|
||||
TableSetupColumn("Source", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Source);
|
||||
TableSetupColumn("Id", ImGuiTableColumnFlags.WidthStretch, .5f, (uint)Columns.Id);
|
||||
TableSetupColumn("Version", ImGuiTableColumnFlags.WidthStretch, .25f, (uint)Columns.Version);
|
||||
TableSetupColumn("Source", ImGuiTableColumnFlags.WidthStretch, .25f, (uint)Columns.Source);
|
||||
TableHeadersRow();
|
||||
|
||||
var sortSpecs = TableGetSortSpecs();
|
||||
@ -143,6 +151,12 @@ internal class PluginListComponent : IRenderComponent
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem("Profiles"))
|
||||
{
|
||||
ProfilesTab();
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem("Sources Configuration"))
|
||||
{
|
||||
BeginChild("Sources List", new(400, 0), ImGuiChildFlags.Border | ImGuiChildFlags.ResizeX);
|
||||
@ -150,8 +164,8 @@ internal class PluginListComponent : IRenderComponent
|
||||
if (BeginTable("Sources Table", 2,
|
||||
ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
TableSetupColumn("Name", ImGuiTableColumnFlags.None, .2f);
|
||||
TableSetupColumn("Url", ImGuiTableColumnFlags.None, .8f);
|
||||
TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, .2f);
|
||||
TableSetupColumn("Url", ImGuiTableColumnFlags.WidthStretch, .8f);
|
||||
TableHeadersRow();
|
||||
|
||||
for (var index = 0; index < _packagesConfig.Value.Sources.Length; index++)
|
||||
@ -321,6 +335,173 @@ internal class PluginListComponent : IRenderComponent
|
||||
End();
|
||||
}
|
||||
|
||||
private unsafe void ProfilesTab()
|
||||
{
|
||||
InputText("##searchbox", ref _profileSearch, 256);
|
||||
|
||||
SameLine();
|
||||
|
||||
if (Button("Create New Profile"))
|
||||
OpenPopup("New Profile");
|
||||
|
||||
|
||||
if (IsItemHovered(ImGuiHoveredFlags.ForTooltip))
|
||||
{
|
||||
SetTooltip("Create a new profile from enabled plugins");
|
||||
}
|
||||
|
||||
if (BeginPopupModal("New Profile", ImGuiWindowFlags.AlwaysAutoResize))
|
||||
{
|
||||
InputText("Name", ref _newProfileName, 50);
|
||||
Separator();
|
||||
|
||||
if (Button("Ok##newProfileOk", new Vector2(120, 0)))
|
||||
{
|
||||
var len = _profiles.Length;
|
||||
_profiles = _profiles.Add(new(_newProfileName, [.. _packages.Select(x => new PackageReference(x.Key, x.Value))]));
|
||||
_selectedProfile = len;
|
||||
|
||||
_packagesConfig.Value = _packagesConfig.Value with
|
||||
{
|
||||
Profiles = _profiles
|
||||
};
|
||||
|
||||
CloseCurrentPopup();
|
||||
}
|
||||
SetItemDefaultFocus();
|
||||
SameLine();
|
||||
if (Button("Cancel##newProfileCancel", new Vector2(120, 0)))
|
||||
{
|
||||
CloseCurrentPopup();
|
||||
}
|
||||
|
||||
EndPopup();
|
||||
}
|
||||
|
||||
Spacing();
|
||||
|
||||
if (_profiles.IsEmpty)
|
||||
{
|
||||
TextDisabled("No Profiles");
|
||||
return;
|
||||
}
|
||||
|
||||
BeginChild("Profile List", new(400, 0), ImGuiChildFlags.Border | ImGuiChildFlags.ResizeX);
|
||||
|
||||
if (BeginTable("ProfilesTable", 2,
|
||||
ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
TableSetupColumn("Id##ProfilesTable", ImGuiTableColumnFlags.WidthStretch, .5f, (uint)Columns.Id);
|
||||
TableSetupColumn("Plugins", ImGuiTableColumnFlags.WidthStretch, .25f, (uint)Columns.Count);
|
||||
TableHeadersRow();
|
||||
|
||||
for (var i = 0; i < _profiles.Length; i++)
|
||||
{
|
||||
var(id, plugins) = _profiles[i];
|
||||
|
||||
if (!id.Contains(_profileSearch, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
|
||||
var selected = _selectedProfile == i;
|
||||
|
||||
if (Selectable($"{id}##profiles{i}", ref selected, ImGuiSelectableFlags.SpanAllColumns))
|
||||
{
|
||||
_selectedProfile = selected ? i : -1;
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
Text(plugins.Length.ToString());
|
||||
}
|
||||
|
||||
EndTable();
|
||||
}
|
||||
|
||||
EndChild();
|
||||
|
||||
SameLine();
|
||||
|
||||
BeginGroup();
|
||||
|
||||
BeginChild("Profile View", new(0, -GetFrameHeightWithSpacing())); // Leave room for 1 line below us
|
||||
|
||||
if (_selectedProfile >= 0)
|
||||
{
|
||||
var (id, plugins) = _profiles[_selectedProfile];
|
||||
|
||||
Text(id);
|
||||
Separator();
|
||||
|
||||
if (BeginTable("ProfilePluginsTable", 2, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
TableSetupColumn("Id##pluginProfilesId", ImGuiTableColumnFlags.WidthStretch, .5f, (uint)Columns.Id);
|
||||
TableSetupColumn("Version##pluginProfilesVersion", ImGuiTableColumnFlags.WidthStretch, .25f, (uint)Columns.Version);
|
||||
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
|
||||
Text(plugin.Id);
|
||||
|
||||
TableNextColumn();
|
||||
Text(plugin.Range.ToShortString());
|
||||
}
|
||||
|
||||
EndTable();
|
||||
}
|
||||
}
|
||||
EndChild();
|
||||
|
||||
if (_selectedProfile >= 0)
|
||||
{
|
||||
if (Button("Activate"))
|
||||
{
|
||||
_packages = _profiles[_selectedProfile].Plugins.ToImmutableDictionary(b => b.Id, b => b.Range);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
SameLine();
|
||||
if (Button("Delete"))
|
||||
OpenPopup("Delete?##ProfileDeletePopup");
|
||||
|
||||
if (BeginPopupModal("Delete?##ProfileDeletePopup", ImGuiWindowFlags.AlwaysAutoResize))
|
||||
{
|
||||
Text("Are you sure you want to delete this profile?");
|
||||
Separator();
|
||||
|
||||
if (Button("Yes", new Vector2(120, 0)))
|
||||
{
|
||||
_profiles = _profiles.RemoveAt(_selectedProfile);
|
||||
|
||||
_packagesConfig.Value = _packagesConfig.Value with
|
||||
{
|
||||
Profiles = _profiles
|
||||
};
|
||||
|
||||
_selectedProfile = -1;
|
||||
|
||||
CloseCurrentPopup();
|
||||
}
|
||||
SetItemDefaultFocus();
|
||||
SameLine();
|
||||
if (Button("No", new Vector2(120, 0)))
|
||||
{
|
||||
CloseCurrentPopup();
|
||||
}
|
||||
|
||||
EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
// TODO sources editor
|
||||
// TODO combobox with active sources (to limit search results to specific list of sources)
|
||||
private unsafe void AvailablePluginsTab()
|
||||
@ -357,6 +538,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
: (_selectedSources ?? _packagesConfig.Value.Sources.ToImmutableHashSet()).Remove(source);
|
||||
|
||||
_searchTask = RefreshAsync();
|
||||
EndCombo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -398,9 +580,9 @@ internal class PluginListComponent : IRenderComponent
|
||||
if (BeginTable("AvailableTable", 3,
|
||||
ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.Sortable))
|
||||
{
|
||||
TableSetupColumn("Id", ImGuiTableColumnFlags.None, .5f, (uint)Columns.Id);
|
||||
TableSetupColumn("Version", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Version);
|
||||
TableSetupColumn("Installed", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Installed);
|
||||
TableSetupColumn("Id", ImGuiTableColumnFlags.WidthStretch, .5f, (uint)Columns.Id);
|
||||
TableSetupColumn("Version", ImGuiTableColumnFlags.WidthStretch, .25f, (uint)Columns.Version);
|
||||
TableSetupColumn("Installed", ImGuiTableColumnFlags.WidthStretch, .25f, (uint)Columns.Installed);
|
||||
TableHeadersRow();
|
||||
|
||||
var sortSpecs = TableGetSortSpecs();
|
||||
@ -569,9 +751,9 @@ internal class PluginListComponent : IRenderComponent
|
||||
_searchResults = builder.ToImmutable();
|
||||
}
|
||||
|
||||
private void Save(bool keepPackages = true)
|
||||
private void Save(bool keepChanges = true)
|
||||
{
|
||||
_packagesConfig.Value = keepPackages ? _packagesConfig.Value with
|
||||
_packagesConfig.Value = keepChanges ? _packagesConfig.Value with
|
||||
{
|
||||
Packages = [.. _packages.Select(b => new PackageReference(b.Key, b.Value))]
|
||||
} : _packagesConfig;
|
||||
@ -649,6 +831,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
Id,
|
||||
Version,
|
||||
Source,
|
||||
Installed
|
||||
Installed,
|
||||
Count
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user