mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Core - Delay start watching plugins for hot reload after initializing
UI - Simplify category management logic UI - Avoid crash during profile icon load Storage - Fix entry metadata retrieval
This commit is contained in:
parent
f66df43cc2
commit
d7a0c2ac4a
@ -98,26 +98,15 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a profile configuration to this category
|
/// Adds a profile configuration to this category
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex)
|
public void AddProfileConfiguration(ProfileConfiguration configuration, ProfileConfiguration? target)
|
||||||
{
|
{
|
||||||
List<ProfileConfiguration> targetList = ProfileConfigurations.ToList();
|
List<ProfileConfiguration> targetList = ProfileConfigurations.Where(c => c!= configuration).ToList();
|
||||||
|
|
||||||
// TODO: Look into this, it doesn't seem to make sense
|
|
||||||
// Removing the original will shift every item in the list forwards, keep that in mind with the target index
|
|
||||||
if (configuration.Category == this && targetIndex != null && targetIndex.Value > targetList.IndexOf(configuration))
|
|
||||||
targetIndex -= 1;
|
|
||||||
|
|
||||||
configuration.Category.RemoveProfileConfiguration(configuration);
|
configuration.Category.RemoveProfileConfiguration(configuration);
|
||||||
|
|
||||||
if (targetIndex != null)
|
if (target != null)
|
||||||
{
|
targetList.Insert(targetList.IndexOf(target), configuration);
|
||||||
targetIndex = Math.Clamp(targetIndex.Value, 0, targetList.Count);
|
|
||||||
targetList.Insert(targetIndex.Value, configuration);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
targetList.Add(configuration);
|
targetList.Add(configuration);
|
||||||
}
|
|
||||||
|
|
||||||
configuration.Category = this;
|
configuration.Category = this;
|
||||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(targetList);
|
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(targetList);
|
||||||
|
|||||||
@ -357,10 +357,10 @@ public class Plugin : CorePropertyChanged, IDisposable
|
|||||||
return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled);
|
return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AutoEnableIfNew()
|
internal bool AutoEnableIfNew()
|
||||||
{
|
{
|
||||||
if (_loadedFromStorage)
|
if (_loadedFromStorage)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
// Enabled is preset to true if the plugin meets the following criteria
|
// Enabled is preset to true if the plugin meets the following criteria
|
||||||
// - Requires no admin rights
|
// - Requires no admin rights
|
||||||
@ -371,11 +371,13 @@ public class Plugin : CorePropertyChanged, IDisposable
|
|||||||
Info.ArePrerequisitesMet();
|
Info.ArePrerequisitesMet();
|
||||||
|
|
||||||
if (!Entity.IsEnabled)
|
if (!Entity.IsEnabled)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
// Also auto-enable any non-device provider feature
|
// Also auto-enable any non-device provider feature
|
||||||
foreach (PluginFeatureInfo pluginFeatureInfo in Features)
|
foreach (PluginFeatureInfo pluginFeatureInfo in Features)
|
||||||
pluginFeatureInfo.Entity.IsEnabled = !pluginFeatureInfo.FeatureType.IsAssignableTo(typeof(DeviceProvider));
|
pluginFeatureInfo.Entity.IsEnabled = !pluginFeatureInfo.FeatureType.IsAssignableTo(typeof(DeviceProvider));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -71,6 +71,7 @@ internal class CoreService : ICoreService
|
|||||||
// Initialize the services
|
// Initialize the services
|
||||||
_pluginManagementService.CopyBuiltInPlugins();
|
_pluginManagementService.CopyBuiltInPlugins();
|
||||||
_pluginManagementService.LoadPlugins(IsElevated);
|
_pluginManagementService.LoadPlugins(IsElevated);
|
||||||
|
_pluginManagementService.StartHotReload();
|
||||||
_renderService.Initialize();
|
_renderService.Initialize();
|
||||||
|
|
||||||
OnInitialized();
|
OnInitialized();
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
|||||||
/// Gets a list containing additional directories in which plugins are located, used while loading plugins.
|
/// Gets a list containing additional directories in which plugins are located, used while loading plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
List<DirectoryInfo> AdditionalPluginDirectories { get; }
|
List<DirectoryInfo> AdditionalPluginDirectories { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whether or not plugins are currently being loaded
|
/// Indicates whether or not plugins are currently being loaded
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -33,6 +33,11 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void LoadPlugins(bool isElevated);
|
void LoadPlugins(bool isElevated);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts monitoring plugin directories for changes and reloads plugins when changes are detected
|
||||||
|
/// </summary>
|
||||||
|
void StartHotReload();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unloads all installed plugins.
|
/// Unloads all installed plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -145,7 +150,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
|||||||
/// <param name="device"></param>
|
/// <param name="device"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when built-in plugins are being loaded
|
/// Occurs when built-in plugins are being loaded
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -41,37 +41,6 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
_pluginRepository = pluginRepository;
|
_pluginRepository = pluginRepository;
|
||||||
_deviceRepository = deviceRepository;
|
_deviceRepository = deviceRepository;
|
||||||
_plugins = new List<Plugin>();
|
_plugins = new List<Plugin>();
|
||||||
|
|
||||||
StartHotReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
|
||||||
{
|
|
||||||
ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json");
|
|
||||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory));
|
|
||||||
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
|
||||||
|
|
||||||
// Remove the old directory if it exists
|
|
||||||
if (Directory.Exists(pluginDirectory.FullName))
|
|
||||||
pluginDirectory.Delete(true);
|
|
||||||
|
|
||||||
// Extract everything in the same archive directory to the unique plugin directory
|
|
||||||
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);
|
|
||||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
|
||||||
foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries)
|
|
||||||
{
|
|
||||||
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/"))
|
|
||||||
{
|
|
||||||
string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
|
||||||
// Create folders
|
|
||||||
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
|
||||||
// Extract files
|
|
||||||
zipArchiveEntry.ExtractToFile(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createLockFile)
|
|
||||||
File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<DirectoryInfo> AdditionalPluginDirectories { get; } = new();
|
public List<DirectoryInfo> AdditionalPluginDirectories { get; } = new();
|
||||||
@ -155,6 +124,35 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
||||||
|
{
|
||||||
|
ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json");
|
||||||
|
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory));
|
||||||
|
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
||||||
|
|
||||||
|
// Remove the old directory if it exists
|
||||||
|
if (Directory.Exists(pluginDirectory.FullName))
|
||||||
|
pluginDirectory.Delete(true);
|
||||||
|
|
||||||
|
// Extract everything in the same archive directory to the unique plugin directory
|
||||||
|
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);
|
||||||
|
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
||||||
|
foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries)
|
||||||
|
{
|
||||||
|
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/"))
|
||||||
|
{
|
||||||
|
string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
||||||
|
// Create folders
|
||||||
|
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
||||||
|
// Extract files
|
||||||
|
zipArchiveEntry.ExtractToFile(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createLockFile)
|
||||||
|
File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public List<Plugin> GetAllPlugins()
|
public List<Plugin> GetAllPlugins()
|
||||||
@ -444,7 +442,9 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
_logger.Warning("Plugin {plugin} contains no features", plugin);
|
_logger.Warning("Plugin {plugin} contains no features", plugin);
|
||||||
|
|
||||||
// It is appropriate to call this now that we have the features of this plugin
|
// It is appropriate to call this now that we have the features of this plugin
|
||||||
plugin.AutoEnableIfNew();
|
bool autoEnabled = plugin.AutoEnableIfNew();
|
||||||
|
if (autoEnabled)
|
||||||
|
_pluginRepository.SavePlugin(entity);
|
||||||
|
|
||||||
List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(PluginBootstrapper).IsAssignableFrom(t)).ToList();
|
List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(PluginBootstrapper).IsAssignableFrom(t)).ToList();
|
||||||
if (bootstrappers.Count > 1)
|
if (bootstrappers.Count > 1)
|
||||||
@ -894,7 +894,7 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
|
|
||||||
#region Hot Reload
|
#region Hot Reload
|
||||||
|
|
||||||
private void StartHotReload()
|
public void StartHotReload()
|
||||||
{
|
{
|
||||||
// Watch for changes in the plugin directory, "plugin.json".
|
// Watch for changes in the plugin directory, "plugin.json".
|
||||||
// If this file is changed, reload the plugin.
|
// If this file is changed, reload the plugin.
|
||||||
|
|||||||
@ -115,9 +115,9 @@ public interface IProfileService : IArtemisService
|
|||||||
/// any changes are made to it.
|
/// any changes are made to it.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash).</param>
|
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash).</param>
|
||||||
/// <param name="targetIndex">The index at which to import the profile into the category.</param>
|
/// <param name="target">The profile before which to import the profile into the category.</param>
|
||||||
/// <returns>The resulting profile configuration.</returns>
|
/// <returns>The resulting profile configuration.</returns>
|
||||||
Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported", int targetIndex = 0);
|
Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported", ProfileConfiguration? target = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Imports the provided ZIP archive stream into the provided profile configuration
|
/// Imports the provided ZIP archive stream into the provided profile configuration
|
||||||
|
|||||||
@ -262,7 +262,7 @@ internal class ProfileService : IProfileService
|
|||||||
{
|
{
|
||||||
ProfileConfiguration configuration = new(category, name, icon);
|
ProfileConfiguration configuration = new(category, name, icon);
|
||||||
|
|
||||||
category.AddProfileConfiguration(configuration, 0);
|
category.AddProfileConfiguration(configuration, category.ProfileConfigurations.FirstOrDefault());
|
||||||
SaveProfileCategory(category);
|
SaveProfileCategory(category);
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
@ -354,7 +354,7 @@ internal class ProfileService : IProfileService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix, int targetIndex = 0)
|
public async Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix, ProfileConfiguration? target)
|
||||||
{
|
{
|
||||||
using ZipArchive archive = new(archiveStream, ZipArchiveMode.Read, true);
|
using ZipArchive archive = new(archiveStream, ZipArchiveMode.Read, true);
|
||||||
|
|
||||||
@ -424,7 +424,7 @@ internal class ProfileService : IProfileService
|
|||||||
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
|
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
|
||||||
|
|
||||||
profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id;
|
profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id;
|
||||||
category.AddProfileConfiguration(profileConfiguration, targetIndex);
|
category.AddProfileConfiguration(profileConfiguration, target);
|
||||||
|
|
||||||
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
|
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
|
||||||
profileConfiguration.LoadModules(modules);
|
profileConfiguration.LoadModules(modules);
|
||||||
@ -436,7 +436,7 @@ internal class ProfileService : IProfileService
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ProfileConfiguration> OverwriteProfile(MemoryStream archiveStream, ProfileConfiguration profileConfiguration)
|
public async Task<ProfileConfiguration> OverwriteProfile(MemoryStream archiveStream, ProfileConfiguration profileConfiguration)
|
||||||
{
|
{
|
||||||
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration.Order + 1);
|
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration);
|
||||||
|
|
||||||
RemoveProfileConfiguration(profileConfiguration);
|
RemoveProfileConfiguration(profileConfiguration);
|
||||||
SaveProfileCategory(imported.Category);
|
SaveProfileCategory(imported.Category);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
namespace Artemis.Storage.Legacy.Entities.Workshop;
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace Artemis.Storage.Legacy.Entities.Workshop;
|
||||||
|
|
||||||
internal class EntryEntity
|
internal class EntryEntity
|
||||||
{
|
{
|
||||||
@ -14,7 +16,7 @@ internal class EntryEntity
|
|||||||
public string ReleaseVersion { get; set; } = string.Empty;
|
public string ReleaseVersion { get; set; } = string.Empty;
|
||||||
public DateTimeOffset InstalledAt { get; set; }
|
public DateTimeOffset InstalledAt { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, object>? Metadata { get; set; }
|
public Dictionary<string, JsonNode>? Metadata { get; set; }
|
||||||
|
|
||||||
public Storage.Entities.Workshop.EntryEntity Migrate()
|
public Storage.Entities.Workshop.EntryEntity Migrate()
|
||||||
{
|
{
|
||||||
@ -29,7 +31,7 @@ internal class EntryEntity
|
|||||||
ReleaseId = ReleaseId,
|
ReleaseId = ReleaseId,
|
||||||
ReleaseVersion = ReleaseVersion,
|
ReleaseVersion = ReleaseVersion,
|
||||||
InstalledAt = InstalledAt,
|
InstalledAt = InstalledAt,
|
||||||
Metadata = Metadata ?? new Dictionary<string, object>()
|
Metadata = Metadata ?? new Dictionary<string, JsonNode>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using Artemis.Storage.Entities.General;
|
using Artemis.Storage.Entities.General;
|
||||||
using Artemis.Storage.Entities.Plugins;
|
using Artemis.Storage.Entities.Plugins;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
@ -41,7 +42,7 @@ public class ArtemisDbContext : DbContext
|
|||||||
.Property(e => e.Metadata)
|
.Property(e => e.Metadata)
|
||||||
.HasConversion(
|
.HasConversion(
|
||||||
v => JsonSerializer.Serialize(v, JsonSerializerOptions),
|
v => JsonSerializer.Serialize(v, JsonSerializerOptions),
|
||||||
v => JsonSerializer.Deserialize<Dictionary<string, object>>(v, JsonSerializerOptions) ?? new Dictionary<string, object>());
|
v => JsonSerializer.Deserialize<Dictionary<string, JsonNode>>(v, JsonSerializerOptions) ?? new Dictionary<string, JsonNode>());
|
||||||
|
|
||||||
modelBuilder.Entity<ProfileContainerEntity>()
|
modelBuilder.Entity<ProfileContainerEntity>()
|
||||||
.Property(e => e.ProfileConfiguration)
|
.Property(e => e.ProfileConfiguration)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Artemis.Storage.Entities.Workshop;
|
namespace Artemis.Storage.Entities.Workshop;
|
||||||
@ -19,5 +20,5 @@ public class EntryEntity
|
|||||||
public string ReleaseVersion { get; set; } = string.Empty;
|
public string ReleaseVersion { get; set; } = string.Empty;
|
||||||
public DateTimeOffset InstalledAt { get; set; }
|
public DateTimeOffset InstalledAt { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, object>? Metadata { get; set; }
|
public Dictionary<string, JsonNode>? Metadata { get; set; }
|
||||||
}
|
}
|
||||||
@ -8,10 +8,11 @@ using Artemis.Storage.Exceptions;
|
|||||||
using Artemis.Storage.Migrations;
|
using Artemis.Storage.Migrations;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.Storage.Repositories;
|
namespace Artemis.Storage.Repositories;
|
||||||
|
|
||||||
public class ProfileRepository(Func<ArtemisDbContext> getContext, List<IProfileMigration> profileMigrators) : IProfileRepository
|
public class ProfileRepository(ILogger logger, Func<ArtemisDbContext> getContext, List<IProfileMigration> profileMigrators) : IProfileRepository
|
||||||
{
|
{
|
||||||
public void Add(ProfileContainerEntity profileContainerEntity)
|
public void Add(ProfileContainerEntity profileContainerEntity)
|
||||||
{
|
{
|
||||||
@ -83,6 +84,8 @@ public class ProfileRepository(Func<ArtemisDbContext> getContext, List<IProfileM
|
|||||||
{
|
{
|
||||||
if (profileMigrator.Version <= configurationJson["Version"]!.GetValue<int>())
|
if (profileMigrator.Version <= configurationJson["Version"]!.GetValue<int>())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
logger.Information("Migrating profile from version {OldVersion} to {NewVersion}", configurationJson["Version"], profileMigrator.Version);
|
||||||
|
|
||||||
profileMigrator.Migrate(configurationJson, profileJson);
|
profileMigrator.Migrate(configurationJson, profileJson);
|
||||||
configurationJson["Version"] = profileMigrator.Version;
|
configurationJson["Version"] = profileMigrator.Version;
|
||||||
|
|||||||
@ -47,7 +47,7 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
|||||||
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||||
}
|
}
|
||||||
else if (ConfigurationIcon.IconBytes != null)
|
else if (ConfigurationIcon.IconBytes != null)
|
||||||
Dispatcher.UIThread.Post(() => LoadFromBitmap(ConfigurationIcon, new MemoryStream(ConfigurationIcon.IconBytes)), DispatcherPriority.ApplicationIdle);
|
Dispatcher.UIThread.Post(LoadFromBitmap, DispatcherPriority.ApplicationIdle);
|
||||||
else
|
else
|
||||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||||
}
|
}
|
||||||
@ -57,14 +57,17 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadFromBitmap(Core.ProfileConfigurationIcon configurationIcon, Stream stream)
|
private void LoadFromBitmap()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_stream = stream;
|
if (ConfigurationIcon?.IconBytes == null)
|
||||||
if (!configurationIcon.Fill)
|
return;
|
||||||
|
|
||||||
|
_stream = new MemoryStream(ConfigurationIcon.IconBytes);
|
||||||
|
if (!ConfigurationIcon.Fill)
|
||||||
{
|
{
|
||||||
Content = new Image {Source = new Bitmap(stream)};
|
Content = new Image {Source = new Bitmap(_stream)};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +76,7 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
|||||||
Background = TextElement.GetForeground(this),
|
Background = TextElement.GetForeground(this),
|
||||||
VerticalAlignment = VerticalAlignment.Stretch,
|
VerticalAlignment = VerticalAlignment.Stretch,
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||||
OpacityMask = new ImageBrush(new Bitmap(stream))
|
OpacityMask = new ImageBrush(new Bitmap(_stream))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
|||||||
@ -68,8 +68,8 @@ public class SidebarCategoryViewDropHandler : DropHandlerBase
|
|||||||
{
|
{
|
||||||
int index = vm.ProfileConfigurations.IndexOf(targetItem);
|
int index = vm.ProfileConfigurations.IndexOf(targetItem);
|
||||||
if (!before)
|
if (!before)
|
||||||
index++;
|
targetItem = index < vm.ProfileConfigurations.Count - 1 ? vm.ProfileConfigurations[index + 1] : null;
|
||||||
vm.AddProfileConfiguration(sourceItem.ProfileConfiguration, index);
|
vm.AddProfileConfiguration(sourceItem.ProfileConfiguration, targetItem?.ProfileConfiguration);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -103,10 +103,10 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
|
|||||||
public bool IsCollapsed => _isCollapsed?.Value ?? false;
|
public bool IsCollapsed => _isCollapsed?.Value ?? false;
|
||||||
public bool IsSuspended => _isSuspended?.Value ?? false;
|
public bool IsSuspended => _isSuspended?.Value ?? false;
|
||||||
|
|
||||||
public void AddProfileConfiguration(ProfileConfiguration profileConfiguration, int? index)
|
public void AddProfileConfiguration(ProfileConfiguration profileConfiguration, ProfileConfiguration? target)
|
||||||
{
|
{
|
||||||
ProfileCategory oldCategory = profileConfiguration.Category;
|
ProfileCategory oldCategory = profileConfiguration.Category;
|
||||||
ProfileCategory.AddProfileConfiguration(profileConfiguration, index);
|
ProfileCategory.AddProfileConfiguration(profileConfiguration, target);
|
||||||
|
|
||||||
_profileService.SaveProfileCategory(ProfileCategory);
|
_profileService.SaveProfileCategory(ProfileCategory);
|
||||||
// If the profile moved to a new category, also save the old category
|
// If the profile moved to a new category, also save the old category
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Storage.Entities.Workshop;
|
using Artemis.Storage.Entities.Workshop;
|
||||||
|
|
||||||
@ -6,7 +8,7 @@ namespace Artemis.WebClient.Workshop.Models;
|
|||||||
|
|
||||||
public class InstalledEntry
|
public class InstalledEntry
|
||||||
{
|
{
|
||||||
private Dictionary<string, object> _metadata = new();
|
private Dictionary<string, JsonNode> _metadata = new();
|
||||||
|
|
||||||
internal InstalledEntry(EntryEntity entity)
|
internal InstalledEntry(EntryEntity entity)
|
||||||
{
|
{
|
||||||
@ -52,7 +54,7 @@ public class InstalledEntry
|
|||||||
ReleaseVersion = Entity.ReleaseVersion;
|
ReleaseVersion = Entity.ReleaseVersion;
|
||||||
InstalledAt = Entity.InstalledAt;
|
InstalledAt = Entity.InstalledAt;
|
||||||
|
|
||||||
_metadata = Entity.Metadata != null ? new Dictionary<string, object>(Entity.Metadata) : new Dictionary<string, object>();
|
_metadata = Entity.Metadata != null ? new Dictionary<string, JsonNode>(Entity.Metadata) : new Dictionary<string, JsonNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Save()
|
internal void Save()
|
||||||
@ -67,7 +69,7 @@ public class InstalledEntry
|
|||||||
Entity.ReleaseVersion = ReleaseVersion;
|
Entity.ReleaseVersion = ReleaseVersion;
|
||||||
Entity.InstalledAt = InstalledAt;
|
Entity.InstalledAt = InstalledAt;
|
||||||
|
|
||||||
Entity.Metadata = new Dictionary<string, object>(_metadata);
|
Entity.Metadata = new Dictionary<string, JsonNode>(_metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -80,14 +82,14 @@ public class InstalledEntry
|
|||||||
/// <returns><see langword="true"/> if the metadata contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
|
/// <returns><see langword="true"/> if the metadata contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
|
||||||
public bool TryGetMetadata<T>(string key, [NotNullWhen(true)] out T? value)
|
public bool TryGetMetadata<T>(string key, [NotNullWhen(true)] out T? value)
|
||||||
{
|
{
|
||||||
if (!_metadata.TryGetValue(key, out object? objectValue) || objectValue is not T result)
|
if (!_metadata.TryGetValue(key, out JsonNode? element))
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = result;
|
value = element.GetValue<T>();
|
||||||
return true;
|
return value != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -97,7 +99,7 @@ public class InstalledEntry
|
|||||||
/// <param name="value">The value to set.</param>
|
/// <param name="value">The value to set.</param>
|
||||||
public void SetMetadata(string key, object value)
|
public void SetMetadata(string key, object value)
|
||||||
{
|
{
|
||||||
_metadata[key] = value;
|
_metadata[key] = JsonSerializer.SerializeToNode(value) ?? throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user